打印
[应用相关]

基于FreeRTOS的定时器订阅服务机制设计

[复制链接]
1333|11
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tfqi|  楼主 | 2021-7-7 12:45 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一.概述
  FreeRTOS操作系统给我们提供了一种多任务并发执行的运行模式,与传统裸机开发的最大不同就是多任务之间的并发执行,大大提高了cpu的利用率。并且在多任务之间设计了消息队列这种通讯方式,使得各任务之间的通讯变得可靠便捷。因此基于这种多任务的设计思路,我们借用单片机的一个定时器lptim来实现一个定时器订阅服务任务,总的来说,这个定时器任务相当于整个操作系统的“守护进程”,给其他所有的任务提供时间订阅的服务,常驻与操作系统之中。在需要提供定时服务的时候该任务工作,在不需要提供任何定时服务的时候该任务阻塞。
  定时器订阅机制的设计思路:由于一个定时器订阅服务任务可以同时对外部多个任务提供服务,而且服务的任务数量不固定且会动态变化,因此考虑使用链表这一数据结构来管理订阅任务节点,也就是对于每个订阅的任务使用一个节点来进行维护,并且在每个节点中包含该任务订阅的基本信息,如任务的编号、订阅时长(周期)、订阅次数(u32,若为0xFFFFFFFF表示该任务无限期订阅)。
  对于计时递减机制的实现思路如下:使用一个定时器lptim提供基本的时钟基准(时基),且这个定时器提供时基的最小分辨率是100ms,按照外部订阅的次数算出最小时钟中断周期,并在每次中断发生时遍历各个任务订阅节点做时间扣减处理,扣减为零的节点向外发送一次timeout消息。另外在有外部订阅任务加入时需动态调整lptim中断时基,并且对之前的各个任务做时间补偿(因为动态调整时基之后会重新开始计时产生中断,那么上次中断和当前的这段时间就被丢掉了,因此需要对之前的各个节点做时间补偿)。
  代码设计思路:由于将各个订阅节点按链表的形式组织,因此涉及的主要是对链表的操作,因此考虑采用面向对象的思想对属性和操作进行封装:属性主要是链表头节点,行为包括链表节点的插入、删除、遍历处理等。通过一个对象管理一个订阅链表,操作逻辑简单明了。


使用特权

评论回复
沙发
tfqi|  楼主 | 2021-7-7 12:46 | 只看该作者
二.代码设计
1.链表操作类设计
//周期定时器订阅链表节点
typedef struct node_timer
{
  Thread_uuid uuid;        //进程号,用来表示订阅任务的身份
  u32 period;              //订阅周期,动态改变(S)
  u32 period_backup;       //订阅周期备份,固定不变,在period减为0时重新装载period值(S)
  u32 timers;              //订阅次数
  struct node_timer *next; //指向下一个节点
}Subscrible_Lsit_Timer;

//定义周期定时器订阅操作时间结构体,也就是设计一个链表操作类
struct Subscrible_Lsit_Timer_Event
{   
        Subscrible_Lsit_Timer Head_Node;                        //订阅周期定时器链表头节点
        u8 (*Add_Node)(Subscrible_Lsit_Timer *,Thread_uuid,u32,u32);    //添加链表节点到订阅链表
        u8 (*Delete_Node)(Subscrible_Lsit_Timer *,Thread_uuid); //从订阅链表中删除uuid节点
        u32 (*Process_Lsit)(Subscrible_Lsit_Timer *,u32,void (*process_node)(Thread_uuid)); //处理函数
        u32 (*Time_Compensate)(Subscrible_Lsit_Timer *,u32,u32);           //进行时钟补偿
};  


使用特权

评论回复
板凳
tfqi|  楼主 | 2021-7-7 12:46 | 只看该作者
2.各实现函数

也就是类成员函数的具体实现:

