FreeRTOS源码分析-11 软件定时器

目录

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 软件定时器定义

问:为什么要有软件定时器?

因为硬件定时器的数量有限,所以会出现软件定时器这样的辅助功能。

FreeRTOS源码分析-11 软件定时器_第1张图片

提醒我们什么时间做什么事。

选择时间,重复模式等辅助实用功能。

FreeRTOS源码分析-11 软件定时器_第2张图片

智能化场景非常常见,使用软件定时器能大大减少cpu使用率。

1.2 FreeRTOS软件定时器介绍

FreeRTOS源码分析-11 软件定时器_第3张图片

到达时间后通过回调函数提供接口实现功能。

 1.3 FreeRTOS软件定时器工作原理

FreeRTOS源码分析-11 软件定时器_第4张图片

横坐标是tick值,软件定时器原理即定时器,可单次执行也可周期执行。

2 软件定时器函数应用

2.1 功能需求

  • 使用软件定时器功能完成闹钟功能设计(具体设计闹钟数量、重复模式、RTC)
  • 当闹钟到达时,可根据执行动作,触发相关的led亮灭

FreeRTOS源码分析-11 软件定时器_第5张图片

2.2 API

FreeRTOS源码分析-11 软件定时器_第6张图片

自动装载即是否重复闹钟

可以使用一个CallBack通过ID来实现不同ID的实现方法。

FreeRTOS源码分析-11 软件定时器_第7张图片

内部即消息队列的发送命令。FreeRTOS源码分析-11 软件定时器_第8张图片

内部即消息队列的发送命令。

Reset和Start几乎没有区别

FreeRTOS源码分析-11 软件定时器_第9张图片

FreeRTOS源码分析-11 软件定时器_第10张图片重点,修改完后会启动定时器,不需要Start或者Reset

2.3 功能实现

设计

FreeRTOS源码分析-11 软件定时器_第11张图片

 FreeRTOS源码分析-11 软件定时器_第12张图片

FreeRTOS源码分析-11 软件定时器_第13张图片

实现

FreeRTOS源码分析-11 软件定时器_第14张图片

FreeRTOS源码分析-11 软件定时器_第15张图片 现象

FreeRTOS源码分析-11 软件定时器_第16张图片

RTC相关CubeMX配置:使能外部低速时钟

FreeRTOS源码分析-11 软件定时器_第17张图片

FreeRTOS源码分析-11 软件定时器_第18张图片

 FreeRTOS源码分析-11 软件定时器_第19张图片

命令解析、led控制创建两个任务和消息队列

Cmd针对RealTime和AlarmTIme

Led针对4个led灯解析

FreeRTOS源码分析-11 软件定时器_第20张图片

 软件定时器相关配置,周期性功能

FreeRTOS源码分析-11 软件定时器_第21张图片

FreeRTOS源码分析-11 软件定时器_第22张图片

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

}

解析实时时钟和闹钟

FreeRTOS源码分析-11 软件定时器_第23张图片

//计算闹钟与实时时钟之前的间隔时间,返回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

问:如果不自动重载,直接更新软件定时器周期行不行?

不行,因为更新软件定时器周期,相当于重启定时器,本来只执行1次的,会重复执行。

LED接收任务

void vLedParseString(uint8_t *buff){
  
    
    uint8_t i;
    for(i=0;i

3 软件定时器原理源码分析

3.1 软件定时器控制块

FreeRTOS源码分析-11 软件定时器_第24张图片

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 软件定时器任务&软件定时器创建

FreeRTOS源码分析-11 软件定时器_第25张图片

 问:为什么创建两个列表

在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();
}

FreeRTOS源码分析-11 软件定时器_第26张图片

即初始化值

	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 软件定时器启动&停止

FreeRTOS源码分析-11 软件定时器_第27张图片

#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;
}

你可能感兴趣的:(FreeRTOS源码分析,单片机,嵌入式硬件,stm32)