目录
1 软件定时器概念及其应用
1.1 软件定时器定义
1.2 FreeRTOS软件定时器介绍
1.3 FreeRTOS软件定时器工作原理
2 软件定时器函数应用
2.1 功能需求
2.2 API
2.3 功能实现
3 软件定时器原理源码分析
3.1 软件定时器控制块
3.2 软件定时器任务&软件定时器创建
3.3 软件定时器启动&停止
1 软件定时器概念及其应用
1.1 软件定时器定义
问:为什么要有软件定时器?
因为硬件定时器的数量有限,所以会出现软件定时器这样的辅助功能。
提醒我们什么时间做什么事。
选择时间,重复模式等辅助实用功能。
智能化场景非常常见,使用软件定时器能大大减少cpu使用率。
1.2 FreeRTOS软件定时器介绍
到达时间后通过回调函数提供接口实现功能。
1.3 FreeRTOS软件定时器工作原理
横坐标是tick值,软件定时器原理即定时器,可单次执行也可周期执行。
2 软件定时器函数应用
2.1 功能需求
- 使用软件定时器功能完成闹钟功能设计(具体设计闹钟数量、重复模式、RTC)
- 当闹钟到达时,可根据执行动作,触发相关的led亮灭
2.2 API
自动装载即是否重复闹钟
可以使用一个CallBack通过ID来实现不同ID的实现方法。
内部即消息队列的发送命令。
内部即消息队列的发送命令。
Reset和Start几乎没有区别
重点,修改完后会启动定时器,不需要Start或者Reset
2.3 功能实现
设计
实现
现象
RTC相关CubeMX配置:使能外部低速时钟
命令解析、led控制创建两个任务和消息队列
Cmd针对RealTime和AlarmTIme
Led针对4个led灯解析
软件定时器相关配置,周期性功能
2.3.1实时时钟功能实现
//参考初始化代码void MX_RTC_Init改写void SetRTC(RTCTimeDates *pRTCTimeDate){if (HAL_RTC_SetTime(&hrtc, &pRTCTimeDate->RtcTime, RTC_FORMAT_BIN) != HAL_OK){Error_Handler();}if (HAL_RTC_SetDate(&hrtc, &pRTCTimeDate->RtcDate, RTC_FORMAT_BIN) != HAL_OK){Error_Handler();}
}RTCTimeDates GetRTC(void)
{RTCTimeDates RTCTimeDate;if (HAL_RTC_GetTime(&hrtc, &RTCTimeDate.RtcTime, RTC_FORMAT_BIN) != HAL_OK){Error_Handler();}if (HAL_RTC_GetDate(&hrtc, &RTCTimeDate.RtcDate, RTC_FORMAT_BIN) != HAL_OK){Error_Handler();}printf("Real Time:%d-%d-%d %d:%d:%d\n",RTCTimeDate.RtcDate.Year + 2000,RTCTimeDate.RtcDate.Month,RTCTimeDate.RtcDate.Date,RTCTimeDate.RtcTime.Hours,RTCTimeDate.RtcTime.Minutes,RTCTimeDate.RtcTime.Seconds);return RTCTimeDate;}
串口接收
void USART1_IRQHandler(void)
{/* USER CODE BEGIN USART1_IRQn 0 */uint8_t u8Data;//判断接收标志置位if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE) == SET){//读取接收寄存器u8Data = huart1.Instance->DR;//进行入队操作xQueueSendFromISR(CmdQueueHandle,&u8Data,NULL);}/* USER CODE END USART1_IRQn 0 */HAL_UART_IRQHandler(&huart1);/* USER CODE BEGIN USART1_IRQn 1 *//* USER CODE END USART1_IRQn 1 */
}void Usart_Task(void const * argument)
{/* USER CODE BEGIN Usart_Task */uint8_t u8Index;/* Infinite loop */for(;;){//每次读取消息之前,把索引初始化为0u8Index = 0;//1、一直等待接收消息,第一个消息应该放在消息缓冲区的第一个元素上if(xQueueReceive(CmdQueueHandle,&u8CmdBuff[u8Index++],portMAX_DELAY)==pdPASS){while(xQueueReceive(CmdQueueHandle,&u8CmdBuff[u8Index++],50)){}u8CmdBuff[u8Index] = '\0';//保证一包完整字符串信息vCmdParseString(u8CmdBuff);//完成解析以后,要清空接收缓冲区,不然会出现问题memset(u8CmdBuff,0,MESSAGE_BUFF_SIZE);}}/* USER CODE END Usart_Task */
}
解析字符串
//解析串口命令字符串
void vCmdParseString(uint8_t *buff){//判断是否为实时时钟设置if(strncmp((char const*)buff,REALTIME,strlen(REALTIME)) == 0){ParseRealTimeString(buff);}//判断是否为闹钟设置else if(strncmp((char const*)buff,ALARMTIME,strlen(ALARMTIME)) == 0){ParseAlarmTimeString(buff);}}
解析实时时钟和闹钟
//计算闹钟与实时时钟之前的间隔时间,返回ms
uint32_t CountAlarmInterval(sAlarmTime AlarmTime){int32_t AlarmTimeTick,RealTimeTick;RTCTimeDates RTCTimeDate;//获取实时时钟RTCTimeDate = GetRTC();//计算闹钟ms计数AlarmTimeTick = AlarmTime.Hours*HT0MS+AlarmTime.Minutes*MT0MS+AlarmTime.Seconds*ST0MS;//计算实时时钟ms计数RealTimeTick = RTCTimeDate.RtcTime.Hours*HT0MS+RTCTimeDate.RtcTime.Minutes*MT0MS+RTCTimeDate.RtcTime.Seconds*ST0MS;printf("AlarmTimeTick = %lu\r\n",AlarmTimeTick);printf("RealTimeTick = %lu\r\n",RealTimeTick);//判断闹钟是否大于等于当前实时时钟//大于->返回闹钟-实时时钟if((AlarmTimeTick-RealTimeTick) >= 0){return AlarmTimeTick-RealTimeTick;}else{//小于->一天的ms值+实时时钟-返回闹钟return DT0MS+RealTimeTick-AlarmTimeTick;}}void ParseAlarmTimeString(uint8_t *buff){char *pbufftime;char *pbufftimeindex;char *pbuffparm;char *pbuffparmindex;uint32_t AlarmTick;TimerHandle_t xTimer;sAlarmTime AlarmTime;void SetRTC(RTCTimeDates *pRTCTimeDate);//获取闹钟时间字符串指针pbufftime = strstr((char const *)buff, ":");//获取闹钟参数字符串指针pbuffparm = strstr((char const *)buff, ",");if (pbufftime != NULL){//指针加1 取出正确的头指针pbufftime++;//取出正确的尾指针pbufftime = strtok(pbufftime, ",");//取出小时pbufftimeindex = strtok(pbufftime, ":");memcpy(AlarmTimeString.Hours, pbufftimeindex, strlen(pbufftimeindex));//取出分钟pbufftimeindex = strtok(NULL, ":");memcpy(AlarmTimeString.Minutes, pbufftimeindex, strlen(pbufftimeindex));//取出秒pbufftimeindex = strtok(NULL, ":");memcpy(AlarmTimeString.Seconds, pbufftimeindex, strlen(pbufftimeindex));}if (pbuffparm != NULL){//指针加1 取出正确的头指针pbuffparm++;//取出工作模式pbuffparmindex = strtok(pbuffparm, ",");memcpy(AlarmTimeString.Mode, pbuffparmindex, strlen(pbuffparmindex));//取出执行动作pbuffparmindex = strtok(NULL, ",");memcpy(AlarmTimeString.Action, pbuffparmindex, strlen(pbuffparmindex));}printf("设置闹钟系统时间为:%s:%s:%s\r\n",AlarmTimeString.Hours,AlarmTimeString.Minutes,AlarmTimeString.Seconds); printf("设置闹钟工作模式为:%s\r\n",AlarmTimeString.Mode); printf("设置闹钟执行动作为:%s\r\n",AlarmTimeString.Action); //转换字符串格式的闹钟参数为整型值AlarmTime.Hours = atoi((char const *)AlarmTimeString.Hours);AlarmTime.Minutes = atoi((char const *)AlarmTimeString.Minutes);AlarmTime.Seconds = atoi((char const *)AlarmTimeString.Seconds);AlarmTime.Mode = atoi((char const *)AlarmTimeString.Mode);AlarmTime.Action = atoi((char const *)AlarmTimeString.Action);//计数周期间隔AlarmTick = CountAlarmInterval(AlarmTime);printf("当前闹钟间隔为:%lu\r\n",AlarmTick);//创建定时器,传入间隔、工作模式、触发动作xTimer = xTimerCreate("timer",AlarmTick,AlarmTime.Mode,(void*)AlarmTime.Action,vTimerCallback);//判断定时器是否创建成功if(xTimer != NULL){//启动定时器xTimerStart(xTimer,0);printf("启动定时器成功!\r\n");}}//解析实时时钟字符串
void ParseRealTimeString(uint8_t *buff)
{char *pbuffdate;char *pbuffdateindex;char *pbufftime;char *pbufftimeindex;RTCTimeDates RTCTimeDate;//获取日期字符串指针pbuffdate = strstr((char const *)buff, ":");//获取时间字符串指针pbufftime = strstr((char const *)buff, ",");if (pbuffdate != NULL){//指针加1 取出正确的头指针pbuffdate++;//取出正确的尾指针pbuffdate = strtok(pbuffdate, ",");//取出年pbuffdateindex = strtok(pbuffdate, "-");memcpy(RealTimeString.Year, pbuffdateindex, strlen(pbuffdateindex));//取出月pbuffdateindex = strtok(NULL, "-");memcpy(RealTimeString.Month, pbuffdateindex, strlen(pbuffdateindex));//取出天pbuffdateindex = strtok(NULL, "-");memcpy(RealTimeString.Date, pbuffdateindex, strlen(pbuffdateindex));}if (pbufftime != NULL){//指针加1 取出正确的头指针pbufftime++;//取出小时pbufftimeindex = strtok(pbufftime, ":");memcpy(RealTimeString.Hours, pbufftimeindex, strlen(pbufftimeindex));//取出分钟pbufftimeindex = strtok(NULL, ":");memcpy(RealTimeString.Minutes, pbufftimeindex, strlen(pbufftimeindex));//取出秒pbufftimeindex = strtok(NULL, ":");memcpy(RealTimeString.Seconds, pbufftimeindex, strlen(pbufftimeindex));}printf("设置当前系统时间为:%s-%s-%s,%s:%s:%s\r\n",RealTimeString.Year,RealTimeString.Month,RealTimeString.Date,RealTimeString.Hours,RealTimeString.Minutes,RealTimeString.Seconds);//字符串转换为实时时钟值RTCTimeDate.RtcDate.Year = atoi((char const *)RealTimeString.Year) - 2000;RTCTimeDate.RtcDate.Month = atoi((char const *)RealTimeString.Month);RTCTimeDate.RtcDate.Date = atoi((char const *)RealTimeString.Date);RTCTimeDate.RtcTime.Hours = atoi((char const *)RealTimeString.Hours);RTCTimeDate.RtcTime.Minutes = atoi((char const *)RealTimeString.Minutes);RTCTimeDate.RtcTime.Seconds = atoi((char const *)RealTimeString.Seconds);//设置当前实时时钟SetRTC(&RTCTimeDate);
}
闹钟触发回调函数
//这个定义再Timer.c中,要再freertos.c中使用必须重新再定义
//主要因为需要针对控制块,进行重新装载if(((xTIMER*)pxTimer)->uxAutoReload){
typedef struct tmrTimerControl
{const char *pcTimerName; /*<< Text name. This is not used by the kernel, it is included simply to make debugging easier. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ListItem_t xTimerListItem; /*<< Standard linked list item as used by all kernel features for event management. */TickType_t xTimerPeriodInTicks;/*<< How quickly and often the timer expires. */UBaseType_t uxAutoReload; /*<< Set to pdTRUE if the timer should be automatically restarted once expired. Set to pdFALSE if the timer is, in effect, a one-shot timer. */void *pvTimerID; /*<< An ID to identify the timer. This allows the timer to be identified when the same callback is used for multiple timers. */TimerCallbackFunction_t pxCallbackFunction; /*<< The function that will be called when the timer expires. */#if( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxTimerNumber; /*<< An ID assigned by trace tools such as FreeRTOS+Trace */#endif#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )uint8_t ucStaticallyAllocated; /*<< Set to pdTRUE if the timer was created statically so no attempt is made to free the memory again if the timer is later deleted. */#endif
} xTIMER;//闹钟触发的软件定时器,回调函数
void vTimerCallback(xTimerHandle pxTimer){uint32_t ulTimerID;uint8_t i;//获取当前定时器IDulTimerID = (uint32_t)pvTimerGetTimerID(pxTimer);//判断定时器工作模式,如果自动重载,则更新软件定时器周期if(((xTIMER*)pxTimer)->uxAutoReload){xTimerChangePeriodFromISR(pxTimer,DT0MS,NULL);printf("明天继续触发动作!!!\r\n");}printf("ulTimerID = %d\r\n",ulTimerID);//根据软件定时器ID号, 发送到led消息队列中for(i=0;i<strlen((char const*)LedCmdString[ulTimerID]);i++){xQueueSend(LedQueueHandle,&LedCmdString[ulTimerID][i],1);}}
问:如果不自动重载,直接更新软件定时器周期行不行?
不行,因为更新软件定时器周期,相当于重启定时器,本来只执行1次的,会重复执行。
LED接收任务
void vLedParseString(uint8_t *buff){uint8_t i;for(i=0;i<LED_NUM;i++){if(strcmp((char const*)buff,(char const*)OpenString[i]) == 0){HAL_GPIO_WritePin(LedPort[i], LedPin[i], GPIO_PIN_RESET);printf("Cmd is %s\n",OpenString[i]);return;}}for(i=0;i<LED_NUM;i++){if(strcmp((char const*)buff,(char const*)CloseString[i]) == 0){HAL_GPIO_WritePin(LedPort[i], LedPin[i], GPIO_PIN_SET);printf("Cmd is %s\n",CloseString[i]);return;}}}
/*** @brief Function implementing the LedTask thread.* @param argument: Not used * @retval None*/
/* USER CODE END Header_Led_Task */
void Led_Task(void const * argument)
{/* USER CODE BEGIN Led_Task */uint8_t u8Index;osTimerStart(RrcTimerHandle,1000);/* Infinite loop */for(;;){//每次读取消息之前,把索引初始化为0u8Index = 0;//1、一直等待接收消息,第一个消息应该放在消息缓冲区的第一个元素上if(xQueueReceive(LedQueueHandle,&u8LedMessageBuff[u8Index++],portMAX_DELAY)==pdPASS){while(xQueueReceive(LedQueueHandle,&u8LedMessageBuff[u8Index++],50)){}u8LedMessageBuff[u8Index] = '\0';//保证一包完整字符串信息vLedParseString(u8LedMessageBuff);//完成解析以后,要清空接收缓冲区,不然会出现问题memset(u8LedMessageBuff,0,MESSAGE_BUFF_SIZE);} }/* USER CODE END Led_Task */
}
3 软件定时器原理源码分析
3.1 软件定时器控制块
typedef struct tmrTimerQueueMessage
{BaseType_t xMessageID; union{TimerParameter_t xTimerParameters;#if ( INCLUDE_xTimerPendFunctionCall == 1 )CallbackParameters_t xCallbackParameters;#endif /* INCLUDE_xTimerPendFunctionCall */} u;
} DaemonTaskMessage_t;typedef struct tmrTimerParameters
{TickType_t xMessageValue; /*一个可选值,例如,传入更改计时器的周期*/Timer_t * pxTimer; /*<< The timer to which the command will be applied. */
} TimerParameter_t;typedef struct tmrCallbackParameters
{PendedFunction_t pxCallbackFunction; /* << The callback function to execute. */void *pvParameter1; // 如事件标志组句柄uint32_t ulParameter2; // 如事件标志组位信息
} CallbackParameters_t;typedef struct tmrTimerControl
{const char *pcTimerName; //名称而已ListItem_t xTimerListItem; //列表项TickType_t xTimerPeriodInTicks;//计时周期UBaseType_t uxAutoReload; //pdTRUE:自动装载pdFALSE:单次计时void *pvTimerID; //ID号,方便回调识别TimerCallbackFunction_t pxCallbackFunction; //计时器到期时将被调用的函数} xTIMER;
3.2 软件定时器任务&软件定时器创建
问:为什么创建两个列表
在systick中,tick值时u32,在不断累加到一定程度会溢出。那么之前活动列表就会出现错误,所以每当溢出得时候,就要进行列表得切换,所以创建了2个列表,防止程序出错。
BaseType_t xTimerCreateTimerTask( void )
{
BaseType_t xReturn = pdFAIL;/* 1、检查 软件定时器列表和队列2、如果没有创建内存空间,需要新建*/prvCheckForValidListAndQueue();if( xTimerQueue != NULL ){#else{//为了满足软件定时器的实时性,软件定时器任务的优先级最高,其实就是最大值xReturn = xTaskCreate( prvTimerTask,"Tmr Svc",configTIMER_TASK_STACK_DEPTH,NULL,( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,&xTimerTaskHandle );}#endif /* configSUPPORT_STATIC_ALLOCATION */}else{mtCOVERAGE_TEST_MARKER();}configASSERT( xReturn );return xReturn;
}static void prvCheckForValidListAndQueue( void )
{taskENTER_CRITICAL();{//如果队列为空,则进行列表的初始化和队列的创建if( xTimerQueue == NULL ){vListInitialise( &xActiveTimerList1 );vListInitialise( &xActiveTimerList2 );pxCurrentTimerList = &xActiveTimerList1;pxOverflowTimerList = &xActiveTimerList2;//创建消息队列/*消息队列参数:1、configTIMER_QUEUE_LENGTH ------软件定时器 队列长度 102、DaemonTaskMessage_t ------整个软件定时器消息的大小*/{xTimerQueue = xQueueCreate( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ) );}#endif}else{mtCOVERAGE_TEST_MARKER();}}taskEXIT_CRITICAL();
}
即初始化值
TimerHandle_t xTimerCreate( const char * const pcTimerName,const TickType_t xTimerPeriodInTicks,const UBaseType_t uxAutoReload,void * const pvTimerID,TimerCallbackFunction_t pxCallbackFunction ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */{Timer_t *pxNewTimer;//动态分配 软件定时器控制块内存空间pxNewTimer = ( Timer_t * ) pvPortMalloc( sizeof( Timer_t ) );if( pxNewTimer != NULL ){//进入控制初始化prvInitialiseNewTimer( pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer );}return pxNewTimer;}static void prvInitialiseNewTimer( const char * const pcTimerName,const TickType_t xTimerPeriodInTicks,const UBaseType_t uxAutoReload,void * const pvTimerID,TimerCallbackFunction_t pxCallbackFunction,Timer_t *pxNewTimer ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
{/* 0 is not a valid value for xTimerPeriodInTicks. */configASSERT( ( xTimerPeriodInTicks > 0 ) );if( pxNewTimer != NULL ){/* 再次判断是否已经创建 队列 初始化了列表 */prvCheckForValidListAndQueue();/*1、进行软件定时器控制块信息的赋值2、把当前软件定时器列表项初始化,便于以后使用*/pxNewTimer->pcTimerName = pcTimerName;pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;pxNewTimer->uxAutoReload = uxAutoReload;pxNewTimer->pvTimerID = pvTimerID;pxNewTimer->pxCallbackFunction = pxCallbackFunction;vListInitialiseItem( &( pxNewTimer->xTimerListItem ) );traceTIMER_CREATE( pxNewTimer );}
}
3.3 软件定时器启动&停止
#define xTimerStart( xTimer, xTicksToWait )
/*参数:1、软件定时器句柄2、定义Start编号3、当前的系统的Tick值4、null5、阻塞等待时间
*/
xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )#define xTimerStop( xTimer, xTicksToWait )
/*参数:1、软件定时器句柄2、定义Stop编号3、0 不需要传入消息4、null5、阻塞等待时间
*/
xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP, 0U, NULL, ( xTicksToWait ) )#define xTimerChangePeriod( xTimer, xNewPeriod, xTicksToWait )
/*参数:1、软件定时器句柄2、定义CHANGE编号3、xNewPeriod 用于改变新的周期4、null5、阻塞等待时间
*/
xTimerGenericCommand( ( xTimer ), tmrCOMMAND_CHANGE_PERIOD, ( xNewPeriod ), NULL, ( xTicksToWait ) )BaseType_t xTimerGenericCommand( TimerHandle_t xTimer, const BaseType_t xCommandID, const TickType_t xOptionalValue, BaseType_t * const pxHigherPriorityTaskWoken, const TickType_t xTicksToWait )
{
BaseType_t xReturn = pdFAIL;
DaemonTaskMessage_t xMessage;if( xTimerQueue != NULL ){/* 1、xCommandID 用于标识 触发的类型 比如start2、xOptionalValue = xTaskGetTickCount在start时,才是软件定时器的真正启动,内部参考systick,这个时候要传入一个初值,才能计算3、xTimer 要操作的软件定时器的句柄*/xMessage.xMessageID = xCommandID;xMessage.u.xTimerParameters.xMessageValue = xOptionalValue;xMessage.u.xTimerParameters.pxTimer = ( Timer_t * ) xTimer;//判断命令类型if( xCommandID < tmrFIRST_FROM_ISR_COMMAND ){//判断调度器状态if( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING ){xReturn = xQueueSendToBack( xTimerQueue, &xMessage, xTicksToWait );}else{xReturn = xQueueSendToBack( xTimerQueue, &xMessage, tmrNO_DELAY );}}else{xReturn = xQueueSendToBackFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken );}traceTIMER_COMMAND_SEND( xTimer, xCommandID, xOptionalValue, xReturn );}else{mtCOVERAGE_TEST_MARKER();}return xReturn;
}