/**
* @brief   在定时器订阅列表中添加节点
*
* @param   node_head:订阅列表头节点地址
*          my_uuid:要添加的进程号
*          my_period:订阅周期(S)
*          my_timers:订阅次数(0xFFFFFFFF为无限次订阅)
*
* @return  HAL_OK:添加成功   
*          HAL_ERROR:添加失败(如果列表中已经有了则失败)
*/
static u8 Add_Node_To_Subscrible_Lsit_Timer(Subscrible_Lsit_Timer *node_head,Thread_uuid my_uuid,u32 my_period,u32 my_timers)
{
    Subscrible_Lsit_Timer *node_temp=node_head->next;   //定义临时节点,为头节点地址
    Subscrible_Lsit_Timer *node_backup=node_head;       //定义备份节点,为头节点地址
    while(node_temp)            //指向不为NULL
    {
      if(node_temp->uuid==my_uuid)return HAL_ERROR;   //如果链表里面已经存在该进程uuid,返回报错
      node_backup=node_temp;
      node_temp=node_temp->next;          //指向下一个节点
    }
    node_backup->next=(Subscrible_Lsit_Timer *)pvPortMalloc(sizeof(Subscrible_Lsit_Timer));    //申请一个新节点插入到链表中
    node_backup->next->uuid=my_uuid;     //插入的节点uuid赋值为my_uuid
    node_backup->next->period=my_period; //插入的节点period赋值为my_period
    node_backup->next->period_backup=my_period; //插入的节点period_backup赋值为my_period
    node_backup->next->timers=my_timers;        //订阅次数
    node_backup->next->next=NULL;        //插入的节点next指向NULL
    return HAL_OK;
}


使用特权

评论回复
地板
tfqi|  楼主 | 2021-7-7 12:47 | 只看该作者
/**
* @brief   在定时器订阅列表中删除节点(按uuid删除)
*
* @param   node_head:订阅列表头节点地址
*          my_uuid:要删除的进程号
*
* @return  HAL_OK:删除成功   
*          HAL_ERROR:删除失败(如果列表中没有该uuid则失败)
*/
static u8 Delete_Node_From_Subscrible_Lsit_Timer(Subscrible_Lsit_Timer *node_head,Thread_uuid my_uuid)
{
    Subscrible_Lsit_Timer *node_temp=node_head;   //定义临时节点,为头节点地址
    Subscrible_Lsit_Timer *node_backup=NULL;   //定义备份节点,为头节点地址
    while(node_temp)            //指向不为NULL
    {
      if(node_temp->next->uuid==my_uuid)
      {
        node_backup=node_temp->next;    //备份当前要删除的节点
        node_temp->next=node_temp->next->next;   //删除该节点
        vPortFree(node_backup); //释放该节点内存
        node_backup=NULL;   //释放该节点内存之后将其指向NULL
        return HAL_OK;   //删除成功,返回HAL_OK
      }
      node_temp=node_temp->next;          //指向下一个节点
    }
    return HAL_ERROR;       //没有找到,返回HAL_ERROR
}


使用特权

评论回复
5
tfqi|  楼主 | 2021-7-7 12:48 | 只看该作者
/**
* @brief   在定时器订阅列表中删除节点(按订阅次数timers为0删除,给Process_Subscrible_Lsit_Timer函数调用)
*
* @param   node_head:订阅列表头节点地址
*
* @return  void(没有订阅次数timers为0就不删除)
*/
static void Delete_Node_From_Subscrible_Lsit_Timer_Over(Subscrible_Lsit_Timer *node_head)
{
    Subscrible_Lsit_Timer *node_temp=node_head;   //定义临时节点,为头节点地址
    Subscrible_Lsit_Timer *node_backup=NULL;   //定义备份节点,为头节点地址
    while(node_temp)            //指向不为NULL
    {
      if(node_temp->next->timers == 0)
      {
        node_backup=node_temp->next;    //备份当前要删除的节点
        node_temp->next=node_temp->next->next;   //删除该节点
        vPortFree(node_backup); //释放该节点内存
        node_backup=NULL;   //释放该节点内存之后将其指向NULL
      }
      if(node_temp->next->timers!=0 || node_temp->next == NULL)
      {
        node_temp = node_temp->next;          //指向下一个节点
      }
    }
}


使用特权

