• 嵌入式系统程序架构的进化


    实用的嵌入式系统我们一般采用分层的架构,可以分成3层(硬件层、驱动层和应用层)或4层(硬件层、驱动层、操作系统层和应用层),这在例解嵌入式系统分层结构已有分析,但是应用程序本身是非常复杂的,尤其是可能涉及到各种人机交互、机机交互的问题,使得系统更加复杂,所以对系统程序的架构做进一步的分析是很有必要的。

    下面的分析我们都假定系统在大的层次上分为3层,设备驱动已准备好,下面的代码为伪代码,看懂程序结构即可。

    1.无交互,少量任务

    例:流水灯

    1. /*exe-1*/
    2. #include"config.h"
    3. int main(void){
    4. unsigned char i=0;
    5. //
    6. leds_init();
    7. //
    8. while(1){
    9. for(i=0;i<8;i++){
    10. leds_on(i);
    11. delay(1000);
    12. leds_off();
    13. }
    14. }
    15. return 0;
    16. }

    没有交互的任务很少。

    2.有少量交互,少量任务

    例:按键控制数码管,数码管显示按下的按键名称1、2、3

    1)采用查询方式

    1. /*exe-2*/
    2. #include "config.h"
    3. int main(){
    4. unsigned char key=0;
    5. //初始化
    6. segment_init();
    7. key_init();
    8. //主循环
    9. while(1){
    10. key=getKey();
    11. switch(key){
    12. case 1:segment_off();segment_on(0,1);break;
    13. case 2:segment_off();segment_on(0,2);break;
    14. case 3:segment_off();segment_on(0,3);break;
    15. default::segment_off();segment_on(0,_OFF_);break;
    16. }
    17. delay(100);
    18. }

    2)采用中断方式

    按键触发外部中断,在中断服务程序中处理按键,并显示键值

    1. /*exe-3*/
    2. #include "config.h"
    3. void eint_isr(void){
    4. key=getKey();
    5. switch(key){
    6. case 1:segment_off();segment_on(0,1);break;
    7. case 2:segment_off();segment_on(0,2);break;
    8. case 3:segment_off();segment_on(0,3);break;
    9. default::segment_off();segment_on(0,_OFF_);break;
    10. }
    11. }
    12. int main(){
    13. unsigned char key=0;
    14. //初始化
    15. segment_init();
    16. key_init();
    17. VIC_init();
    18. //主循环
    19. while(1);
    20. }

    用上了中断后你就会爱上它,舍不得不用了。

    3.有多种交互,多种任务

    例:处理按键、显示、控制、测量电机转速

    按键三个,分别为加速,减速,停止,属于输入。显示属于输出,电机属于输出,电机测试属于输入。

    完成上述工作需要实现的功能很多,不用操作系统的情况下,我们一般采用中断驱动的前后台结构

    1)轮询结构--超级循环结构

    主函数的主循环中处理各种交互及业务,适合于简单任务

    1. /*exe-4*/
    2. #include "config.h"
    3. void handle_key(){
    4. ...
    5. }
    6. void handle_disp(){
    7. ...
    8. }
    9. void handle_motor(){
    10. ...
    11. }
    12. int main(){
    13. unsigned char key=0;
    14. //初始化
    15. segment_init();
    16. key_init();
    17. motor_init();
    18. //主循环
    19. while(1){
    20. handle_key();
    21. handle_disp();
    22. handle_motor();
    23. }
    24. }

    每个任务不能执行时间太长,否则会影响执行效果。

    2)中断驱动的前后台结构

    a中断驱动结构

    代码和上述exe-4相似,将按键、显示等都放在中断程序中,主循环空转

    中断驱动结构的主要问题是将业务逻辑也放在了中断服务程序中,但业务逻辑比较复杂时,中断响应可能不及时,并且结构复杂。

    b轮询与中断结合的结构

    • 中断作为前台,主要处理各种设备控制及交互等;
    • 业务逻辑放在主函数的主循环中,作为后台;
    • 中断服务程序中一般不进行业务处理(避免处理时间太长);
    • 前后台之间、任务之间通过变量标志进行协同。

     在各种任务中,有些任务是必然存在的:数据的的显示、按键的处理等,一般来说业务逻辑负责处理和产生数据。这时候我们可以采用所谓的MVC架构,M即模型(model),可以看作就是数据,V即视图(view),如何显示数据;而C即控制(control),也就是处理、产生数据;业务逻辑(C)处理数据、模型保存数据、视图显示数据,业务逻辑一般最复杂,且分布较为复杂。MVC架构对于有交互的系统来说这是一种非常有效的架构,通常我们定时器中断周期性的处理显示问题、按键扫描问题。下面的结构仔细体会:

    1. /*exe-5*/
    2. #include "config.h"
    3. //处理和显示的数据,相当于model
    4. int keyValue=0;
    5. int motor_cnt=0;//记录电机转速产生的脉冲数
    6. int motorSpeed=0;
    7. unsigned char pattern[]={_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,_OFF_};
    8. //同步变量
    9. int motorCntrl_flag=0;
    10. int speedDisp_flag=1;
    11. int keyPress_flag=0;
    12. void sysTick_isr(void){
    13. //按键处理,获得键值--getKey()
    14. //置处理按键标志
    15. if(keyValue!=0){
    16. keyPress_flag=1;
    17. }
    18. //显示速度值----MVC中的view
    19. segment_disp(pattern,5);
    20. //处理电机速度
    21. if(time==1S){
    22. motorSpeed=motor_cnt*60;
    23. motor_cnt=0;
    24. speedDisp_flag=1;
    25. }
    26. }
    27. //下面都是业务逻辑了
    28. void eint1_isr(void){//外部中断
    29. motor_cnt++;//测量发生中断的次数,通过一秒钟发生的中断次数计算电机转速
    30. }
    31. void handle_key(){
    32. switch(keyValue){
    33. case 1:motorCntrlFlag=1;break;
    34. case 2:motorCntrlFlag=2;break;
    35. case 3:motorCntrlFlag=3;break;
    36. case 4:motorCntrlFlag=4;break;
    37. }
    38. }
    39. void handle_motor(){
    40. switch(motorCntrlFlag){
    41. case 1://电机加速
    42. ...;break;
    43. case 2://电机减速
    44. ...;break;
    45. case 3://电机停止
    46. ...;break;
    47. case 4://电机启动
    48. ...;break;
    49. }
    50. }
    51. void handle_disp(){
    52. int speed=motorSpeed;
    53. for(i=7;i>=0;i--){
    54. pattern[i]=speed%10;
    55. speed/=10;
    56. }
    57. }
    58. int main(){
    59. //初始化
    60. segment_init();
    61. key_init();
    62. motor_init();
    63. //主循环
    64. while(1){//主循环中主要放业务逻辑或需要处理时间很长的任务
    65. if(keyPress==1){
    66. handle_key();
    67. keyPress=0;//保证只执行一次,其他类似,一般在中断其启动,在此执行处理
    68. }
    69. if(speedDisp_flag==1){
    70. handle_disp();
    71. speedDisp_flag=0;
    72. }
    73. if(motorCntrlFlag!=0){
    74. handle_motor();
    75. motorCntrlFlag=0;
    76. }
    77. }
    78. }

    MVC架构实现例1-4: 

    1. /*exe-1的改进*/
    2. #include"config.h"
    3. //显示的数据,相当于MVC中的model;
    4. unsigned char pattern[]={0x010x020x04,0x08,0x10,0x20,0x40,0x80};
    5. int main(void){
    6. unsigned char i=0;
    7. //
    8. leds_init();
    9. //
    10. while(1){
    11. for(i=0;i<8;i++){//显示任务,只复杂显示,与显示的内容无关,相当于view
    12. leds_on(pattern[i]);
    13. delay(1000);
    14. leds_off();
    15. }
    16. }
    17. return 0;
    18. }
    1. /*exe-2的改进*/
    2. #include "config.h"
    3. //数据,相当于model
    4. unsigned char pattern[]={_OFF_,};
    5. int main(){
    6. unsigned char key=0;
    7. //初始化
    8. segment_init();
    9. key_init();
    10. //主循环
    11. while(1){
    12. //按键处理
    13. key=getKey();
    14. switch(key){
    15. case 1:pattern[0]=1;break;
    16. case 2:pattern[0]=2;break;
    17. case 3:pattern[0]=3;break;
    18. default::pattern[0]=_OFF_;break;
    19. }
    20. //显示处理
    21. segment_off();//消影
    22. segment_on(0,pattern[0]);//在0位显示按键值
    23. dealy(100);
    24. }
    25. }
    1. /*exe-3的改进*/
    2. #include "config.h"
    3. unsigned char pattern[]={_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,};
    4. void sysTick_isr(void){
    5. //按键处理
    6. key=getKey();
    7. if(key)
    8. pattern[0]=key;
    9. //显示
    10. segment_disp(*pattern,10);
    11. }
    12. int main(){
    13. unsigned char key=0;
    14. //初始化
    15. segment_init();
    16. key_init();
    17. VIC_init();
    18. //主循环
    19. while(1);
    20. }

    从上述分析可以看出,对于嵌入式系统来说,定时器是非常重要的。但其重要性还不仅如此,我们常常会遇到系统要求在什么样的时间条件下做什么事情、又在什么样的时间条件下做什么事情,这时定时器中断服务程序可能要控制多个同步变量、或进行多种处理、不仅仅计时,为处理效率考虑,我们常常发现定时器不够用了!

    从这里我们也可以看出,嵌入式系统中有三个设备非常重要:GPIO、中断、定时器!一定要掌握。

    这里还有几个问题需要解决:

    执行的任务多,有一定的实时性要求----时间轮片法。考虑将任务函数进行拆分能通过程序优化缩短执行时间则最好优化,如果不能优化的,则必须保证该任务的执行周期必须远大于任务所执行的耗时时间,同时要求主循环或任务函数中不能存在毫秒级别的延时。再多的话,系统就太复杂了,就要考虑上操作系统了。

    随着系统复杂程度的增加,同步变量的数量越来越多,如何管理这些同步变量?----消息事件机制,统一管理

    同一个消息,同一个任务,在不同的状态下需要执行不同的操作,业务逻辑复杂如何解决?---有限状态机

    某轮询任务复杂、处理时间较长,影响其他任务的及时处理,如何解决?----大任务拆成小任务(部分解决问题,不是长久之计);多任务操作系统

    对于大部分的情况来说,我们可能主要用到用到状态机,至于消息事件机制,如果已经复杂到同步变量解决不了了,为什么不用操作系统呢?任务拆分也不是那么好拆的。所以一旦感觉到超出自己管理范围了,非常有可能是你的系统结构设计有问题。

    3)多任务操作系统

    这是另外一个比较复杂的话题,暂略。

  • 相关阅读:
    构建RAG应用-day05: 如何评估 LLM 应用 评估并优化生成部分 评估并优化检索部分
    音视频入门基础:H.264专题(8)——H.264官方文档的描述符
    淘宝数据采集接口
    欧洲历史脉络
    ORB-SLAM2 ---- Initializer::FindHomography函数
    YOLOv7改进:极简的神经网络模型 VanillaNet---VanillaBlock助力检测,实现暴力涨点 | 华为诺亚2023
    VSCode snippets
    用户身份管理(CIAM)如何帮助业务持续增长?|身份云研究院
    最新区块链论文速读--CCF A会议 INFOCOM 2023 共5篇 附pdf下载
    EL 表达式
  • 原文地址:https://blog.csdn.net/wxg_wuchujie88/article/details/128076427