1.3.2 三要素实例

MS程序入口为main函数,驱动初始化后就进入第一要素大循环。

代码清单1-1:大循环

int main(void)
{
    ushort idata message;
    Byte idata type, value;
    Init();
    while(true) 
    {
        message = PendMessageQueue();
        type = GetMessageType(message);
        value = GetMessageData(message);
        switch(type) 
        {
            case MessageKey:                                //按键消息处理
                 EnterFunction(value);                      //函数指针处理方式
                 //KeyProcess(value);                       //状态机处理方式
                 break;
            case MessageUsart:                              //串口消息处理
                 UsartProcess(value);
                 break;
            //请填充消息及处理函数
            default:                                        //软件定时器消息处理
                Function(message);
                break;
        }
    }
}

先通过init()函数初始化系统,之后进入while(true)大循环,通过PendMessageQueue()函数读取消息,消息为16 bit,由高八位的消息类型和低八位的消息值组成。通过switch根据消息类型决定执行对应的事件处理函数,消息一般由外部设备的中断或者节拍产生。

1)MessageKey是按键消息类型,菜单界面设计支持函数指针和状态机两种方式。函数指针方式是基于EnterFunction函数指针变量执行相关按键操作;而状态机方式则是直接调用按键处理函数KeyProcess。

2)MessageUsart是串口消息类型,处理串口消息。

3)default是软件定时器,利用函数宏定义把16 bit的消息message转换成一个函数地址入口,再执行这个函数,详细内容在软件定时器部分讲解。

4)项目中新增消息,可以自己手动添加。但应注意先要在system.h的MessageType消息枚举类型中添加该消息。

大循环在不停处理消息,因为消息不能抢占,所以不适合有速度要求的设备,它只适合于界面等速度慢、处理时间长、可以被打断的进程。

第二要素系统节拍由定时器2的中断产生,代码如下。

代码清单1-2:系统节拍中断

void SystickHandler(void) interrupt 5 
{
     KeySystickRoutine();                          //按键例行服务程序
     RtcSystickRoutine();                          //时钟例行服务程序
     TimerSystickRoutine();                 //软件定时器例行服务程序
     /*例行程序*/
     TF2 = 0;
}

由Timer216 bit自动重装作为系统节拍。interrupt为中断标志关键字,中断号为5,初始化为10ms一次,即每秒执行100次,里面执行按键扫描例行程序、RTC时钟计时服务程序及软件定时器例行程序。实际项目需求的例行程序可以继续添加。

有些例行处理执行最后,抛出一条消息作为结束,这个消息在大循环中执行,比如按键扫描和软件定时器。受MCU51的性能影响,系统节拍无法取得更低,若太低,会导致系统频繁中断,节拍之间可执行的代码量太少,以22.1184MHz单指令周期的MCU51为例,假设节拍设置为1ms,每个节拍最多运行22K个指令。若设置为0.1ms,则只能运行2.2K个指令,这个显然有些少,除非节拍内操作非常简单。MS默认设置为10ms,也就是一个节拍对应221K个指令。需要重点指出的是,节拍内的例行处理,要尽可能在节拍规定的时间内完成。若偶尔超过节拍时间,只会影响节拍的相对精度,导致后续节拍的后延,而频繁的超时,则会导致系统无法正常运行。

此外,根据项目实际情况决定节拍的中断优先级及节拍间隔,比如录音,就需要高的节拍速度及高的中断优先级。不需要太精准的控制一般不建议采用优先级太高的节拍中断,但这样,其他中断会影响节拍的相对精度。笔者提倡尽可能地多用系统节拍处理一些事情,比如按键扫描,数码管显示,读取I/O状态等,尽量少用其他中断,因为其他中断不确定性强,当多个中断同时产生时,嵌套深,不可控因素多。应该说,高可靠系统尽量用大循环及节拍,其他中断少用,当然不是说不用。

第三要素中断可以由其他任意中断产生,可以是外部设备中断源,也可以是内部设备中断源,比如串口中断,可把接收的串口数据当作按键来处理。

代码清单1-3:串口中断

static void UsartHandler(void) interrupt 4
{
    RI = 0;
    PostMessage(MessageKey, SBUF);                // 模拟按键消息类型用于软件仿真
    //PostMessage(MessageUsart, SBUF);            // 发送串口消息
}

串口中断由各种外部中断、定时器或者串口等一些外设组成。串口中断,中断号为4,MS默认串口发送不用中断,仅接收用中断。对于简单的串口接收,尤其是数据量少的,可以直接发送串口类型消息PostMessage(MessageUsart,SBUF);对于数据量大的,复杂的串口接收,建议直接在中断中预处理成帧后,发出串口消息作为标志,再在大循环中处理。在Keil的Debug软件仿真时,可以用Serial#1串口终端作为串口显示及按键输入。对于一些外部中断,节拍扫描够用时,不建议用中断。