评论回复
6
tfqi|  楼主 | 2021-7-7 12:49 | 只看该作者
/**
* @brief   定时器中断处理函数(每次定时器中断到来之后做递减处理,为0的回消息,次数到的删除节点)
*
* @param   node_head:订阅列表头节点地址
*          time_base:当前时基
*          process_node:进程定时时间到的回调函数
*
* @return  更新后的时基(可能会不变)
*/
static u32 Process_Subscrible_Lsit_Timer(Subscrible_Lsit_Timer *node_head,u32 time_base,void (*process_node)(Thread_uuid))
{
  Subscrible_Lsit_Timer *node_temp1=node_head->next;    //定义临时节点node_temp1,为递减操作
  Subscrible_Lsit_Timer *node_temp2=NULL;              //定义临时节点node_temp2,为更新时基
  //1.计时到,计数递减
  while(node_temp1)
  {
     node_temp1->period-=time_base;   //订阅时间递减
     //printf("uuid=%d   time_last=%d\r\n",node_temp1->uuid,node_temp1->period);
     if(node_temp1->period == 0)     //当前节点订阅时间到,需要操作
     {
          if(process_node != NULL)process_node(node_temp1->uuid);
          node_temp1->period=node_temp1->period_backup;  //重新装载period值
          if(node_temp1->timers != 0xFFFFFFFF)node_temp1->timers--;  //订阅次数减一
     }
     node_temp1=node_temp1->next;    //指向下一个节点
  }
  //2.遍历确定是否有计数次数到的节点,删除该节点
  Delete_Node_From_Subscrible_Lsit_Timer_Over(node_head);
  if(node_head->next == NULL)return 0;    //没有节点了,返回0
  node_temp2=node_head->next;            //注意一定要在这对node_temp2初始化  
  //printf_list(node_head);
  //3.假如还有节点定时时间未到,那么计算新的时基
  time_base=node_temp2->period < 2400 ? node_temp2->period:2400;
  while(node_temp2)
  {
    if(node_temp2->period < time_base)
    {
      time_base=node_temp2->period;   //如果有比基本时钟time_base更小的剩余时间,那么更新时基
    }
    node_temp2=node_temp2->next;    //指向下一个节点
  }
  return time_base;
}


使用特权

评论回复
7
tfqi|  楼主 | 2021-7-7 12:49 | 只看该作者
/**
* @brief   在新任务订阅时钟的时候重新计算系统时基并补偿之前任务的时基
*
* @param   node_head:订阅列表头节点地址
*          time_base:当前时基
*          time_compensate:需要补偿的时间
*
* @return  更新后的时基(可能会不变)
*/
static u32 Timer_Compensate(Subscrible_Lsit_Timer *node_head,u32 time_base,u32 time_compensate)
{
  Subscrible_Lsit_Timer *node_temp1=node_head->next;    //定义临时节点node_temp1,为递减操作
  Subscrible_Lsit_Timer *node_temp2=NULL;     //定义临时节点node_temp1,为更新时基
  while(node_temp1 && node_temp1->next)    //注意要排除最后一个节点
  {
     node_temp1->period-=time_compensate;   //除最后一个节点外各订阅任务时间补偿
     node_temp1=node_temp1->next;           //指向下一个节点
  }
  node_temp2=node_head->next;
  while(node_temp2)         //更新时基
  {
    if(node_temp2->period < time_base)
    {
      time_base=node_temp2->period;   //如果有比基本时钟time_base更小的剩余时间,那么更新时基
    }
    node_temp2=node_temp2->next;    //指向下一个节点
  }
  return time_base;
}


使用特权

评论回复
8
tfqi|  楼主 | 2021-7-7 12:50 | 只看该作者
3.实例化一个链表操作对象
//timer订阅结构体变量
static struct Subscrible_Lsit_Timer_Event Subscrible_timer = {
          .Head_Node={                              //订阅链表头节点初始化
        .uuid = uuid_default,                        //uuid为default
               .period=0,                         //订阅周期为0
               .period_backup=0,                  //订阅周期备份为0
               .timers=0,                         //订阅次数为0
               .next = NULL                       //头节点指向NULL
             },
        .Add_Node=Add_Node_To_Subscrible_Lsit_Timer,           //添加节点函数实例化
        .Delete_Node=Delete_Node_From_Subscrible_Lsit_Timer,   //删除节点函数实例化
        .Process_Lsit=Process_Subscrible_Lsit_Timer,           //链表处理函数实例化
        .Time_Compensate=Timer_Compensate                      //时间补偿函数实例化
};


使用特权

