1. 程式人生 > >【UCOSIII】UCOSIII軟體定時器

【UCOSIII】UCOSIII軟體定時器

在學習STM32的時候會使用定時器來做很多定時任務,這個定時器是微控制器自帶的,也就是硬體定時器,在UCOSIII中提供了軟體定時器,我們可以使用這些軟體定時器完成一些功能,本文我們就講解一下UCOSIII軟體定時器。

UCOSIII軟體定時器簡介

定時器其實就是一個遞減計數器,當計數器遞減到0的時候就會觸發一個動作,這個動作就是回撥函式,當定時器計時完成時就會自動的呼叫這個回撥函式。因此我們可以使用這個回撥函式來完成一些設計。比如,定時10秒後開啟某個外設等等,在回撥函式中應避免任何可以阻塞或者刪除定時任務的函式。

如果要使用定時器的話需要將巨集OS_CFG_TMR_DEL_EN定義為1。

定時器的解析度由我們定義的系統節拍頻率OS_CFG_TICK_RATE_HZ決定

,比如我們定義為200,系統時鐘週期就是5ms,定時器的最小解析度肯定就是5ms。但是定時器的實際分 辨 率 是 通 過 巨集OS_CFG_TMR_TASK_RATE_HZ定 義 的 , 這 個 值 絕 對 不 能 大 於OS_CFG_TICK_RATE_HZ。比如我們定義OS_CFG_TMR_TASK_RATE_HZ為100,則定時器的時間解析度為10ms。有關UCOSIII定時器的函式都在os_tmr.c檔案中。

什麼是回撥函式呢?

回撥函式就是一個通過函式指標呼叫的函式。如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。回撥函式不是由該函式的實現方法直接呼叫,而是在特定的事件或條件發生時由另外的一方呼叫的,用於對該事件或條件進行響應。


UCOSIII軟體定時器API函式

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");
}