实用的嵌入式系统我们一般采用分层的架构,可以分成3层(硬件层、驱动层和应用层)或4层(硬件层、驱动层、操作系统层和应用层),这在例解嵌入式系统分层结构已有分析,但是应用程序本身是非常复杂的,尤其是可能涉及到各种人机交互、机机交互的问题,使得系统更加复杂,所以对系统程序的架构做进一步的分析是很有必要的。
下面的分析我们都假定系统在大的层次上分为3层,设备驱动已准备好,下面的代码为伪代码,看懂程序结构即可。
例:流水灯
- /*exe-1*/
- #include"config.h"
- int main(void){
- unsigned char i=0;
- //
- leds_init();
- //
- while(1){
- for(i=0;i<8;i++){
- leds_on(i);
- delay(1000);
- leds_off();
- }
-
- }
- return 0;
- }
没有交互的任务很少。
例:按键控制数码管,数码管显示按下的按键名称1、2、3
1)采用查询方式
- /*exe-2*/
- #include "config.h"
- int main(){
- unsigned char key=0;
- //初始化
- segment_init();
- key_init();
- //主循环
- while(1){
- key=getKey();
- switch(key){
- case 1:segment_off();segment_on(0,1);break;
- case 2:segment_off();segment_on(0,2);break;
- case 3:segment_off();segment_on(0,3);break;
- default::segment_off();segment_on(0,_OFF_);break;
- }
- delay(100);
- }
2)采用中断方式
按键触发外部中断,在中断服务程序中处理按键,并显示键值
- /*exe-3*/
- #include "config.h"
- void eint_isr(void){
- key=getKey();
- switch(key){
- case 1:segment_off();segment_on(0,1);break;
- case 2:segment_off();segment_on(0,2);break;
- case 3:segment_off();segment_on(0,3);break;
- default::segment_off();segment_on(0,_OFF_);break;
- }
- }
- int main(){
- unsigned char key=0;
- //初始化
- segment_init();
- key_init();
- VIC_init();
- //主循环
- while(1);
- }
用上了中断后你就会爱上它,舍不得不用了。
例:处理按键、显示、控制、测量电机转速
按键三个,分别为加速,减速,停止,属于输入。显示属于输出,电机属于输出,电机测试属于输入。
完成上述工作需要实现的功能很多,不用操作系统的情况下,我们一般采用中断驱动的前后台结构
主函数的主循环中处理各种交互及业务,适合于简单任务
- /*exe-4*/
- #include "config.h"
- void handle_key(){
- ...
- }
- void handle_disp(){
- ...
- }
- void handle_motor(){
- ...
- }
-
- int main(){
- unsigned char key=0;
- //初始化
- segment_init();
- key_init();
- motor_init();
- //主循环
- while(1){
- handle_key();
- handle_disp();
- handle_motor();
- }
- }
每个任务不能执行时间太长,否则会影响执行效果。
代码和上述exe-4相似,将按键、显示等都放在中断程序中,主循环空转
中断驱动结构的主要问题是将业务逻辑也放在了中断服务程序中,但业务逻辑比较复杂时,中断响应可能不及时,并且结构复杂。
在各种任务中,有些任务是必然存在的:数据的的显示、按键的处理等,一般来说业务逻辑负责处理和产生数据。这时候我们可以采用所谓的MVC架构,M即模型(model),可以看作就是数据,V即视图(view),如何显示数据;而C即控制(control),也就是处理、产生数据;业务逻辑(C)处理数据、模型保存数据、视图显示数据,业务逻辑一般最复杂,且分布较为复杂。MVC架构对于有交互的系统来说这是一种非常有效的架构,通常我们定时器中断周期性的处理显示问题、按键扫描问题。下面的结构仔细体会:
- /*exe-5*/
- #include "config.h"
- //处理和显示的数据,相当于model
- int keyValue=0;
- int motor_cnt=0;//记录电机转速产生的脉冲数
- int motorSpeed=0;
- unsigned char pattern[]={_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,_OFF_};
- //同步变量
- int motorCntrl_flag=0;
- int speedDisp_flag=1;
- int keyPress_flag=0;
-
- void sysTick_isr(void){
- //按键处理,获得键值--getKey()
- //置处理按键标志
- if(keyValue!=0){
- keyPress_flag=1;
- }
-
- //显示速度值----MVC中的view
- segment_disp(pattern,5);
- //处理电机速度
- if(time==1S){
- motorSpeed=motor_cnt*60;
- motor_cnt=0;
- speedDisp_flag=1;
- }
- }
- //下面都是业务逻辑了
- void eint1_isr(void){//外部中断
- motor_cnt++;//测量发生中断的次数,通过一秒钟发生的中断次数计算电机转速
-
- }
- void handle_key(){
- switch(keyValue){
- case 1:motorCntrlFlag=1;break;
- case 2:motorCntrlFlag=2;break;
- case 3:motorCntrlFlag=3;break;
- case 4:motorCntrlFlag=4;break;
- }
- }
- void handle_motor(){
- switch(motorCntrlFlag){
- case 1://电机加速
- ...;break;
- case 2://电机减速
- ...;break;
- case 3://电机停止
- ...;break;
- case 4://电机启动
- ...;break;
- }
- }
- void handle_disp(){
- int speed=motorSpeed;
- for(i=7;i>=0;i--){
- pattern[i]=speed%10;
- speed/=10;
- }
- }
- int main(){
- //初始化
- segment_init();
- key_init();
- motor_init();
- //主循环
- while(1){//主循环中主要放业务逻辑或需要处理时间很长的任务
- if(keyPress==1){
- handle_key();
- keyPress=0;//保证只执行一次,其他类似,一般在中断其启动,在此执行处理
- }
- if(speedDisp_flag==1){
- handle_disp();
- speedDisp_flag=0;
- }
- if(motorCntrlFlag!=0){
- handle_motor();
- motorCntrlFlag=0;
- }
-
- }
- }
MVC架构实现例1-4:
- /*exe-1的改进*/
- #include"config.h"
- //显示的数据,相当于MVC中的model;
- unsigned char pattern[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
- int main(void){
- unsigned char i=0;
- //
- leds_init();
- //
- while(1){
- for(i=0;i<8;i++){//显示任务,只复杂显示,与显示的内容无关,相当于view
- leds_on(pattern[i]);
- delay(1000);
- leds_off();
- }
-
- }
- return 0;
- }
- /*exe-2的改进*/
- #include "config.h"
- //数据,相当于model
- unsigned char pattern[]={_OFF_,};
- int main(){
- unsigned char key=0;
- //初始化
- segment_init();
- key_init();
- //主循环
- while(1){
- //按键处理
- key=getKey();
- switch(key){
- case 1:pattern[0]=1;break;
- case 2:pattern[0]=2;break;
- case 3:pattern[0]=3;break;
- default::pattern[0]=_OFF_;break;
- }
- //显示处理
- segment_off();//消影
- segment_on(0,pattern[0]);//在0位显示按键值
- dealy(100);
- }
- }
- /*exe-3的改进*/
- #include "config.h"
- unsigned char pattern[]={_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,_OFF_,};
- void sysTick_isr(void){
- //按键处理
- key=getKey();
- if(key)
- pattern[0]=key;
- //显示
- segment_disp(*pattern,10);
- }
- int main(){
- unsigned char key=0;
- //初始化
- segment_init();
- key_init();
- VIC_init();
- //主循环
- while(1);
- }
从上述分析可以看出,对于嵌入式系统来说,定时器是非常重要的。但其重要性还不仅如此,我们常常会遇到系统要求在什么样的时间条件下做什么事情、又在什么样的时间条件下做什么事情,这时定时器中断服务程序可能要控制多个同步变量、或进行多种处理、不仅仅计时,为处理效率考虑,我们常常发现定时器不够用了!
从这里我们也可以看出,嵌入式系统中有三个设备非常重要:GPIO、中断、定时器!一定要掌握。
这里还有几个问题需要解决:
执行的任务多,有一定的实时性要求----时间轮片法。考虑将任务函数进行拆分能通过程序优化缩短执行时间则最好优化,如果不能优化的,则必须保证该任务的执行周期必须远大于任务所执行的耗时时间,同时要求主循环或任务函数中不能存在毫秒级别的延时。再多的话,系统就太复杂了,就要考虑上操作系统了。
随着系统复杂程度的增加,同步变量的数量越来越多,如何管理这些同步变量?----消息事件机制,统一管理
同一个消息,同一个任务,在不同的状态下需要执行不同的操作,业务逻辑复杂如何解决?---有限状态机
某轮询任务复杂、处理时间较长,影响其他任务的及时处理,如何解决?----大任务拆成小任务(部分解决问题,不是长久之计);多任务操作系统
对于大部分的情况来说,我们可能主要用到用到状态机,至于消息事件机制,如果已经复杂到同步变量解决不了了,为什么不用操作系统呢?任务拆分也不是那么好拆的。所以一旦感觉到超出自己管理范围了,非常有可能是你的系统结构设计有问题。
这是另外一个比较复杂的话题,暂略。