评论回复
9
tfqi|  楼主 | 2021-7-7 12:51 | 只看该作者
4.订阅服务主任务
  基于FreeRTOS系统设计定时器订阅主任务,具体设计流程如下:

/* 定义订阅任务Time_Subscribe_Task_Handle */
osThreadId_t Time_Subscribe_Task_Handle;                        //任务9的句柄为TimerprocessHandle
const osThreadAttr_t Time_Subscribe_Task_attributes = {
  .name = "SubscribeTask",                               //定义任务8的名称为"timerprocess"
  .priority = (osPriority_t) osPriorityNormal1,            //定义任务8的优先级为osPriorityLow6
  .stack_size = 128 * 4                                 //定义任务8的栈大小是128 * 4
};

/* 定义订阅任务的消息队列 Subscribe_Queue_Handle */
osMessageQueueId_t Subscribe_Queue_Handle;
const osMessageQueueAttr_t Subscribe_Queue_attributes = {
  .name = "SubscribeQueue"
};

/**
  * @brief  FreeRTOS initialization,在freertos的初始化工作中创建订阅任务及其接收队列
  * @param  None
  * @retval None
  */
void MX_FREERTOS_Init(void)
{
  /* 创建定时器订阅任务消息队列 Subscribe_Queue_Handle */
  Subscribe_Queue_Handle = osMessageQueueNew (10, 4, &Subscribe_Queue_attributes);
  /* 创建定时器订阅任务subscribe_task */
  Time_Subscribe_Task_Handle = osThreadNew(subscribe_task, NULL, &Time_Subscribe_Task_attributes);
}

