美文网首页单片机学习嵌入式软件开发
嵌入式开发系列教程(五) 嵌入式系统软件设计(下)

嵌入式开发系列教程(五) 嵌入式系统软件设计(下)

作者: qianlihu | 来源:发表于2017-04-17 10:16 被阅读76次

    在上一节中,提到了关于延时操作的处理方案

    static void LedOn()
    {
        LED = ON   // 点亮LED灯
        delay(3)    //延迟3秒
        LED = OFF   //关闭LED灯
    }
    
    while(1){
        event = Event();     //事件检测,可以检测到按键按下,完成了事件的出队列
        if(event != -1){
            func = findEventHandler(event);  //事件匹配,可以匹配到LedOn函数
            func();                           //事件处理,调用LedOn函数,延时阻塞在这里
        }
    }
    

    在事件处理中,我们不能直接调用delay,会严重影响事件循环

    #define KEYDOWN     3   //按键按下的事件为3
    #define TIMER       4   //定时器事件
    static eventHandler[16] = {NULL,NULL,NULL,LedOn,LedOff ....};//LedOn的数组下标为3
    
    static void LedOn()
    {
        LED = ON;   // 点亮LED灯
        TimerOn(3);    //开启一个3s的定时器
    }
    void keyDownEvent()    //在中断中实现
    {
        if(isKeyDown())
            setBit(EVENT,KEYDONW);   //设置事件
    }
    void TimerEvent() //时钟中断中实现
    {
        setBit(EVENT,TIMER);
    }
    

    为了避免影响事件循环,我们引入了定时器事件,但代码逻辑已经非常不清楚。
    解决这种问题,大概有三种方案,OS的线程,协程,回调。三种方法各有利弊,我们先介绍回调方案,至于其他解决方案,留到后面的章节再去讨论

    我们先实现一个普适的定时器

    typdef void (*timerHandler)();
    static uint Tick  = 0;  //当前系统时钟滴答
    
    struct timerEvent{
        uint tick;     //时钟滴答
        timerHandler handler;  //定时器事件处理函数
    };
    
    struct timerEventNode{
        struct timerEvent timer;
        struct timerEventNode *next;
        struct timerEventNode *prev; 
    };
    
    struct timerEventHead{
        struct timerEventNode *next;
        struct timerEventNode *prev;
    }
    struct timerEventHead Head = {
        .next = NULL;
        .prev = NULL;
    };
    
    void setTimerEvent(int ticks,timerHandler handler) //tick为要延时的滴答数
    {
        struct timerEventNode *node = malloc(sizeof(*node));
        node->timer.tick = Tick + ticks;    //请思考一下回滚问题 
        node->timer.handler = handler;
      
        if(Head.next == NULL && Head.prev == NULL){  //定时器链表操作
            ...
        }else{
            ...
        }
    }
    
    void execTimerEvent()
    {
        struct timerEventNode *node = NULL;
        for(node = Head.next;node != NULL;node = node->next){
            if(node.timer.tick == Tick){ //定时器到了
                node.timer.handler() //调用定时处理函数
                //TODO     从定时器链表中删掉自己
            }
        }
    }
    
    void timerEvent()  //中断中实现,定时器中断若被设置为1ms间隔,则滴答计数器的精度为1ms
    {
        Tick++;    //系统滴答计数加一,
        execTimerEvent(); //寻找到时的计时器,并执行相应处理函数
    }
    
    
    void timerInit()
    {
        //TODO  //设置定时器
    }
    

    根据上面伪代码的实现,我们可以了解到这个定时器,有以下特点

    • 系统滴答间隔为1ms。也就是说,我们1ms遍历一次定时器链表。
    • 定时器是链表实现的,也就是说,每次对定时器链表的遍历都是O(n)的复杂度
    • 定时滴答数会回滚,即 当前滴答数为MAX_INT时,下一个滴答便会回滚到0
    • struct timerEventtick参数为系统Tick+间隔,这对回滚有什么益处?

    你可以思考怎么优化上面提出的问题,这里我们不再讨论。

    我们再回头看一下LED定时闪烁的问题

    void LedFlash()   //led 闪烁,相比来讲代码结果已经比较清晰
    {
        if(LED == ON)
            LED = OFF;
        else
            LED = ON;
        setTimerEvent(3000,LedFlash); //led 3000ms(tick) 闪烁一次
    }
    

    我们把这段代码结合我们前面提到的定时器放到我们的事件循环中

    
    void fifoPush(int event)      //先入先出队列,数据入队
    int  fifoPop()                //出队,无数据返回 -1
    
    int Event()
    {
        reutrn fifoPop()     //没有事件时,返回 -1
    }
    typedef void (*eventHandler)(); //函数指针的声明
    
    #define KEYDOWN     3   //按键按下的事件为3
    #define TIMER       4   //定时器事件
    
    static eventHandler[16] = {NULL,NULL,NULL,LedFlash,timerEvent ....};//LedFlash的数组下标为3
    
    void keyDownEvent()    //在中断中实现
    {
        if(isKeyDown())
            fifoPush(KEYDONW);   //设置事件
    }
    
    void LedFlash()   //led 闪烁,相比来讲代码结果已经比较清晰
    {
        if(LED == ON)
            LED = OFF;
        else
            LED = ON;
        setTimerEvent(3000,LedFlash); //led 3000ms(tick) 闪烁一次
    }
    
    int main()
    {
        int event;
        eventHandler func;
        timerInit();
        while(1){
            event = Event();     //事件检测,可以检测到按键按下,完成了事件的出队列
            if(event != -1){
                func = findEventHandler(event);  //事件匹配,可以匹配到LedFlash函数
                func();                           //事件处理,调用LedFlash函数
            }
        }
    }
    
    

    我们看到,对于LED闪烁函数,其代码结构已经变得比较清晰。
    请思考一下上面代码中,timerEvent的执行过程,setTimerEvent的执行过程,以及事件是怎么挂载和卸载的。

    这是一个免费,开源的教程,如果你喜欢可以转发,也可以打赏奖励。 欢迎关注微信公众号小站练兵

    相关文章

      网友评论

        本文标题: 嵌入式开发系列教程(五) 嵌入式系统软件设计(下)

        本文链接:https://www.haomeiwen.com/subject/hwttzttx.html