ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

嵌入式 C语言上下文的快速切换

2021-12-19 19:32:51  阅读:149  来源: 互联网

标签:初始化 C语言 mainloop cpost 嵌入式 事件 模块 cevent 上下文


前言

我们通常认为,在中断中,不能执行耗时的操作,否则会影响系统的稳定性,尤其对于嵌入式编程。对于带操作系统的程序而言,可以通过操作系统的调度,将中断处理分成两个部分,耗时的操作可以放到线程中去执行,但是对于没有操作系统的情况,又应该如何处理呢

比较常见的,我们可能会定义一些全局变量,作为flag,然后在mainloop中不停的判断这些flag,再在中断中修改这些flag,最后在mainloop中执行具体的逻辑,但是这样,无疑会增加耦合,增加程序维护成本。

cpost

cpost链接:https://github.com/NevermindZZT/cpost
cpost正是应用在这种情况下的一个简单但又十分方便的工具,它可以特别方便的进行上下文的切换,减少模块耦合

cpost借鉴的Android的handler机制,通过在mainloop中跑一个任务,然后在其他地方,可以是中断,也可以是模块逻辑中,直接抛出需要执行的函数,使其脱离调用处的上下文,运行在mainloop中。cpost还支持延迟处理,可以指定函数在抛出后多久执行

使用

cpost的使用十分简单,这里以使用在嵌入式无操作系统中为例,主要用作中断延迟处理的情况

  • 配置系统tick
    配置cpost.h中的宏CPOST_GET_TICK(),配置成获取系统tick,以stm32 hal为例
    #define CPOST_GET_TICK() HAL_GetTick()
  • 配置处理进程
    在mainloop调用cpostProcess函数
    int main(void)
    {
        ...
        while (1)
        {
            cpostProcess();
        }
        return 0;
    }
    
  • 抛出任务
    在中断等需要进行上下文切换的地方调用cpsot接口,使其在mainloop中运行
    cpost(intHandler);

原理解析

cpost的原理其实很简单,其代码量也十分少,总共加起来就只有几十行代码,cpost维护了一个而全局的数组

CpostHandler cposhHandlers[CPOST_MAX_HANDLER_SIZE] = {0};

其中,数组的每一个元素表示包含了需要执行的函数和参数,当调用cpost接口时,被post的函数和参数会被保存在这个数组中,然后mainloop中运行的cpostProcess函数会遍历这个数组,当满足条件时,执行对应的函数,从而达到上下文切换的目的

void cpostProcess(void)
{
    size_t tick;
    for (size_t i = 0; i < CPOST_MAX_HANDLER_SIZE; i++)
    {
        if (cposhHandlers[i].handler)
        {
            tick = CPOST_GET_TICK();
            if (cposhHandlers[i].time == 0 || 
                (tick >= cposhHandlers[i].time 
                    ? CPOST_GET_TICK() >= cposhHandlers[i].time
                    : CPOST_MAX_TICK - cposhHandlers[i].time + CPOST_GET_TICK()))
            {
                cposhHandlers[i].handler(cposhHandlers[i].param);
                cposhHandlers[i].handler = NULL;
            }
        }
    }
}

cevent

https://github.com/NevermindZZT/cpost

原理

cevent借鉴的是Android系统的广播机制,一方面,各模块在工作的时候,都会有多个具体的事件点,在高耦合的编程中,可能会在这些地方调用其他模块的功能,比如说,在通信模块接收到指令的时候,需要闪烁一下指示灯。

使用cevent,我们可以在这些地方抛出一个事件,当前模块不需要关心在这各地方需要执行哪些其他模块的逻辑,由其他模块,或者用户定义一个事件监听,当具体的事件发生时,执行相应的动作。

使用

cevent使用注册的方式监听事件,会依赖于编译环境,目前支持keil,iar,和gcc,对于gcc,需要修改链接文件(.ld),在只读数据区添加:

_cevent_start = .;
KEEP (*(cEvent))
_cevent_end = .;
  1. 初始化cevent
    系统初始化时,调用ceventInit();
  2. 注册cevent事件监听
    在c文件中,调用CEVENT_EXPORT(0, handler, (void *)param);导出事件监听
  3. 发送cevent事件
    在事件发生的地方,调用ceventPost(0);抛出事件

使用cevent解耦模块初始化

嵌入式编程中,我们习惯会在程序启动的时候,调用各个模块的初始化函数,其实这也是一种耦合,会造成main函数中出现很长的初始化代码,借助cevent,我们可以对初始化进行优化解耦。

  1. 定义初始化事件
    定义初始化事件的值,对于初始化,有些模块可能会依赖于其他模块的初始化,会有一个先后顺序要求,所以这里我们可以把初始化分成两个阶段,定义两个事件,当然,如果有更复杂的要求,可以再多分几个阶段,只需要多定义几个事件就行

    #define     EVENT_INIT_STAGE1       0
    #define     EVENT_INIT_STAGE2       1
    
  2. 初始化cevent,抛出事件
    在main函数中初始化cevent,并抛出初始化事件

    int main(void)
    {
        ...
        ceventInit();
    
        ceventPost(EVENT_INIT_STAGE1);
        ceventPost(EVENT_INIT_STAGE2);
        ...
        return 0;
    }
    
  3. 注册事件监听
    对所有需要初始化的函数注册事件监听,这里我以对letter-shell注册事件监听为例,分为两个部分,初始化串口和初始化shell。

    在serial模块中,将串口初始化注册到初始化第一阶段,cevent支持将不大于7个的参数直接传递到注册的监听函数中,下面的注册方式,相当于在EVENT_INIT_STAGE1事件发生的地方,也就是main函数中对应的位置,调用serialInit(&debugSerial)

    CEVENT_EXPORT(EVENT_INIT_STAGE1, serialInit, (void *)(&debugSerial));
    

    然后再shell模块中,将shell初始化函数注册到初始化第二阶段。

    CEVENT_EXPORT(EVENT_INIT_STAGE1, shellInit);
    

使用cevent解耦mainloop

再无操作系统的嵌入式编程中,我们如果同时希望运行多个模块的逻辑,通常是在mainloop中循环调用,这种将函数写入mainloop的做法,也会增加耦合

int main(void)
{
    ...

    while (1)
    {
        // 写在mainloop中的模块逻辑
        shellTask(&shell);
        LedProcess();
        ...
    }
    return 0;
}

通过使用cevent,也可以很方便的消除这种耦合

  1. 定义mainloop事件
    定义mainloop事件的值
    #define     EVENT_MAIN_LOOP         3
    
  2. 在mainloop中抛出事件
    去掉mainloop中对其他模块的调用,改为排除mainloop事件
    int main(void)
    {
        ...
    
        while (1)
        {
            ceventPost(EVENT_MAIN_LOOP);
        }
        return 0;
    }
    
  3. 在各模块中注册事件监听
    分别在各个模块中,注册对mainloop事件的监听
    CEVENT_EXPORT(EVENT_MAIN_LOOP, shellTask, (void *)(&shell));
    CEVENT_EXPORT(EVENT_MAIN_LOOP, LedProcess);
    

结语

cevent是一个非常小的模块,本身代码及其简单,但是,通过模仿广播机制,让cevent可以发挥很强大的功能,通过,还可以结合cpost,实现延迟事件等功能。

标签:初始化,C语言,mainloop,cpost,嵌入式,事件,模块,cevent,上下文
来源: https://blog.csdn.net/helaisun/article/details/122027908

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有