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

2022-01-23  本文已影响0人  那个混子

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

前言

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

裸机程序执行

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

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


核心概念理解

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

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

之前我自己在学校做智能车的时候,大概差不多使用的就是定时器中断,不同的时间用不同的变量进行计时,也还得行,看着条理清晰一些,但是现在去看看别人写的,自己之前那种方法还是有点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
上一篇下一篇

猜你喜欢

热点阅读