【UCOSIII】UCOSIII軟體定時器
在學習STM32的時候會使用定時器來做很多定時任務,這個定時器是微控制器自帶的,也就是硬體定時器,在UCOSIII中提供了軟體定時器,我們可以使用這些軟體定時器完成一些功能,本文我們就講解一下UCOSIII軟體定時器。
UCOSIII軟體定時器簡介
定時器其實就是一個遞減計數器,當計數器遞減到0的時候就會觸發一個動作,這個動作就是回撥函式,當定時器計時完成時就會自動的呼叫這個回撥函式。因此我們可以使用這個回撥函式來完成一些設計。比如,定時10秒後開啟某個外設等等,在回撥函式中應避免任何可以阻塞或者刪除定時任務的函式。
如果要使用定時器的話需要將巨集OS_CFG_TMR_DEL_EN定義為1。
定時器的解析度由我們定義的系統節拍頻率OS_CFG_TICK_RATE_HZ決定
什麼是回撥函式呢?
回撥函式就是一個通過函式指標呼叫的函式。如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。回撥函式不是由該函式的實現方法直接呼叫,而是在特定的事件或條件發生時由另外的一方呼叫的,用於對該事件或條件進行響應。
UCOSIII軟體定時器API函式
函式 | 含義 |
OSTmrCreate() | 建立定時器並制定執行模式 |
OSTmrDel() | 刪除定時器 |
OSTmrRemainGet() | 獲取定時器的剩餘時間 |
OSTmrStart() | 啟動定時器計數 |
OSTmrStateGet() | 獲取當前定時器狀態 |
OSTmrStop() | 停止計數器倒計時 |
建立一個定時器
如果我們要使用定時器,肯定需要先建立一個定時器,使用OSTmrCreate()函式來建立一個定時器,這個函式也用來確定定時器的執行模式,OSTmrCreate()函式原型如下:
void OSTmrCreate (OS_TMR *p_tmr, //指向定時器的指標,巨集OS_TMR是一個結構體 CPU_CHAR *p_name, //定時器名稱 OS_TICK dly, //初始化定時器的延遲值 OS_TICK period, //重複週期的延遲值 OS_OPT opt, //定時器執行選項 OS_TMR_CALLBACK_PTR p_callback, //指向回撥函式的名字 void *p_callback_arg, //回撥函式的引數 OS_ERR *p_err) //呼叫此函式以後返回的錯誤碼 { CPU_SR_ALLOC(); OS_CRITICAL_ENTER(); p_tmr->State = (OS_STATE )OS_TMR_STATE_STOPPED; /* Initialize the timer fields */ p_tmr->Type = (OS_OBJ_TYPE )OS_OBJ_TYPE_TMR; p_tmr->NamePtr = (CPU_CHAR *)p_name; p_tmr->Dly = (OS_TICK )dly; p_tmr->Match = (OS_TICK )0; p_tmr->Remain = (OS_TICK )0; p_tmr->Period = (OS_TICK )period; p_tmr->Opt = (OS_OPT )opt; p_tmr->CallbackPtr = (OS_TMR_CALLBACK_PTR)p_callback; p_tmr->CallbackPtrArg = (void *)p_callback_arg; p_tmr->NextPtr = (OS_TMR *)0; p_tmr->PrevPtr = (OS_TMR *)0; OSTmrQty++; /* Keep track of the number of timers created */ OS_CRITICAL_EXIT_NO_SCHED(); *p_err = OS_ERR_NONE; }
opt引數:定時器執行選項,這裡有兩個模式可以選擇。OS_OPT_TMR_ONE_SHOT單次定時器,OS_OPT_TMR_PERIODIC週期定時器。
軟體定時器工作模式
單次定時器
使用OSTmrCreate()函式建立定時器時把引數opt設定為OS_OPT_TMR_ONE_SHOT,就是建立的單次定時器。建立一個單次定時器以後,我們一旦呼叫OSTmrStart()函式定時器就會從建立時定義的dly開始倒計數,直到減為0呼叫回撥函式並停止。單次定時器的定時器只執行一次。
上圖展示了單次定時器在呼叫OSTmrStart()函式後開始倒計數,將dly減為0後呼叫回撥函式的過程,到這裡定時器就停止執行,不再做任何事情了,我們可以呼叫OSTmrDel()函式來刪除這個執行完成的定時器。其實我們也可以重新呼叫OSTmrStart()函式來開啟一個已經執行完成的定時器,通過呼叫OSTmrStart()函式來重新觸發單次定時器,如下圖所示。
週期模式(無初始延遲)
使用OSTmrCreate()函式建立定時器時把引數opt設定為OS_OPT_TMR_PERIODIC,就是建立的週期定時器。當定時器倒計數完成後,定時器就會呼叫回撥函式,並且重置計數器開始下一輪的定時,就這樣一直迴圈下去。如果使用OSTmrCreate()函式建立定時器的時候,引數dly為0的話,那麼定時器在每個週期開始時計數器的初值就為period,如下圖所示。
週期定時器(有初始化延遲)
在建立定時器的時候也可以建立帶有初始化延時的,初始化延時就是OSTmrCreate()函式中的引數dly就是初始化延遲,定時器的第一個週期就是dly。當第一個週期完成後就是用引數period作為週期值,呼叫OSTmrStart()函式開啟有初始化延時的定時器,如下圖所示。
UCOSIII實際例程
例程要求:本例程新建兩個任務:任務A和任務B,任務A用於建立兩個定時器:定時器1和定時器2,任務A還建立了另外一個任務B。其中定時器1為週期定時器,初始延時為200ms,以後的定時器週期為1000ms,定時器2為單次定時器,延時為2000ms。
任務B作為按鍵檢測任務,當KEY_UP鍵按下的時候,開啟定時器1;當KEY0按下的時候開啟定時器2;當KEY1按下的時候,同時關閉定時器1和2;任務B還用來控制LED0,使其閃爍,提示系統正在執行。
定時器1定時完成以後呼叫回撥函式重新整理其工作區域的背景,並且在LCD上顯示定時器1執行的次數。定時器2定時完成後也呼叫其回撥函式來重新整理其工作區域的背景,並且顯示執行次數,由於定時器2是單次定時器,我們通過串列埠列印來觀察單次定時器的執行情況。
例子:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "key.h"
#include "includes.h"
//UCOSIII中以下優先順序使用者程式不能使用,ALIENTEK
//將這些優先順序分配給了UCOSIII的5個系統內部任務
//優先順序0:中斷服務服務管理任務 OS_IntQTask()
//優先順序1:時鐘節拍任務 OS_TickTask()
//優先順序2:定時任務 OS_TmrTask()
//優先順序OS_CFG_PRIO_MAX-2:統計任務 OS_StatTask()
//優先順序OS_CFG_PRIO_MAX-1:空閒任務 OS_IdleTask()
//任務優先順序
#define START_TASK_PRIO 3
//任務堆疊大小
#define START_STK_SIZE 128
//任務控制塊
OS_TCB StartTaskTCB;
//任務堆疊
CPU_STK START_TASK_STK[START_STK_SIZE];
//任務函式
void start_task(void *p_arg);
//任務優先順序
#define TASK1_TASK_PRIO 4
//任務堆疊大小
#define TASK1_STK_SIZE 128
//任務控制塊
OS_TCB Task1_TaskTCB;
//任務堆疊
CPU_STK TASK1_TASK_STK[TASK1_STK_SIZE];
void task1_task(void *p_arg);
OS_TMR tmr1; //定時器1
OS_TMR tmr2; //定時器2
void tmr1_callback(void *p_tmr, void *p_arg); //定時器1回撥函式
void tmr2_callback(void *p_tmr, void *p_arg); //定時器2回撥函式
int lcd_discolor[14]={ WHITE, RED, BLUE, BRED, //LCD刷屏時使用的顏色
GRED, GBLUE, BLACK, MAGENTA,
GREEN, CYAN, YELLOW,BROWN,
BRRED, GRAY };
int main(void) //主函式
{
OS_ERR err;
CPU_SR_ALLOC();
delay_init(); //時鐘初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中斷分組配置
uart_init(115200); //串列埠初始化
LED_Init(); //LED初始化
LCD_Init(); //LCD初始化
KEY_Init(); //按鍵初始化
POINT_COLOR = RED;
LCD_ShowString(30,10,200,16,16,"ALIENTEK STM32F1");
LCD_ShowString(30,30,200,16,16,"UCOSIII Examp 9-1");
LCD_ShowString(30,50,200,16,16,"KEY_UP:Start Tmr1");
LCD_ShowString(30,70,200,16,16,"KEY0:Start Tmr2");
LCD_ShowString(30,90,200,16,16,"KEY1:Stop Tmr1 and Tmr2");
LCD_DrawLine(0,108,239,108); //畫線
LCD_DrawLine(119,108,119,319); //畫線
POINT_COLOR = BLACK;
LCD_DrawRectangle(5,110,115,314); //畫一個矩形
LCD_DrawLine(5,130,115,130); //畫線
LCD_DrawRectangle(125,110,234,314); //畫一個矩形
LCD_DrawLine(125,130,234,130); //畫線
POINT_COLOR = BLUE;
LCD_ShowString(6,111,110,16,16, "TIMER1:000");
LCD_ShowString(126,111,110,16,16,"TIMER2:000");
OSInit(&err); //初始化UCOSIII
OS_CRITICAL_ENTER(); //進入臨界區
//建立開始任務
OSTaskCreate((OS_TCB * )&StartTaskTCB, //任務控制塊
(CPU_CHAR * )"start task", //任務名字
(OS_TASK_PTR )start_task, //任務函式
(void * )0, //傳遞給任務函式的引數
(OS_PRIO )START_TASK_PRIO, //任務優先順序
(CPU_STK * )&START_TASK_STK[0], //任務堆疊基地址
(CPU_STK_SIZE)START_STK_SIZE/10, //任務堆疊深度限位
(CPU_STK_SIZE)START_STK_SIZE, //任務堆疊大小
(OS_MSG_QTY )0, //任務內部訊息佇列能夠接收的最大訊息數目,為0時禁止接收訊息
(OS_TICK )0, //當使能時間片輪轉時的時間片長度,為0時為預設長度,
(void * )0, //使用者補充的儲存區
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, //任務選項
(OS_ERR * )&err); //存放該函式錯誤時的返回值
OS_CRITICAL_EXIT(); //退出臨界區
OSStart(&err); //開啟UCOSIII
}
void start_task(void *p_arg) //開始任務函式
{
OS_ERR err;
CPU_SR_ALLOC();
p_arg = p_arg;
CPU_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err); //統計任務
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN //如果使能了測量中斷關閉時間
CPU_IntDisMeasMaxCurReset();
#endif
#if OS_CFG_SCHED_ROUND_ROBIN_EN //當使用時間片輪轉的時候
//使能時間片輪轉排程功能,時間片長度為1個系統時鐘節拍,既1*5=5ms
OSSchedRoundRobinCfg(DEF_ENABLED,1,&err);
#endif
//建立定時器1
OSTmrCreate((OS_TMR *)&tmr1, //定時器1
(CPU_CHAR *)"tmr1", //定時器名字
(OS_TICK )20, //20*10=200ms
(OS_TICK )100, //100*10=1000ms
(OS_OPT )OS_OPT_TMR_PERIODIC, //週期模式
(OS_TMR_CALLBACK_PTR)tmr1_callback,//定時器1回撥函式
(void *)0, //引數為0
(OS_ERR *)&err); //返回的錯誤碼
//建立定時器2
OSTmrCreate((OS_TMR *)&tmr2,
(CPU_CHAR *)"tmr2",
(OS_TICK )200, //200*10=2000ms
(OS_TICK )0,
(OS_OPT )OS_OPT_TMR_ONE_SHOT, //單次定時器
(OS_TMR_CALLBACK_PTR)tmr2_callback, //定時器2回撥函式
(void *)0,
(OS_ERR *)&err);
OS_CRITICAL_ENTER(); //進入臨界區
//建立TASK1任務
OSTaskCreate((OS_TCB * )&Task1_TaskTCB,
(CPU_CHAR * )"Task1 task",
(OS_TASK_PTR )task1_task,
(void * )0,
(OS_PRIO )TASK1_TASK_PRIO,
(CPU_STK * )&TASK1_TASK_STK[0],
(CPU_STK_SIZE)TASK1_STK_SIZE/10,
(CPU_STK_SIZE)TASK1_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
OS_CRITICAL_EXIT(); //退出臨界區
OSTaskDel((OS_TCB*)0,&err); //刪除start_task任務自身
}
void task1_task(void *p_arg) //任務1的任務函式
{
u8 key,num;
OS_ERR err;
while(1)
{
key = KEY_Scan(0);
switch(key)
{
case WKUP_PRES: //當key_up按下的話開啟定時器1
OSTmrStart(&tmr1,&err); //開啟定時器1
printf("開啟定時器1\r\n");
break;
case KEY0_PRES: //當key0按下的話開啟定時器2
OSTmrStart(&tmr2,&err); //開啟定時器2
printf("開啟定時器2\r\n");
break;
case KEY1_PRES: //當key1按下話就關閉定時器
OSTmrStop(&tmr1,OS_OPT_TMR_NONE,0,&err); //關閉定時器1
OSTmrStop(&tmr2,OS_OPT_TMR_NONE,0,&err); //關閉定時器2
printf("關閉定時器1和2\r\n");
break;
}
num++;
if(num==50) //每500msLED0閃爍一次
{
num = 0;
LED0 = ~LED0;
}
OSTimeDlyHMSM(0,0,0,10,OS_OPT_TIME_PERIODIC,&err); //延時10ms
}
}
void tmr1_callback(void *p_tmr, void *p_arg) //定時器1的回撥函式
{
static u8 tmr1_num=0;
LCD_ShowxNum(62,111,tmr1_num,3,16,0x80); //顯示定時器1的執行次數
LCD_Fill(6,131,114,313,lcd_discolor[tmr1_num%14]);//填充區域
tmr1_num++; //定時器1執行次數加1
}
void tmr2_callback(void *p_tmr,void *p_arg) //定時器2的回撥函式
{
static u8 tmr2_num = 0;
tmr2_num++; //定時器2執行次數加1
LCD_ShowxNum(182,111,tmr2_num,3,16,0x80); //顯示定時器1執行次數
LCD_Fill(126,131,233,313,lcd_discolor[tmr2_num%14]); //填充區域
LED1 = ~LED1;
printf("定時器2執行結束\r\n");
}