簡單實用軟件定時器

軟件定時器

在嵌入式開發中,定時器是及其常見的,但考慮到芯片外設資源有限,可以自己寫一個軟件定時器,應用於對計時不是太嚴格的場合,比如LED的閃爍,定時處理某一任務等等。該軟件定時器的原理是基於滴答系統時鐘中斷,在中斷中獲得時間基,該時間基可由用戶自由設置。另外有兩種方式可以實現軟件定時處理功能,後面會講到。

軟件定時器結構體元素

首先說明一下該軟件定時器的結構體元素:

#define STIMER_EMPTY  0	
#define STIMER_VALID  1
#define STIMER_BASETIME 10   /* ms */
typedef int (*stimerproc_t)(int arg);
typedef struct {
 stimerproc_t 	proc;
 uint32_t 	arg;  	/* 傳入參數 */
 uint32_t  	inv;  	/* 時間間隔 */
 uint32_t	cnt;  	/* 時間計數 */
 uint8_t   	status; /* 定時器狀態 */
 uint32_t 	tflag; 	/* 超時標誌 */
 int        	cycle; 	/* 循環週期,-1爲無限循環 */
} stimer_t;

stimer_t結構體中有一個指針函數proc,這是用戶定時處理的任務,並且看到該指針函數有一個傳入參數,這由用戶添加定時器時傳入。其中循環週期,視具體任務情況而定。
在枚舉中定義需要使用到的定時器序號,本文使用的是指定對象定時器的方式,另外的一種方式爲定義一個大的定時器緩衝區,在添加定時器時檢測到空的定時器即可添加,這兩種方式各有取捨,看具體應用場景修改。

typedef enum {
 LED_TIMER = 0,
 KEY_TIMER,
 MAX_TIMER
}stimer_index_t;
static stimer_t g_stimer_buf[MAX_TIMER]; /* 定時器緩衝區 */

定時器核心代碼

在瞭解軟件定時器結構體元素之後,再來詳細看一下軟件定時器核心代碼:

int add_stimer(uint8_t id, stimerproc_t proc, uint32_t inv, uint32_t arg, int cycle)
{
    if (id >= MAX_TIMER) return -1;
    if (STIMER_EMPTY == g_stimer_buf[id].status) {
      g_stimer_buf[id].status = STIMER_VALID;
      g_stimer_buf[id].cnt = 0;
      g_stimer_buf[id].tflag = 0;
      g_stimer_buf[id].inv = inv;
      g_stimer_buf[id].proc = proc;
      g_stimer_buf[id].arg = arg;
      g_stimer_buf[id].cycle = cycle;
      return 1;
    }
    return -1;
}

void stop_stimer(uint8_t id)
{
    if (id >= MAX_TIMER) return;
    if (STIMER_VALID == g_stimer_buf[id].status) {
    	g_stimer_buf[id].status = STIMER_EMPTY;
  	g_stimer_buf[id].cnt = 0;
  	g_stimer_buf[id].cycle = 0;
  	g_stimer_buf[id].tflag = 0;
  	g_stimer_buf[id].inv = 0;
    }
}

void stimer_proc(void)
{
    int i;
    for (i = 0; i < MAX_TIMER; i++) {
  	if (STIMER_VALID == g_stimer_buf[i].status) {
   	    g_stimer_buf[i].cnt++;
      	    if (g_stimer_buf[i].cnt >= g_stimer_buf[i].inv) {
    	        g_stimer_buf[i].tflag = 1;
    	        g_stimer_buf[i].cnt = 0;
   	    }
        }
    }
}
void stimer_task(void)
{
    int i;
    stimerproc_t func;
    for (i = 0; i < MAX_TIMER; i++) {
  	if (STIMER_VALID == g_stimer_buf[i].status) {
   	    if (g_stimer_buf[i].tflag) {
    	        g_stimer_buf[i].tflag = 0;
                func = g_stimer_buf[i].proc;
    	        if ( func != NULL) 
      		    (*func)(g_stimer_buf[i].arg);
                if (0 == g_stimer_buf[i].cycle) 
     		    stop_stimer(i);
      	        if (g_stimer_buf[i].cycle > 0)
     		    g_stimer_buf[i].cycle--;
  	     }
  	}
    } 
}

__weak void system_tick_callback(void)
{
    static int cnt = 0;
    cnt++;
    if (cnt >= STIMER_BASETIME) {
       cnt = 0;
       stimer_proc(); 
    }
}
/* 滴答中斷1ms一次 */
void SysTick_Handler(void)
{
    HAL_IncTick();
    system_tick_callback();
}
void main(void)
{
    while (1) 
    {
	stimer_task();
    }
}

利用滴答系統時鐘產生的中斷計時獲得時間,這裏的滴答時鐘配置爲1ms一次中斷,當中斷10次時即爲一個軟件定時器的時間基。
當需要添加一個定時任務時,比如讓LED燈500ms反轉一次狀態:

int led_task_proc(int arg)
{
    bsp_led_togglepin(LED1);
    return 0;
}
/* 添加軟件定時器 */
void main(void)
{
    add_stimer(LED_TIMER, led_task_proc, 500 / STIMER_BASETIME, 0, -1);
    while (1) 
    {
        stimer_task();
    }
}

以上這種方式爲在滴答中斷中計時,在main函數中執行,另外還有一種方式針對無阻塞並對時間有要求的任務,即是把stimer_proc()與stimer_task()結合在一起實現,任務在滴答中斷中執行,切記定時任務不能阻塞滴答中斷。

void stimer_proc(void)
{
    int i;
    stimerproc_t func;
    for (i = 0; i < MAX_TIMER; i++) {
  	if (STIMER_VALID == g_stimer_buf[i].status) {
	    g_stimer_buf[i].cnt++;
       	    if (g_stimer_buf[i].cnt >= g_stimer_buf[i].inv) {
       	         g_stimer_buf[i].cnt = 0;
       	         func = g_stimer_buf[i].proc;
   		 if ( func != NULL) 
    		     (*func)(g_stimer_buf[i].arg);
    		 if (0 == g_stimer_buf[i].cycle) 
      		    stop_stimer(i);
    		 if (g_stimer_buf[i].cycle > 0)
      		    g_stimer_buf[i].cycle--;
   	  }
       }
    }
}
void main(void)
{	
    while(1)
    {
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章