No.1 定時器介面timer的設計
<span style="font-size: 12px; background-color: rgb(255, 255, 255);"> </span><span style="font-size: 12px; background-color: rgb(255, 255, 255);"><span style="font-family:KaiTi_GB2312;">微控制器的種類繁多,且架構不同,導致開發週期長,效率低,排錯難。本人就深受其苦。由此,需要一種程式化的方法來梳理整個微控制器軟體系統。</span></span>
在參考有關微控制器及c語言的書籍後,暫將普通的微控制器程式(不考慮作業系統)分為四個層次。
1、硬體基礎層(Hardware Base Layer):該層由生產具體晶片的廠商提供,例如reg51.h標頭檔案和Startup_A51.asm等。一般微控制器入門從該層開始。但該層是對具體硬體的描述,僅從該層開發效率低。即使面對相同的晶片,實現的功能不同就有可能會成為編寫軟體的障礙。
2、硬體抽象層(Hardware Absract Layer):該層如同我們平時用c語言編寫的微控制器相關函式,如常用的timer_init()、usart_init()、usart_sendData()等。進入微控制器程式設計至今已有兩年,回顧寫過的這類函式,大部分都是用void作為形參或返回值。這樣的函式雖然有了結構化的雛形,但離模組化、功能化還很遠。
3、應用程式介面層(Application Interface Layer):一般的微控制器程式是沒有該層的,有人會有這樣的考慮,微控制器的RAM和ROM都很有限,過多的函式呼叫會受到硬體資源的限制,並且實時性變差。但沒有這一層級,我們所需實現的綜合功能就要直接和硬體打交道,而硬體邏輯和我們所需要的功能邏輯有一定的出入,不相容的邏輯體系會使開發工程左右為難,因此,這一層的功能便是在受限的硬體邏輯和開發者需要的功能邏輯之間相互轉換。如果介面設計良好,對於微控制器軟體體系的改良是有幫助的。
4、應用程式層(Application Layer):這一層主要實現使用者想要的功能,如:控制電機的轉速恆定,控制加熱器溫度恆定,自制的mp3等。
此文提供一個案例,便是定時器介面timer的設計
定時器的需求定位:
用於定時任務,間隔一定的時長來完成一些使用者操作。
設計的介面為:
<span style="font-family:KaiTi_GB2312;"><span style="font-family:Courier New;"><span style="white-space:pre"> </span>void Timer_init(void); //初始化定時器相關硬體
Timer_T Timer_new(void); //新建定時器
void Timer_run(Timer_T tim,int msec); //執行定時器
void Timer_stop(Timer_T tim); //停止定時器
char Timer_isTimeout(Timer_T tim); //判斷定時器超時
void Timer_clearTimeout(Timer_T tim); //清除定時器超時標誌</span></span>
該介面使用了一個結構體Struct Timer_T,而Timer_T是指向該結構體的指標型別,這樣使用者便不需要了解該結構體的具體細節,只需用Timer_T指標來標識要使用的定時器。全部介面均圍繞該指標來實現,降低使用者使用介面的難度。
在實現方面,由於在微控制器中實現動態分配記憶體較困難,該實現使用了Struct Timer陣列TimerArray[TIMER_TOTAL_NUM]來儲存定時器的具體引數。
<span style="font-family:KaiTi_GB2312;"><span style="font-family:Courier New;"><span style="white-space:pre"> </span>struct Timer
{
int total_time; //總時長
int left_time; //剩餘時長
Timer_Status status; //定時器狀態
char timeout_flag; //超時標誌,1有效
}TimerArray[TIMER_TOTAL_NUM];</span></span>
函式的實現如下:
<span style="font-family:KaiTi_GB2312;"><span style="font-family:Courier New;">#include "timer.h"
#include <reg51.h>
#define T Timer_T
typedef struct T *T;</span></span>
<span style="font-family:KaiTi_GB2312;"><span style="font-family:Courier New;">typedef enum Timer_Status_Enum
{
stop_sta =0,
run_sta = 1
}Timer_Status;
struct Timer_T
{
int total_time; <span style="white-space:pre"> </span>//總時長
int left_time; <span style="white-space:pre"> </span>//剩餘時長
Timer_Status status; <span style="white-space:pre"> </span>//定時器狀態
char timeout_flag; <span style="white-space:pre"> </span>//超時標誌 ,1有效
}TimerArray[TIMER_TOTAL_NUM];
static timer_num = 0; <span style="white-space:pre"> </span>//指示已包含的定時器數目
void timer0_init(void);
//初始化定時器
void Timer_init(void)
{
timer0_init(); //硬體相關
}
//建立一個新的定時器,返回其指標,不成功則返回NULL
T Timer_new(void)
{
T tim = (void *)0;
if(timer_num < TIMER_TOTAL_NUM)
{
tim = &TimerArray[timer_num];
tim->total_time = 0;
tim->left_time = 0;
tim->status = stop_sta;
tim->timeout_flag = 0;
timer_num++;
}
return tim;
}
//判斷是否定時器存在,檔案內部使用
static char Timer_isTimerExist(T tim)
{
int i;
for(i=0; i<timer_num; i++)
{
if(tim == &TimerArray[i])
return 1;
}
return 0;
}
//執行指定的定時器
void Timer_run(T tim,int msec)
{
if(Timer_isTimerExist(tim))
{
if(msec>0)
{
tim->total_time = msec;
tim->left_time = msec;
tim->timeout_flag = 0;
tim->status = run_sta;
}
}
}
//停止指定的定時器,復位其引數
void Timer_stop(T tim)
{
if(Timer_isTimerExist(tim))
{
if(tim->status == run_sta)
{
tim->status = stop_sta;
tim->left_time = tim->total_time;
tim->timeout_flag = 0;
}
}
}
//判斷超時
char Timer_isTimeout(T tim)
{
if( Timer_isTimerExist(tim) )
return tim->timeout_flag;
else
return 0;
}
//清除超時標誌
void Timer_clearTimeout(T tim)
{
if( Timer_isTimerExist(tim) )
tim->timeout_flag = 0;
}
/*----------------------------硬體抽象---------------------------*/
//初始化定時器
void timer0_init(void)
{
/* 初始化定時器硬體部分實現:
1、1ms精確定時
2、在中斷服務函式中處理 剩餘時長 和 超時標誌
*/
TMOD = 0x02; //定時器0工作於模式2,自動重灌
TH0 = 0x38; //256-200
TL0 = 0x38; //256-200
ET0 = 1; //允許定時器0中斷
EA = 1; //允許所有中斷
TR0 = 1; //啟動定時器0
}
//中斷處理函式
void timer0(void) interrupt 1
{
static char cnt=0;
unsigned char i;
T tim = &TimerArray[0];
if(cnt++ == 4)
{
cnt = 0;
for(i=0; i<timer_num; i++)
{
if(tim->status == run_sta)
{
if(tim->left_time>0)
tim->left_time--;
else
{
tim->left_time = tim->total_time;
tim->timeout_flag = 1;
}
}
tim++;
}
}
}
#undef T</span></span>
該程式在Proteus8.0軟體上模擬,使用微控制器AT89C51。
<span style="font-family:KaiTi_GB2312;">void main(void)
{
Timer_T tim1,tim2;
G1 = 0;
Timer_init();
tim1 = Timer_new();
tim2 = Timer_new();
Timer_run(tim1,10000);<span style="white-space:pre"> </span>//定時器1定時10s
Timer_run(tim2,2000);<span style="white-space:pre"> </span>//<span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">定時器2定時2s</span></span>
<span style="font-family:KaiTi_GB2312;"> while(1)
{
if(Timer_isTimeout(tim1))
{
G1 = !G1;
Timer_clearTimeout(tim1);
}
if(Timer_isTimeout(tim2))
{
R1 = !R1;
Timer_clearTimeout(tim2);
}
delay(200);
}
}</span>
結果分析:
1、實現了定時效果,定時精度高,達到0.001s左右;
2、在while(1)迴圈中新增延時函式delay(200)後,printf函式執行時間變長,但不存在累計性的時間誤差,原因是:硬體定時器使用了自動重灌模式,加上定時器中斷例程執行時間短,使得定時器的精確性得到保證。
3、該介面缺失的功能:在超時後完成使用者的操作,本例是在main函式的while(1)中執行,一種設想的介面為:在超時後啟動一個時間極短的硬體定時器,將使用者操作作為定時器中斷例程來執行,這樣定時器的超時響應極短,但這個定時器的執行時間應該合適,考慮其與Timer硬體定時器中斷例程執行順序的先後關係。
<span style="font-family:KaiTi_GB2312;"><pre name="code" class="cpp"><span style="font-size: 12px; font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">參考書籍:《c語言介面與實現——建立可重用軟體的技術》 【美】David R. Hansion著 郭旭譯</span></span>