单片机裸机步调——步调架构

[复制链接]
发表于 2026-2-2 06:06:12 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

步调架构等同于头脑体系

建一栋楼房,地基要先计划好,而不是立刻砌砖,地基和布局都公道,房子就住得惬意,也不会闹心。
写一段步调也一样,步调构架要想好,而不是一边写一边调解构架,想到这个功能就先写这个功能,多个功能放在一起后无法调和,又要改构架,服从非常低。
把构架用图形的情势计划好,再像添砖加瓦一样去编码,实现功能。
一、前背景序次法

公司都有负责接待来宾的前台,当有人来访或有电话来,前台就会关照其他负责人,正在有序工作的负责人必须立刻克制手中工作,接待或接听竣过后再回到原来的事变上继续。有人来访或有电话比做前台,正在有序工作比做背景。
前背景序次实行是入门开辟者常用的步调架构,逻辑简朴,复杂度低,代码量少,最直观了,从上往下实行,全部使命都在一个循环里实行,不思量每个函数实行所须要的时间。但遇到延时就要等候,因此会造成其他函数隔断实行时间的差别,只管可以大概通过定时器停止的方式,但条件是停止服务函数实行的时间必须短。
序次实行就是在你前面的实行完了,才到你实行,好比上体育课列队报数一样,必须按序次来,你前面相邻的同砚报完数,你才气报数。
另有就是有停止发生,就优先实行停止要实行的事变,好比体育课,点到谁的名字就优先得去跑两圈。
   未到场停止的轮询体系
  1. int main(void)
  2. {
  3.     uint8 keyValue;
  4.     InitSys();                  // 系统初始化
  5.     while (1)
  6.     {
  7.         TaskDisplayClock();//显示系统时钟
  8.         keyValue = TaskKeySan();//扫描键盘
  9.         switch (keyValue)
  10.        {
  11.             case x: TaskDispStatus(); break;
  12.             ...
  13.             default: break;
  14.         }
  15.     }
  16. }
复制代码
在while(1)死循环中从第一行代码往下实行到末了一行,然后返回到第一行,云云反复实行。
   在轮询体系根本上到场停止处理处罚
  1. int flag1 = 0,flag2= 0,flag3 = 0;
  2. int main(){
  3.   hardwareInit();//硬件初始化
  4.   while(1){
  5.     if(flag1){
  6.       doSomething1();//处理事件1
  7.       flag1=0;//清除标志位
  8.     }
  9.     if(flag2){
  10.       doSomething2();//处理事件2
  11.       flag2=0;//清除标志位
  12.     }
  13.     if(flag3){
  14.       doSomething3();//处理事件3
  15.       flag3=0;//清除标志位
  16.     }
  17.   }
  18. }
  19. //中断处理程序1
  20. void ISR1(void ){
  21.   flag1=1//置位标志位
  22. }
  23. //中断处理程序2
  24. void ISR2(void ){
  25.   flag2=1//置位标志位
  26. }
  27. //中断处理程序3
  28. void ISR3(void ){
  29.   flag3=1//置位标志位
  30. }
复制代码
外部变乱的相应(改变一些标记位,确保所用时间非常短)在停止内里完成,变乱的处理处罚照旧要回到轮询体系中完成。
   1、如果停止服务步调实行时间太长,大概导致停止嵌套非常深,轻易导致栈溢出,也轻易出现非常,好比死机。
2、停止只处理处罚告急变乱!告急变乱要用停止处理处罚!好比串口数据的吸取和发送,速率太快,放在循环里处理处罚太慢了。然而对于数据的处理处罚,就不消发急了,可以在停止里吸取完成后设置标记位,然后在主循环里根据标记位完成处理处罚,之后手动开启相应停止去发送。
  停止在这里称为前台,main()函数中的主循环称为背景。

二、时间片轮询法

   敢言:时间片轮询法是万能的裸机步调构架
  时间片法是一种多使命实行法,它通过为每个使命分配肯定的实行时间片,使得全部使命都可以大概按照肯定的时间隔断瓜代实行。使命实行的时间片是固定的,当一个使命的时间片用完后,体系切换到下一个使命实行。这种方式使得多个使命可以大概并行实行,进步了体系的资源使用率和服从。