/**
* @brief 定时器订阅处理任务
* @param argument: Not used
* @retval None
*/
void subscribe_task(void *argument)
{
  BaseType_t err=pdFALSE;

  period_subscribe *my_period_subscribe;

  u8 ret=0;                  //返回
  static u8 sub_flag=0;      //定义订阅标志,1表示订阅,2表示取消订阅
  u32 read_count=0;          //当前读取的定时器的值
  u32 address_temp=0;        //接收传递过来的地址

  static message_type message_id=default_messageid;            //解析出来uuid号,用来判断不同的消息源
  static Thread_uuid uuid=uuid_default;                  //解析出来uuid号,用来判断不同的消息源
  static u32 period=0;               //解析出来周期值,用来判断订阅任务的订阅周期
  static u32 timers=0;               //解析出来订阅次数
  static u32 time_base=0;             //定义中断时基
  u32 time_base_temp=0;               //定义中断时基

  for(;;)
  {
    if(Subscribe_Queue_Handle!=NULL)
    {
      err=xQueueReceive(Subscribe_Queue_Handle,(u8*)&address_temp,portMAX_DELAY);//请求消息Message_Queue1,为无限期等待
      if(err==pdTRUE)                    //接收到消息
      {
         my_period_subscribe=(period_subscribe *)address_temp;  //地址转换为订阅结构体类型
         message_id=my_period_subscribe->message_id;             //获取message_id
         switch(message_id)
         {
            /*****************************lprtim2定时器中断处理**************************************/
            case lprtim2_interupter_sign:         //lprtim2定时器中断
                 portENTER_CRITICAL();            /*进入临界区*/
                 time_base_temp=Subscrible_timer.Process_Lsit(&Subscrible_timer.Head_Node,time_base,process_node_time);
                 if(time_base_temp == 0)
                 {
                      //printf("订阅链表为空了,停止计时器\r\n");
                      HAL_LPTIM_PWM_Stop_IT(&hlptim2);                 //订阅链表为空了,停止计时器
                 }
                 else if(time_base_temp!=time_base)
                 {
                     time_base = time_base_temp;
                     //printf("时基需要调整,调整为:%d\r\n",time_base);
                     HAL_LPTIM_PWM_Stop_IT(&hlptim2);                 //首先停止定时器中断,相当关于复位一次,至关重要!!!
                     HAL_LPTIM_PWM_Start_IT(&hlptim2,(u32)(25.6*time_base),0);                //重新设置定时器           
                 }
                 portEXIT_CRITICAL();/*退出临界区*/
              break;  
            /*****************************订阅处理部分*************************************/  
            case key1_lptim2_send_subscribe:       //按键任务订阅定时器
              sub_flag=1;      //订阅标志
              break;
            case key2_lptim2_send_subscribe:       //按键任务订阅定时器
              sub_flag=1;      //订阅标志
              break;              
            /*****************************取消订阅处理部分*************************************/   
            case key1_lptim2_send_dissubscribe:          //取消电源管理订阅定时器
              sub_flag=2;      //订阅标志
              break;   
            case key2_lptim2_send_dissubscribe:          //取消电源管理订阅定时器
              sub_flag=2;      //订阅标志
              break;         
            default:break;
         }
         if(sub_flag == 1)      //订阅处理      
         {
              uuid=my_period_subscribe->uuid;    //获取进程号
              period=my_period_subscribe->period;//获取订阅周期
              timers=my_period_subscribe->timers;//获取订阅次数
             //printf("uuid=%d  period=%d  timers=%d 订阅\r\n",uuid,period,timers);
              if(Subscrible_timer.Add_Node(&Subscrible_timer.Head_Node,uuid,period,timers)==HAL_OK)//添加该节点到订阅链表上,添加成功才执行
              {
                  if(Subscrible_timer.Head_Node.next->next==NULL)    //如果链表为空,则为首个订阅定时器任务到来
                  {
                      //printf("首个任务 %d 订阅时钟,订阅时间为%d,订阅次数为%d S\r\n",uuid,period,timers);
                      time_base=period<2400 ? period:2400;        //更新时基为首个订阅周期(其中最大定时周期=240S=4min)
                      //printf("------当前时基为:%d--------\r\n",time_base);
                      MX_LPTIM2_Init(LPTIM_PRESCALER_DIV128,(u32)(25.6*time_base),0);       //设置lptim2的中断周期为time_base S
                  }              
                  else {                                              //如果链表不为空,那么要进行时基调整
                          read_count=HAL_LPTIM_ReadCounter(&hlptim2); //获取当前计数器的值
                          //printf("当前计数器的值为:%d\r\n",read_count);
                          time_base_temp=Subscrible_timer.Time_Compensate(&Subscrible_timer.Head_Node,time_base,(u32)(read_count/25.6));  //进行时钟补偿
                          if(time_base_temp!=time_base)
                          {
                               time_base = time_base_temp;
                               //printf("121212时基需要调整,调整为:%d\r\n",time_base);
                          }
                          //printf("------当前时基为:%d--------\r\n",time_base);
                          HAL_LPTIM_PWM_Stop_IT(&hlptim2);                 //首先停止定时器中断,相当关于复位一次,至关重要!!!
                          HAL_LPTIM_PWM_Start_IT(&hlptim2,(u32)(25.6*time_base),0);                //重新设置定时器
                          //printf("------定时器设置完成--------\r\n");
                  }
              }
              else{                             //该进程已经订阅过时钟,订阅失败
                          //printf("该进程已经订阅过时钟,订阅失败!!! uuid=%d  period=%d \r\n",uuid,period);
              }
              sub_flag = 0;   //处理标志清零
         }
         else if(sub_flag == 2)  //取消订阅处理  
         {
              uuid=my_period_subscribe->uuid;    //获取进程号
              ret = Subscrible_timer.Delete_Node(&Subscrible_timer.Head_Node,uuid);   //从订阅列表中删除节点
              if(ret == HAL_OK)//链表中有该节点,删除成功
              {
                  //printf("删除该节点成功\r\n");
                  if(Subscrible_timer.Head_Node.next == NULL)   //链表为空了,关闭定时器
                  {
                      //printf("链表为空了,关闭定时器\r\n");
                      HAL_LPTIM_PWM_Stop_IT(&hlptim2);  //关闭定时器
                  }
              }
              else{             //链表中没有该节点,删除失败
                      //printf("该节点未订阅,删除该节点失败\r\n");
              }
              sub_flag = 0;   //处理标志清零
          }
      }
      else if(err==pdFALSE)
      {
          osDelay(10);        //延时1ms,也就是10个时钟节拍
      }
    }   
  }
}


使用特权

评论回复
10
tfqi|  楼主 | 2021-7-7 12:51 | 只看该作者
代码分析
  借助FreeRTOS操作系统,将定时器订阅服务设计为一个任务,并且在系统调度开启之后常驻于系统之中,作为一个“守护进程”;在没有其他任务申请订阅服务的时候,该任务会阻塞在监听消息队列中,也就是:

