基於freeRTOS定時器實現鬧鍾(定時)任務
在智能硬件產品中硬件中,鬧鍾定時任務是基本的需求。一般通過APP設置定時任務,從雲端或者是APP直連硬件將鬧鍾任務保存在硬件flash中,硬件運行時會去處理鬧鍾任務。
最簡單的實現方式是在循環或者定時器處理函數中不斷的去判斷當前時間是否等於鬧鍾設定時間,若相等則產生相應的動作。
這樣做雖然可行,但是做了太多無用的計算。我們可以根據當前時間距離下一次鬧鍾激發時間,設定一個對應的定時器,定時器激發時就是鬧鍾時間,然后繼續根據下次激發時間設定新的定時器,這樣可以減少不必要的時間比較。
鬧鍾任務的表示
鬧鍾任務的表示包含一下及部分的內容:鬧鍾時間、重復類型、響應操作。
鬧鍾任務的本地表示可以根據cron格式來定義,當然也可以DIY一個,只要包含以上三個方面內容的就可以。
cron格式的時間表示如下:
Character | Descriptor | Acceptable Values |
---|---|---|
1 | Minute | 0~59, * (no specific value) |
2 | Hour | 0~23, * (no specific value) |
3 | Day of month | 1~31, * (no specific value) |
4 | Month | 1~12, * (no specific value) |
5 | Day of week | 0~7, * (no specific value) |
比如對於一個周一到周五早上7:00的鬧鍾,可以表示如為:0 7 * * 1,2,3,4,5
。
我在代碼中定義了如下的鬧鍾任務:
/* cron 格式時間表示 */
typedef struct
{
int min; // minute, 0xFFFFFFFF表示*
int hour; // hour, 0xFFFFFFFF表示*
int wday; // day of week, 0xFFFFFFFF表示*,bit[0~6] 表示周一到周日,如周一到周五每天響鈴,則0x1F
int mday; // day of month, 0xFFFFFFFF表示*, bit[0~31] 表示1~31日,每月1,3,5號響鈴,則0x15
int mon; // month, 0xFFFFFFFF表示*,bit[0~11] 表示1~12月
} cron_tm_t;
/* 鬧鍾任務 */
typedef struct
{
int id; // 任務ID
cron_tm_t cron_tm; // cron格式時間
int action; // 響應操作,對於燈控產品來說,action可以表示開關、顏色、場景等等
TimerHandle_t xTimer; // 定時器句柄
list_node_t node; // 節點,用於將一系列定時任務組織成list
} alarm_task_t;
鬧鍾下一次激發時間
基於定時器實現鬧鍾的原理是在每次設置鬧鍾時計算出距離下一次激發的間隔時間,然后設置一個相應時間的定時器。
首先需要根據cron格式時間計算出下一次激發的時間
/* 距離鬧鍾下一次激發的時間(min) */
int get_expiry_time(alarm_task_t *alarm_task)
{
/* 首先獲取當前時間 */
time_t t;
struct tm now;
time(&t);
localtime_r(&t, &now);
int ret = 0;
if(alarm_task->cron_tm.min != 0xFFFFFFFF)
{
ret += cron_tm.min - now.tm_min;
}
if(alarm_task->cron_tm.hour != 0xFFFFFFFF)
{
int flag = 0;
for(int i=0; i<24; i++)
{
if(i == alarm_task->cron_tm.hour){
flag=1;
break;
}
}
if(flag){
ret += 60*(i-now.tm_hour);
}
}
/* 找到wday或者mday中距離今天最近的一天 */
int t = ret<0?1:0;
int d_wday=0;
int d_mday=0;
for(int i=0; i<7; i++)
{
int wday = now.tm_wday + t +i;
if(1<<(wday%7) & alarm_task->cron_tm.wday){
d_wday=i+t;
break;
}
}
for(int i=0; i<30; i++)
{
int mday = now.tm_mday + t +i;
if(1<<(mday%30) & alarm_task->cron_tm_.mday){
d_mday = i+t;
break;
}
}
int d = d_mday<d_wday?d_mday:d_wday;
ret += d*24*60;
return ret;
}
設置定時器
每次硬件上電時,或者鬧鍾激發時,都要根據下一次激發的時間,設置一次定時器。
int set_timer(alarm_task_t *alarm_task)
{
int t = get_expiry_time(alarm_task);
if(t <= 0) return -1; // 不需要設置下一次鬧鍾,不重復的鬧鍾,且時間已過
alarm_task->xTimer = xTimerCreate( "timer",
t / portTICK_RATE_MS,
1,
(void*)alarm_task,
timer_task_callback );
if(alarm_task->xTimer == NULL)
{
printf("!!! timer created failed\n");
return -1;
}
else
{
xTimerStart(alarm_task->xTimer, 0);
}
return 0;
}