美文网首页
玩转单片机之裸机多任务操作技巧

玩转单片机之裸机多任务操作技巧

作者: 那个混子 | 来源:发表于2022-01-23 23:49 被阅读0次

    "磨棱角,褪优越,沉下心"
    "不止于心动,更付诸于行动,执行力!“

    前言

    今天想分享一个我们在进行裸机开发时,处理多任务的技巧。基于前人的分享,做一个简单记录,还是比较实用。虽然说现在流行许多嵌入式操作系统,但是实际上接触得更多的还是裸机。

    裸机程序执行

    这个图中我们可以看出,单片机的运行是在ALU的主导下进行的;而定时器指是一个定时装置,它在定时计数期间是无需ALU干预的,完全独立运行;串口的通讯单元对数据的接收与发送也是完全独立完成的,并不需要ALU干预。很显然这三个任务是并行处理,切互不干涉,只有在定时器或串口产生中断时才会到代码中临时运行一段程序,已向单片机的主体运行过程交付一下结果,以便进行汇总处理。

    一个任务的线程:假设一个任务的执行代码有50步,通常编程只会一次执行完毕,但是我们现在需要想想,因为我们会嫌这个任务总占用着ALU的时间而影响其他任务的执行效果,所以就可以对任务进行划分,把它分为5份,每份10步,这样我们每次执行其中的一个程序片–每次正在运行的程序片我们称为线程。


    核心概念理解

    这里引入的思想就是细分思想,就是把需要执行的时间任务进行细分,也就是划分为很小的时间片,根据不同的时间片去执行。这里说到的并行执行其实也只是从整体表现觉得是并行的,本质上并不是并行。
    在我工作中,我们也并没有上什么操作系统,也都是跑裸机,基本上也是使用这种时间片的思想,把一个2ms的时间事件细分为4个0.5ms的时间片。

    我们可能接触多一点应该大概有两种

    • 在主函数while(1)中添加许多不同的任务进行顺序执行。
    • 配置外部中断、定时中断,结合死循环使用。

    之前我自己在学校做智能车的时候,大概差不多使用的就是定时器中断,不同的时间用不同的变量进行计时,也还得行,看着条理清晰一些,但是现在去看看别人写的,自己之前那种方法还是有点low。
    附上之前我做车的任务处理:

    void TIM2_IRQHandler (void)
    {
        uint32 state = TIM2->SR;                                                        // 读取中断状态
        TIM2->SR &= ~state;                                                     // 清空中断状态
    ///----------------------------
            static uint8 t_2ms = 0;
            static uint8 t_6ms = 0;
            static uint8 t_10ms = 0;
            static uint8 t_100ms = 0;
    /*************************中断执行程序********2ms进一次中断***************/
            t_2ms++;
          t_6ms++;
          t_10ms++;           
          t_100ms++;     
    //2ms直立控制周期     
            if(t_2ms == 1)       
          {
            t_2ms = 0;
            Flag.T_2ms=1;
          }        
    //6ms角度外环控制周期
          if(t_6ms == 3)      
          {
            t_6ms = 0;
            Flag.T_6ms=1;
          }        
    //10ms转向环      
                if (t_10ms == 5)     //转向外环10ms
                {
                    t_10ms = 0;
                    Flag.T_10ms=1;
                }
    
    //100ms速度控制周期      
                if (t_100ms == 50)   //速度 100ms  
                {
                    t_100ms = 0;
                    Flag.T_100ms=1;
                        
                }
    //**********************************************************
         Fuse_result();     
    }
    

    下面参考别人的整理了另外一种多任务处理的方法

    void TaskDisplayClock(void);
    void TaskKeySan(void);
    void TaskDispStatus(void);
    
    // 任务结构
    typedef unsigned char uint8 ;
    
    typedef struct 
    {
        uint8 Run;                 // 程序运行标记:0-不运行,1运行
        uint8 Timer;              // 计时器,用于运行起来变化的量
        uint8 ItvTime;              // 任务运行间隔时间
        void (*TaskHook)(void);    // 要运行的任务函数
    } TASK_COMPONENTS;       // 别名
    
    static TASK_COMPONENTS TaskComps[] = 
    {
        {0, 60, 60, TaskDisplayClock},         // 显示时钟
        {0, 20, 20, TaskKeySan},               // 按键扫描
        {0, 30, 30, TaskDispStatus},            // 显示工作状态
    
    };
    
    // 任务清单
    typedef enum _TASK_LIST
    {
        TAST_DISP_CLOCK,            // 显示时钟
        TAST_KEY_SAN,             // 按键扫描
        TASK_DISP_WS,             // 工作状态显示
      //...........
         TASKS_MAX         // 总的可供分配的定时任务数目
    } TASK_LIST;
    
    /**************************************************************************************
    * FunctionName   : TaskRemarks()
    * Description    : 任务标志处理
    * EntryParameter : None
    * ReturnValue    : None
    * attention      : ***在定时器中断中调用此函数即可***
    **************************************************************************************/
    void TaskRemarks(void)
    {
        uint8 i;
    
        for (i=0; i<TASKS_MAX; i++)          // 逐个任务时间处理
        {
             if (TaskComps[i].Timer)          // 时间不为0
            {
               TaskComps[i].Timer--;         // 减去一个节拍
               if (TaskComps[i].Timer == 0)       // 时间减完了
               {
                 TaskComps[i].Timer = TaskComps[i].ItvTime; // 恢复计时器值,从新下一次
                 TaskComps[i].Run = 1;           // 任务可以运行
               }
            }
       }
    }
    
    /**************************************************************************************
    * FunctionName   : TaskProcess()
    * Description    : 任务处理|判断什么时候该执行那一个任务
    * EntryParameter : None
    * ReturnValue    : None
    * * attention      : ***放在mian的while(1)即可***
    **************************************************************************************/
    void TaskProcess(void)
    {
        uint8 i;
    
        for (i=0; i<TASKS_MAX; i++)           // 逐个任务时间处理
        {
             if (TaskComps[i].Run)           // 时间不为0
            {
                 TaskComps[i].TaskHook();         // 运行任务
                 TaskComps[i].Run = 0;          // 标志清0
            }
        }   
    }
    
    /**************************************************************************************
    * FunctionName   : main()
    * Description    : 主函数
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    int main(void) 
    { 
       // InitSys();                  // 初始化-打开定时器
    
        while (1)
        {
            TaskProcess();             // 任务处理
        }
    }
    
    
    /**************************************************************************************
    * FunctionName   : TaskDisplayClock()
    * Description    : 显示任务
    
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    void TaskDisplayClock(void)
    {
    
    }
    
    /**************************************************************************************
    * FunctionName   : TaskKeySan()
    * Description    : 扫描任务
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    void TaskKeySan(void)
    {
    
    }
    
    /**************************************************************************************
    * FunctionName   : TaskDispStatus()
    * Description    : 工作状态显示
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    void TaskDispStatus(void)
    {
    
    }
    
    

    实验验证

    说明:在keil中对51单片机进行移植上述代码,测试,P10和P20两个IO以50ms,100ms的频率翻转,验证一下代码的可行性,其中配置了单片机1ms的定时中断作为最小时间片。
    代码移植如下:

    验证结果如下:

    参考资料:
    网络博文:https://blog.csdn.net/qq_37272520/article/details/88916568
    知乎等其他文章内容

    在此感谢网络其他佬的分享,上述部分内容参考网络,用于学习记录传播,如有不妥请联系修改

    小结

    本次主要分享一个简单的逻辑多任务处理的代码demo,这种写更规范,使用起来就更方便一些。当然了,主要还是理解思路方法,代码实现的方式不止上面这种。就先到这里了!

    欢迎关注本人微信公众号:那个混子
    记录自己学习的过程,分享乐趣、技术、想法、感悟、情感!
    单片机类嵌入式交流学习可加企鹅群:120653336

    相关文章

      网友评论

          本文标题:玩转单片机之裸机多任务操作技巧

          本文链接:https://www.haomeiwen.com/subject/fggmhrtx.html