err=xQueueReceive(Subscribe_Queue_Handle,(u8*)&address_temp,portMAX_DELAY);


  1.定时器中断和外部任务会向该队列中发送消息,当订阅任务消息队列中接收到消息时会退出阻塞状态继续执行后面的代码,首先解析接收到的消息类型,判断是任务订阅或取消订阅还是定时器中断,对于这三种消息会执行不同的处理,使用switch—case结构做不同的处理区分,首先一个任务发起订阅,会向本任务的消息队列中传入订阅内容,包括:任务身份标识uuid、订阅周期period、订阅次数timers等;本订阅服务任务首次接收到订阅消息后会将订阅者的身份标识uuid等信息创建一个节点并加入到自身维护的链表中,同时根据订阅周期开启定时器,订阅服务开始运作,实现部分如下:

if(Subscrible_timer.Head_Node.next->next==NULL)    //如果链表为空,则为首个订阅定时器任务到来
{
   //printf("首个任务 %d 订阅时钟,订阅时间为%d,订阅次数为%d S\r\n",uuid,period,timers);
   time_base=period<2400 ? period:2400;        //更新时基为首个订阅周期(其中最大定时周期=240S=4min)
   //printf("------当前时基为:%d--------\r\n",time_base);
   //定时器中断的最小分辨率是100ms
   MX_LPTIM2_Init(LPTIM_PRESCALER_DIV128,(u32)(25.6*time_base),0);       //设置lptim2的中断周期为time_base S
}


  2.当有第二个任务发送订阅消息时,执行同样的操作创建节点挂入链表,但是由于定时器已经开启且按照第一个任务的订阅周期设置中断,此时应该综合考虑两个任务的中断时间重新调整定时器中断时间,也就是OS调度的原理,计算出最近一次订阅时间到的时间点更改定时器周期,但是之前给其他任务已经计的时间需要补偿,因此需要做两步工作:首先是给其他任务做时间补偿,其次是更改定时器中断时基。具体代码实现部分如下:

//如果链表不为空,那么要进行时基调整
else {                                             
         read_count=HAL_LPTIM_ReadCounter(&hlptim2); //获取当前计数器的值
         //printf("当前计数器的值为:%d\r\n",read_count);
         //第一步:进行对其他任务的时间补偿
         time_base_temp=Subscrible_timer.Time_Compensate(&Subscrible_timer.Head_Node,time_base,(u32)(read_count/25.6));  //进行时钟补偿
         if(time_base_temp!=time_base)
         {
              time_base = time_base_temp;
              //printf("121212时基需要调整,调整为:%d\r\n",time_base);
         }
         //printf("------当前时基为:%d--------\r\n",time_base);
         //第二步:进行定时器时基调整
         HAL_LPTIM_PWM_Stop_IT(&hlptim2);                 //首先停止定时器中断,相当关于复位一次,至关重要!!!
         HAL_LPTIM_PWM_Start_IT(&hlptim2,(u32)(25.6*time_base),0);                //重新设置定时器
         //printf("------定时器设置完成--------\r\n");
}




  3.当订阅链表中有节点之后就代表订阅任务需要对外提供服务了,此时定时器开始计时,并在计时时间到之后产生中断并向本任务发送消息,此时本任务收到定时器中断消息之后需要做如下处理:首先对订阅链表做轮询,并将各个节点的订阅事件做扣减,扣减为0的节点表示该任务的订阅时间到了,需要给该节点的任务发送一个time out的消息;另外判断该节点任务的订阅次数,做减一处理,若减一之后为零表示该任务的订阅次数全部服务完成,则将该节点删除;否则使用该节点中的定时时长重装载值period_backup重新加载订阅周期值:period = period_backup,开启下一轮的计时扣减。在对订阅链表中的所有节点均已轮询处理结束后需要动态调整定时器订阅周期time_base,至此处理结束。该部分处理的代码如下:

