一。软件定时器概念及应用

1.软件定时器定义

        就是软件实现定时器。

2.FreeRTOS软件定时器介绍

        如上图所示,Times的左边为设置定时器时间,设置方式可以为任务设置或者中断设置;Times的右边为定时器的定时响应,使用CallBack响应。 

3.FreeRTOS软件定时器工作原理

 软件定时器可以参考一下手机的闹钟。单次响应与多次响应。

二。软件定时器函数应用

1.功能需求

- 使用软件定时器功能完成闹钟功能设计

- 当闹钟到达时,可根据执行动作,触发相关的led亮灭

2.API

(1)xTimerCreate()创建一个定时器

(2)xTimerStart()启动定时器

(3) xTimerReset()重启软件定时器

(4)pvTimerGetTimerID()获取软件定时器标识符值

(5)xTimerChangePeriod()修改软件定时器周期值

 3.功能设计

 如上图所示,需要三个部分,用户在串口端设置时钟参数,RTC作为定时器的核心,并驱动GPIO

(1)串口命令定义

1.设置实时时钟 参数头:年-月-日,小时:分钟:秒 realtime:2019-2-19,16:31:00

2.设置闹钟参数 参数头:小时:分钟:秒,是否重复,操作LED动作 alarmtime:16:32:40,0,0

(2)功能业务划分

1.实时时钟:RTC功能开发

2.命令参数配置:串口解析功能开发

3.软件定时功能:软件定时器

4.多任务消息同步:消息队列

4.功能实现分析

(1)Cubemx配置

1.配置RTC

2.配置串口

3.创建任务

4.创建消息队列

(2)实时时钟读写操作

1.设置实时时钟

2.读取实时时钟

(3)命令解析任务

1.使能串口接收中断

2.串口中断发送消息队列

3.解析命令字符串

4.解析实时时钟字符串

5.解析闹钟字符串

6.计算闹钟与实时时钟间隔

(3)软件定时器回调函数

1.定时器打印实时时钟

2.闹钟回调函数

(4)LED处理任务

LED处理任务

5.功能实现详细步骤

1.Cubemx创建工程

注意:是在物联网操作系统第5节消息队列的基础之上创建工程。

(1)使能RCC低速时钟(LSE)

注意:上述在核心板原理图上

具体解释:

        软件实现定时器,需要连接实时时钟,上述图片的晶振是操作实时时钟的晶振,这个晶振连接在低速时钟上,所以我们需要配置低速时钟。 

 (2)配置RTC

 (3)时钟配置为低速的外部时钟

2.FREERTOS配置

(1)软件定时器的配置

(2) 创建控制Led的消息队列

 (3)RTC时钟的创建

有一个定时打印的实时功能,所以创建一个RTC的时钟

 6.步骤:

1.RTC配置

(1)RTC.h的设置

typedef struct{
	RTC_TimeTypeDef RtcTime;
	RTC_DateTypeDef RtcDate;
}RTCTimeDates;

void SetRTC(RTCTimeDates *pRTCTimeDate);
RTCTimeDates GetRTC(void);

(2)RTC.c的配置

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

2.FREERTOS配置

(1)FREERTOS.c加入使用到的头文件,创建使用的结构体

#include "rtc.h"
#include "stdlib.h"
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;

(2)配置宏

#define LED_NUM  4

//串口接收buff
#define MESSAGE_BUFF_SIZE 50
//led接收buff
#define LED_MESSAGE_BUFF_SIZE 20
//实时时钟字符串头
#define REALTIME "realtime"
//闹钟字符串头
#define ALARMTIME "alarmtime"
//ms转换
#define ST0MS 1000ul
#define MT0MS (ST0MS*60)
#define HT0MS (MT0MS*60)
#define DT0MS (HT0MS*24)

(3)定义数据与字符串集,在命令分析时对比使用

uint8_t u8CmdBuff[MESSAGE_BUFF_SIZE];
static uint8_t u8LedMessageBuff[LED_MESSAGE_BUFF_SIZE];
//LED消息队列,字符串集
uint8_t *LedCmdString[LED_NUM*2] = {

"openled6",
"openled7",
"openled8",
"openled9",
"closeled6",
"closeled7",
"closeled8",
"closeled9",
};

uint8_t *OpenString[LED_NUM] = {

"openled6",
"openled7",
"openled8",
"openled9",

};

uint8_t *CloseString[LED_NUM] = {

"closeled6",
"closeled7",
"closeled8",
"closeled9",

};

GPIO_TypeDef * LedPort[LED_NUM] = {

Led6_GPIO_Port,
Led7_GPIO_Port,
Led8_GPIO_Port,
Led9_GPIO_Port

};

uint16_t LedPin[LED_NUM] ={
Led6_Pin,
Led7_Pin,
Led8_Pin,
Led9_Pin


};

//实时时钟字符串拆分解析结构体
struct sRealTimeString{
 uint8_t Hours[10];
 uint8_t Minutes[10]; 
 uint8_t Seconds[10]; 
 uint8_t Month[10]; 
 uint8_t Date[10];  
 uint8_t Year[10]; 

}RealTimeString;
//闹钟字符串拆分解析结构体
struct sAlarmTimeString{
 uint8_t Hours[10];
 uint8_t Minutes[10]; 
 uint8_t Seconds[10]; 
 uint8_t Mode[10]; 
 uint8_t Action[10];  

}AlarmTimeString;
//闹钟参数结构体
typedef struct{
 uint8_t Hours;
 uint8_t Minutes; 
 uint8_t Seconds; 
 uint8_t Mode; 
 uint8_t Action;  

} sAlarmTime;

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

(5)LedTask主函数的内容

    uint8_t u8Index;
	osTimerStart(RtcTimerHandle,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);		
		}		
  }

(6)在串口的任务上方,写函数触发的函数

//闹钟触发的软件定时器,回调函数
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);  
  }  
}

//计算闹钟与实时时钟之前的间隔时间,返回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);
}
//解析串口命令字符串
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);
    }    
}

(7)UsartTask任务中编写————》注意接受缓冲区要清除多少

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

结果:

 

更多推荐