时间片法实用于中等复杂度的嵌入式体系,可以满意对实时性要求较高的场景。它须要公道设置使命的优先级和时间片巨细,以确保紧张使命优先实行,而且每个使命都能在得当的时间内完成。
使用定时器设定差别的时间片,定时到了某个时间节点,就去实行对应时间片里的代码,代码实行时间不能凌驾期间片时间,时间片时间应该根据所实行使命的耗时来设定。好比,按键扫描,通常都须要软件防抖,序次法是延时10ms左右再去判断,但10ms极大浪费了CPU的资源,在这段时间内CPU完全可以处理处罚很多其他事变,时间片轮询法就能很好使用CPU资源。
天下万事万物都是基于时间这条线而存在而运作的,谁都摆脱不了与时间的关联。
时间片轮询方式也轻易明确,你设定的时间到了,你就实行,没到就继续等候,各个时间片之间没有优先级。
   时间片轮询法体系的实现思绪(只须要一个恣意的定时器)
1、在体系初始化里初始化一个定时器,假设定时停止为1ms(也可以改成10ms,根据实际需求,但须要留意,停止过于频仍服从就低,停止太长,实时性差)。
2、界说多个使命变量及标记位
3、界说使命函数
4、把使命调治添加到主循环中
5、把使命切换逻辑控制添加到停止服务中
  1、初始化定时器,以51单片机为例
  1. //定时器初始化,定时1ms中断一次
  2. void Timer1_Init(){
  3.         AUXR &= 0xBF;                //定时器时钟12T模式
  4.         TMOD &= 0x0F;                //设置定时器模式
  5.         TMOD |= 0x10;                //设置定时器模式
  6.         TL1 = 0x30;                //设置定时初值
  7.         TH1 = 0xF8;                //设置定时初值
  8.         TF1 = 0;                //清除TF1标志
  9.         TR1 = 1;                //定时器1开始计时
  10.         ET1 = 1;       //使能定时器中断
  11.         EA = 1;
  12. }
  13. //定时器中断服务函数
  14. void TM1_Isr() interrupt 3
  15. {       
  16.         TL1 = 0x30;                //设置定时初值
  17.         TH1 = 0xF8;                //设置定时初值
  18. }
复制代码
2、界说使命干系
  1. //任务结构
  2. typedef struct _TASK_COMPONENTS
  3. {
  4.         unsigned int Run; //程序运行标记: 0-不运行,1-运行
  5.         unsigned int Timer; //计时器
  6.         unsigned int ItvTime;  //任务运行时间间隔
  7.         void (*TaskHook)(void); //要运行的任务函数
  8. }TASK_COMPONENTS;//任务定义,TASK_COMPONENTS是_TASK_COMPONENTS的别名
  9. static TASK_COMPONENTS TaskComps[]=         //时间节拍为1ms,由定时器产生
  10. {
  11.         {0,10,10,TaskRfCheck},        //处理RF数据
  12.         {0,20,20,TaskUart2Check},  //每次只处理串口2的一个字节数据
  13.         {0,8,8,TaskRGBdata},//RGB数据处理         刷新频率不能低于30HZ
  14.         {0,30,30,TaskPWMout}, //刷新PWM数据
  15.         {0,1000,1000,TaskTimers}, //计时器
  16. };
  17. //任务清单
  18. typedef enum _TASK_LIST
  19. {
  20.         TASK_RF_CHECK,
  21.         TASK_UART2_CHECK,
  22.         TASK_RGB_DATA,
  23.         TASK_UART1_PRINTF,
  24.         TASK_TIMERS,
  25.         TASKS_MAX,         //总共可分配的定时任务数目
  26. }TASK_LIST;
复制代码
3、界说使命函数
  1. void TaskRfCheck() //识别遥控器按键
  2. {
  3. //在此处添加代码
  4. }
  5. void TaskUart2Check() //解析串口2数据
  6. {
  7. //在此处添加代码
  8. }
  9. void TaskRGBdata()        //处理RGBW数据
  10. {       
  11. //在此处添加代码
  12. }
  13. void TaskPWMout()          //刷新PWM数据
  14. {         
  15. //在此处添加代码
  16. }
  17. void TaskTimers()  //灯带独立计时器
  18. {
  19. //在此处添加代码
  20. }