//主要的处理工作在函数Process_Lsit()中完成,函数返回值是新的时基time_base,当链表为空返回0
time_base_temp=Subscrible_timer.Process_Lsit(&Subscrible_timer.Head_Node,time_base,process_node_time);
if(time_base_temp == 0)
{
     //printf("订阅链表为空了,停止计时器\r\n");
     HAL_LPTIM_PWM_Stop_IT(&hlptim2);                 //订阅链表为空了,停止计时器
}
else if(time_base_temp!=time_base)
{
    time_base = time_base_temp;
    //printf("时基需要调整,调整为:%d\r\n",time_base);
    HAL_LPTIM_PWM_Stop_IT(&hlptim2);                 //首先停止定时器中断,相当关于复位一次,至关重要!!!
    HAL_LPTIM_PWM_Start_IT(&hlptim2,(u32)(25.6*time_base),0);                //重新设置定时器           
}



  上面分析得知:在Process_Lsit函数中会遍历处理各个订阅节点,当判断某个节点的订阅时间到了之后会给该订阅节点任务发送一个消息,这一机制的执行是通过回调函数的形式实现的,实现部分如下:

//遍历节点
while(node_temp1)
{
    node_temp1->period-=time_base;   //订阅时间递减
    //printf("uuid=%d   time_last=%d\r\n",node_temp1->uuid,node_temp1->period);
    if(node_temp1->period == 0)     //当前节点订阅时间到,需要操作
    {
         if(process_node != NULL)process_node(node_temp1->uuid);        //调用的是process_node回調函数,出入的参数是该节点的uuid身份值
         node_temp1->period=node_temp1->period_backup;  //重新装载period值
         if(node_temp1->timers != 0xFFFFFFFF)node_temp1->timers--;  //订阅次数减一
    }
    node_temp1=node_temp1->next;    //指向下一个节点
}



  我们可以在外部自己实现一个函数来做处理,这个函数接收的参数就是服务时间到的任务身份标识uuid,之所以采用回調函数的方式是为了使得代码在底层驱动和上层业务的分层,底层的处理只对外提供一个接口,至于怎样处理都交给业务层去决定。这样可以很好的做到业务分层,这是一种代码设计思想。
  4.各任务还可以发送消息取消之前的订阅,这个处理的机制很简单,需要取消订阅的任务只需要把它的身份uuid发送给本任务就行了,本人无接收到取消订阅的消息之后会根据该uuid去遍历链表,若订阅链表中无该uuid则不做任何处理,若有则将该uuid对应的节点删除,不在为该任务提供订阅服务。具体的代码实现如下:

else if(sub_flag == 2)  //取消订阅处理  
{
     uuid=my_period_subscribe->uuid;    //获取进程号
     ret = Subscrible_timer.Delete_Node(&Subscrible_timer.Head_Node,uuid);   //从订阅列表中删除节点
     if(ret == HAL_OK)//链表中有该节点,删除成功
     {
         //printf("删除该节点成功\r\n");
         if(Subscrible_timer.Head_Node.next == NULL)   //链表为空了,关闭定时器
         {
             //printf("链表为空了,关闭定时器\r\n");
             HAL_LPTIM_PWM_Stop_IT(&hlptim2);  //关闭定时器
         }
     }
     else{             //链表中没有该节点,删除失败
             //printf("该节点未订阅,删除该节点失败\r\n");
     }
     sub_flag = 0;   //处理标志清零
}



  至此,整个订阅服务的流程分析完成,总体来看就是对链表的操作:创建,插入,删除等基本操作,链表中的各节点维护的是各订阅任务的订阅信息,并根据定时器中断去遍历整个链表并动态调整链表中的节点信息。


使用特权

评论回复
11
tfqi|  楼主 | 2021-7-7 12:52 | 只看该作者
三.总结
  本文详细介绍了基于FreeRTOS操作系统的定时器订阅服务机制的设计流程和细节,总体上是借助链表这一数据结构实现的,另外还需要考虑定时器计时时基的动态调整,这里参考了OS Tick的设计思路,动态调整为最近一个任务time out的时间。其中的实现细节较多,本文只是结合代码大概分析了设计流程,具体的实现部分还需要认真设计,包括对系统共享资源的临界保护、内存管理、进程调度等细节问题的处理。


使用特权

评论回复
12
木木guainv| | 2021-8-6 12:34 | 只看该作者
请问什么是订阅服务机制啊

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

57

主题

3317

帖子

4

粉丝