軟件定時器
在嵌入式開發中,定時器是及其常見的,但考慮到芯片外設資源有限,可以自己寫一個軟件定時器,應用於對計時不是太嚴格的場合,比如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)
{
}
}