目录
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(;;)
{
//每次读取消息之前,把索引初始化为0
u8Index = 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;
//获取当前定时器ID
ulTimerID = (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(;;)
{
//每次读取消息之前,把索引初始化为0
u8Index = 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 ------软件定时器 队列长度 10
2、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、null
5、阻塞等待时间
*/
xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )
#define xTimerStop( xTimer, xTicksToWait )
/*
参数:
1、软件定时器句柄
2、定义Stop编号
3、0 不需要传入消息
4、null
5、阻塞等待时间
*/
xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP, 0U, NULL, ( xTicksToWait ) )
#define xTimerChangePeriod( xTimer, xNewPeriod, xTicksToWait )
/*
参数:
1、软件定时器句柄
2、定义CHANGE编号
3、xNewPeriod 用于改变新的周期
4、null
5、阻塞等待时间
*/
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 用于标识 触发的类型 比如start
2、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;
}