复制代码
4、界说使命调治函数并添加到主循环中
  1. void TaskProcess()
  2. {
  3.    static unsigned char i=0;  //必须是static类型
  4.    for(i=0;i<TASKS_MAX;i++)        //逐个任务时间处理
  5.    {
  6.           if(TaskComps[i].Run)        //任务可以运行
  7.           {
  8.                    TaskComps[i].TaskHook();  //调用任务函数
  9.                  TaskComps[i].Run = 0; //标志清0
  10.           }
  11.    }                        
  12. }
  13. void SysInit_all()
  14. {
  15.         Timer1_Init(); //定时器1初始化,用于多个任务调度
  16.         WDT_CONTR=0x35;//看门狗设置,超时2秒则复位
  17. }
  18. //主函数
  19. int main()
  20. {
  21.         SysInit_all();//系统初始化
  22.         while(1)
  23.         {
  24.                  TaskProcess(); //任务处理
  25.                 WDT_CONTR |= 0x10;   //喂看门狗,防止系统复位                        
  26.         }
  27.         return 1;
  28. }
复制代码
5、把使命切换逻辑控制添加到停止服务中
  1. void TaskRemarks(void)
  2. {
  3.    static unsigned char i=0;   //必须是static类型
  4.    for(i=0;i<TASKS_MAX;i++)         //逐个任务处理
  5.    {
  6.            if(TaskComps[i].Timer) //时间不为0
  7.            {
  8.                   TaskComps[i].Timer--; //减去一个节拍
  9.                   if(TaskComps[i].Timer<=0)//时间减完了
  10.                   {
  11.                          TaskComps[i].Timer = TaskComps[i].ItvTime;         //恢复计时器值,重新下一次
  12.                          TaskComps[i].Run = 1; //任务可以运行
  13.                   }
  14.            }
  15.    }
  16. }
  17. //每1ms就执行一次TaskRemarks()
  18. void TM1_Isr() interrupt 3
  19. {       
  20.         TL1 = 0x30;                //设置定时初值
  21.         TH1 = 0xF8;                //设置定时初值
  22.         TaskRemarks();        //任务调度
  23. }
复制代码
时间片轮询法的流程图大抵为:

在步调中,只要把时间节点控制成有规律性的,有周期性的,那么就非常好添加代码或调试信息了。
好比在时间片为1秒的使命里,到场调试信息打印语句,那么在步调运行时每秒就会打印一次打印信息。
好比每秒打印一次字符串信息,task_1s_function函数每秒就被调用一次:
  1. void task_1s_function()
  2. {
  3.         printf("1 sec is up\r\n");
  4. }
复制代码
好比每5秒打印一次字符串信息:
  1. void task_1s_function()
  2. {
  3.         static unsigned int count = 0;
  4.         if(++count > 5)
  5.         {
  6.                 count = 0;
  7.                 printf("5 sec is up\r\n");
  8.         }
  9. }
复制代码
好比变乱A发生3秒后,实行一次变乱B:
  1. static unsigned int count_down = 0;
  2. void A_event_occured()
  3. {
  4.         printf("Event A occurs\r\n");
  5.         count_down = 3;
  6. }
  7. void B_event_occured()
  8. {
  9.         printf("Event B occurs\r\n");
  10. }
  11. void task_1s_function()
  12. {
  13.         if(count_down)
  14.         {       
  15.                 count_down--;
  16.                 if(count_down == 0)
  17.                 {
  18.                         B_event_occured();
  19.                 }
  20.         }
  21. }
复制代码
当A变乱被触发时,实行一次B变乱:
  1. bool A_occured_flag = false;
  2. A事件被触发,启用标志位 A_occured_flag
  3. A_occured_flag = true;
  4. 周期地(比如10ms计时一到,F_Timer_10MS被设为true)循环执行那边判断标志位,如果被启用了,就执行语句体,否则不执行。只要限制条件满足,相关处理逻辑就会执行一次。
  5. while(1)
  6. {
  7.         if(F_Timer_10MS)
  8.         {
  9.                 F_Timer_10MS = false;
  10.                 if(A_occured_flag )
  11.                 {
  12.                         if(限制条件)
  13.                         {       
  14.                                 相关处理逻辑
  15.                                 A_occured_flag = false;
  16.                         }
  17.                 }
  18.         }
  19. }
复制代码
当一个状态发生反转时,打印一次信息
  1. if(R_loadON != R_loadON_last){
  2.         R_loadON_last = R_loadON;
  3.         if(R_loadON) LOG_STR("R_loadON == 1\r\n");
  4.         else LOG_STR("R_loadON == 0\r\n");
  5. }
复制代码
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表