STM8S003F3上一种基于时间片思想的多任务设计方法

2019-07-19 21:04发布

      刚开始接触STM8,就被其极高的性价比所吸引,刚好手头有一些STM8S003F3的板子,于是就以此为基础进行研究。
      之前使用STM32F103时已经挂载了ucosii,正如周立功先生所说的,一旦使用操作系统来编程,那么就很难再接受以前的前后台编程,
所以在还未确切明了STM8究竟有什么功能的时候,我首先想的是这玩意能使用哪种操作系统呢。ucosii肯定不行,对STM8这样的小身板
而言太大了,扛不住。在网上找啊找,找到了atomtheards,号称专为STM8而设计的。根据atom的官方资料,RAM要求低至1KB,S003刚
好达标,于是找了个基于IAR的例程一番修改,编译成功,也能仿真下载,就是无法正常工作,一直提示堆栈指针超出范围,这还是只有
一个LED显示任务的情况,在加了一个简单的串口任务以后,连编译都无法通过。大概分析了一下,还是RAM太小,跑不动,但是添加任
务就得开辟堆栈,开辟堆栈就需要占用RAM,就那么点资源,三两下就玩完了,所以像S003这种小RAM的片子基本跟OS拜拜了。
      但事还没完,OS用不了,那么有没有其他方法来实现呢?想起以前看过的时间片设计方法,就是给每一个任务分配一个时间片(就好
比任务是一辆辆的汽车,有大有小,时间片就是汽油,大车加多点,小车加少点),虽然实时性差点,但资源占用少。不过呢,一个完整
的时间片操作对S003而言还是有点复杂(确切的说是我自己感觉复杂),毕竟在不同的任务跳转时还是需要开辟堆栈进行现场保护的,巧
妇难为无米之炊,RAM太小,要把人逼疯的节奏。RAM太小,不要开辟堆栈不就行了,那跟前后台方式有什么区别?实际上,我这里实现
的方法就是一种前后台的过程,但实现的思想是类似于时间片的多任务方式。
      首先实现的是一个定时函数Delay_Ds(u8 ds_num, u32 t); 
     
      volatile u32 tick;             //系统心跳,每1ms加1,在定时中断中实现                         
      static   u32 fac_us = 0;  //us延时倍乘数

      static   u32 ds_read[DS_NUM_MAX];       //读取当前tick
      static   u32 ds_read_bit[DS_NUM_MAX]; //当前tick已读取标志

      static   u8  ds_sw[DS_NUM_MAX];          //定时开关

      /*****************************************************************************
     功能:定时
     入参:ds_num, 定时编号
               t, 定时时间,单位ms
     返回:REACHED, 定时时间到
               UNREACHED, 定时时间未到
     说明1:当定时函数用于不同的定时任务时,定时编号不能重复
     说明2:u32个ms的一个计数周期约49天,不考虑tick跨越多个计数周期的情况(也不用考虑
                 ,因为一个定时最多也就u32个计数)
     ******************************************************************************/
    Timing_Type   Delay_Ds(u8 ds_num, u32 t)
    {
       if(ds_sw[ds_num] == 1)  return UNREACHED;  //定时器关闭

       CLI();      //不允许中断打断
       if(ds_read_bit[ds_num]==0)              //定时开始,还未读取当前tick值
      {
        ds_read[ds_num]=tick;                     //读取当前tick
        ds_read_bit[ds_num]=1;      //标记为已读
      } 
      SEI();
    
      if(ds_read[ds_num]<tick)      //tick还在一个计数周期内
      {
        if(t<=tick-ds_read[ds_num])        //定时时间到
        {
          ds_read_bit[ds_num]=0;                  //重新计数
          return REACHED;      //返回定时到
        }
        else  return UNREACHED;      //否则返回定时未到
      }
      else if(ds_read[ds_num]>tick)      //tick已经在下一个计数周期内
      {
        if(t<=0xffffffff-ds_read[ds_num]+tick)  
        {
          ds_read_bit[ds_num]=0;                  //定时时间到,重新计数
          return REACHED; 
        }
        else  return UNREACHED;
      }
      else  return UNREACHED;      //
    }                        
    例如,LED以500ms的周期进行闪烁,那么就可以使用Delay_Ds()函数为LED闪烁任务设置一个500ms定时,500ms定时到,则执行一次
LED操作,然后让出CPU去执行其它任务。过程如下:
     while(1)
     {
         while(Delay_Ds(led_num, 500) == REACHED) {LED_Task();}
         while(Dealy_Ds(uart_num, 10) == REACHED) {UART_Task();}
         ……
     }
CPU在整个主循环中查询各个任务定时是否到,到则执行一次任务并重新计时,不到则继续查询。
      这样实现的好处显而易见,由于每个任务都是执行完成以后才会让出CPU,所以不会出现被打断的情况,所以不用开辟堆栈来进行现场
保护,在S003上运行起来绰绰有余,甚至更小RAM的单片机都可以运行;相比较传统的前后台编程方式,这种方法的逻辑更简单,不同的
任务可以看作是一个独立的处理过程;实现过程是硬件无关的,所以写好的程序可以在任意平台移植。当然,缺点也同样明显,一个任务
的执行时间不能太长,否则会影响其它任务的处理;未涉及任务的优先级,实时性差等等。
      现已通过这种方法在S003上实现了一个1HZ的LED闪烁任务,一个2HZ的LED闪烁任务和一个每10ms接收一次串口数据并发送回去的串
口任务,当串口助手以100ms发送一帧数据时 ,S003可以很流畅地运行,执行效果与使用OS的多任务处理一样。
      由于本人水平有限,难免有错误以及不足之处,欢迎大家批评指正。我在此也是抛砖引玉,如果各位有更好的方法,还请不吝赐教!
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。