电子产业一站式赋能平台

PCB联盟网

搜索
查看: 153|回复: 0
收起左侧

嵌入式开发用这3种软件架构,直接无敌。

[复制链接]

310

主题

310

帖子

2764

积分

三级会员

Rank: 3Rank: 3

积分
2764
发表于 2025-3-4 07:01:00 | 显示全部楼层 |阅读模式
关注公众号,回复“入门资料”获取单片机入门到高级开挂教程
开发板带你入门,我们带你飞

文 | 无际(微信:2777492857)
全文约4480字,阅读大约需要 15 分钟
做开发1-3年的时候,一些简单的项目,功能基本都能实现,项目复杂度一上来,我的代码就变得乱七八糟了,改一行就崩一片,修个小bug像是拆弹,剪错一根线,整个程序原地爆炸。
           
后来接触了不少项目,我发现一个规律:那些代码写得牛的人,都有个共同点,很多功能明明很简单,代码却写得弯弯绕绕的,这不是一个全局变量就能搞定吗?为啥搞这么麻烦?
           
后面独立开发比较复杂项目时,才搞懂他们的精髓所在,像搭积木一样,整齐有序,扩展性和移植性又好,这背后靠的是软件架构。
           
我开始钻研这个东西,慢慢从"救命,代码炸了"的小白,变成了能淡定应对复杂项目的"老司机。
           
这些都是架构的功劳,没有架构,代码就是一盘散沙;有了架构,开发效率也能蹭蹭往上走,换一个项目大多数代码Ctrl C+V,然后稍微改改就搞定了。
           
好了,废话少说,进入正题。
           
我要介绍的三种嵌入式软件架构,分别是:分层架构、状态机架构、事件驱动架构。
           
每一种我都会掰开揉碎讲清楚,从原理、优点、到适用场景,还会带点例子,保你一看就懂,拿回去就能在项目里试试水。
           
一、分层架构
分层架构就像给代码分了"楼层":底层管硬件,中层管逻辑,上层管应用。   
           
每层只跟旁边的层打交道,互不越界。
           
1.分层架构的优点
1.1 清晰到飞起:每层干啥一目了然,改代码不用满世界找。
1.2 扩展性强:加个新功能?直接在上层插一块就行,下层不动。
1.3 复用性高:底层写好了,别的项目也能拿来用。
           
2.适用场景
适合那种功能多、跟硬件耦合深的复杂项目,比如我们无际单片机项目6这种物联网网关、工业控制器等等。
           
3.实战举例
还是拿我们项目6(WiFi+4G+Lora防盗报警主机)为例:
           
我们一共把程序分为3层:硬件层、中间层、应用层。   

4trdpfw4d5q640519640.png

4trdpfw4d5q640519640.png

           
硬件层(HAL):单片机驱动外围器件的程序,比如控制LED、Flash、液晶屏、温湿度、电池电量检测、语言输出、触摸等等。
           
中间层:主要是把采集到的信号转换成具体的值,比如把ADC转换成电量,还有就是一些协议数据的解析,比如lora、4G、WiFi。
           
应用层:具体的产品业务逻辑,比如菜单、探测器配对、防盗报警逻辑等等。
           
这样分层的好处是啥?假如换了传感器,你只要改HAL层,其他层照用不误,省时省力。
           
二、状态机架构
状态机就是把系统分成几个状态,比如待机、运行、停止,然后定义好每个状态下能干啥、收到啥信号会跳到哪。就像玩游戏的流程图,走哪步都清清楚楚。
               
这个架构问的人最多,我来重点讲解下。
           
1.状态机架构优点
1.1 逻辑不乱:再复杂的流程也能理顺,画张图就明白。
1.2 调试省心:状态一目了然,问题出在哪秒定位。
1.3 稳定如山:不会因为漏了个条件就崩。
           
2.适用场景
适合有状态切换的系统,比如洗衣机控制、协议解析、按键处理。
           
3.实战举例
还是拿无际单片机的项目报警主机举例,展示如何使用状态机实现四种防盗报警模式:离家布防、在家布防、撤防和报警中的示例。
           
我将使用C语言实现一个简单的状态机,说明这四种模式如何定义、切换,并通过代码展示其工作原理。
           
1.1 防盗报警模式的状态机设计
状态机由状态、事件和状态转移规则组成,以下是设计的具体步骤:
(1) 定义四种状态
我们首先定义系统的四种状态:
撤防(Disarmed):系统未激活,不检测入侵。
离家布防(Arm Away):家中无人,所有区域的入侵都会触发报警。
在家布防(Arm Home):家中有人,仅特定区域的入侵触发报警。
报警中(Alarming):检测到入侵,系统发出警报。   
           
用C语言的枚举类型定义如下:
  • typedef enum {    STATE_DISARMED,    // 撤防    STATE_ARM_AWAY,    // 离家布防    STATE_ARM_HOME,    // 在家布防    STATE_ALARMING     // 报警中} SecurityState;
    (2) 定义触发事件
    状态的切换由事件驱动。以下是常见的事件:
    启动离家布防(EVENT_ARM_AWAY)
    启动在家布防(EVENT_ARM_HOME)
    撤防(EVENT_DISARM)
    检测到入侵(EVENT_INTRUSION)
    报警超时(EVENT_TIMEOUT)
               
    用C语言定义如下:
  • typedef enum {    EVENT_ARM_AWAY,    // 启动离家布防    EVENT_ARM_HOME,    // 启动在家布防    EVENT_DISARM,      // 撤防    EVENT_INTRUSION,   // 检测到入侵    EVENT_TIMEOUT      // 报警超时} SecurityEvent;
    (3) 定义系统结构
    为了记录当前状态和相关信息(如报警计时器),我们定义一个结构体:
  • typedef struct {    SecurityState currentState;  // 当前状态    int alarmTimer;              // 报警计时器(秒)} SecuritySystem;
    2.2 状态处理逻辑
    每个状态有对应的处理函数,根据接收到的事件决定是否切换状态或执行特定操作。
               
    (1) 撤防状态
    行为:系统未激活,不响应入侵。
    可切换到:离家布防、在家布防。
  • void stateDisarmed(SecuritySystem* system, SecurityEvent event) {    switch (event) {        case EVENT_ARM_AWAY:            system->currentState = STATE_ARM_AWAY;            printf("切换到离家布防
    ");            break;        case EVENT_ARM_HOME:            system->currentState = STATE_ARM_HOME;            printf("切换到在家布防
    ");            break;        default:            printf("系统处于撤防状态
    ");            break;    }}
               
    (2) 离家布防状态
    行为:检测所有区域的入侵,若触发则进入报警状态。
    可切换到:撤防、报警中。
  • void stateArmAway(SecuritySystem* system, SecurityEvent event) {    switch (event) {        case EVENT_DISARM:            system->currentState = STATE_DISARMED;            printf("切换到撤防
    ");            break;        case EVENT_INTRUSION:            system->currentState = STATE_ALARMING;            system->alarmTimer = 30;  // 报警持续30秒            printf("检测到入侵!切换到报警中
    ");            break;        default:            printf("系统处于离家布防状态
    ");            break;    }}           
    (3) 在家布防状态
    行为:仅特定区域(如门窗)触发入侵报警,其他区域(如客厅)忽略。
    可切换到:撤防。
  • void stateArmHome(SecuritySystem* system, SecurityEvent event) {    switch (event) {        case EVENT_DISARM:            system->currentState = STATE_DISARMED;            printf("切换到撤防
    ");            break;        case EVENT_INTRUSION:            printf("在家布防模式下检测到入侵,仅特定区域触发报警
    ");            // 可根据需求决定是否切换到报警中            break;        default:            printf("系统处于在家布防状态
    ");            break;    }}           
    (4) 报警中状态
    行为:发出警报,等待超时或手动撤防。
    可切换到:撤防。
  • void stateAlarming(SecuritySystem* system, SecurityEvent event) {    if (event == EVENT_TIMEOUT) {        system->currentState = STATE_DISARMED;        printf("报警超时,切换到撤防
    ");    } else if (event == EVENT_DISARM) {        system->currentState = STATE_DISARMED;        printf("报警中手动撤防
    ");    } else {        printf("系统处于报警中
    ");    }}           
    3.状态机运行函数
    以下函数根据当前状态调用对应的状态处理函数:
  • void runSecurityStateMachine(SecuritySystem* system, SecurityEvent event) {    switch (system->currentState) {        case STATE_DISARMED:            stateDisarmed(system, event);            break;        case STATE_ARM_AWAY:            stateArmAway(system, event);            break;        case STATE_ARM_HOME:            stateArmHome(system, event);            break;        case STATE_ALARMING:            stateAlarming(system, event);            break;    }}           
    4.示例代码与运行
    以下是完整的C语言代码,模拟状态切换:
  • #include int main() {    SecuritySystem system = {STATE_DISARMED, 0};  // 初始为撤防状态    // 模拟事件序列    SecurityEvent events[] = {        EVENT_ARM_AWAY,   // 启动离家布防        EVENT_INTRUSION,  // 检测到入侵        EVENT_TIMEOUT,    // 报警超时        EVENT_ARM_HOME,   // 启动在家布防        EVENT_DISARM      // 撤防    };    for (int i = 0; i sizeof(events)/sizeof(events[0]); ++i) {        printf("事件: %d
    ", events);        runSecurityStateMachine(&system, events);    }    return 0;}
    输出示例:
    C                  
    事件: 0                  
    切换到离家布防                  
    事件: 1                  
    检测到入侵!切换到报警中                  
    事件: 2                  
    报警超时,切换到撤防                  
    事件: 3                  
    切换到在家布防                  
    事件: 4                  
    切换到撤防
               
    5.总结   
    这个防盗报警系统的状态机包含四种模式:
    离家布防:全面监控,入侵即报警。
    在家布防:部分监控,灵活应对。
    撤防:关闭监控,无报警。
    报警中:触发警报,自动或手动结束。
    通过状态机设计,系统逻辑清晰、易于扩展,非常适合管理防盗报警模式的复杂状态切换。
               
    三、事件驱动架构
    事件驱动就是有事做事,没事睡觉。系统等着事件(按键、传感器、定时器等)触发,一旦有动静,立马跑去处理,像个反应超快的客服。
               
    在嵌入式系统中,这种架构通常通过事件循环、回调函数或消息队列实现。在单片机开发中,中断机制是最常见的事件驱动实现方式。
               
    1.事件驱动架构的优点
    快如闪电:实时性强,绝不拖泥带水。
    省电省资源:没事时可以睡大觉,功耗低到感人,如果研究过TI蓝牙协议栈就知道,他们用的就是事件驱动架构。
    模块化强:每个事件独立,互不干扰。
               
    2.适用场景
    适合实时性要求高、事件多的系统,比如RTOS应用、智能家居、GUI界面。   
               
    3.实战举例
    按键按下:中断触发,灯开关。
    定时器到点:检查光线传感器,自动调亮度。
    串口收到命令:执行远程控制。
               
    下面以代码示例模型,来直观感受下以事件驱动架构的实现。
               
    设计思路
    事件循环:主程序通过一个无限循环持续检查并处理事件队列中的事件。
    回调函数:为每种事件类型定义一个处理函数,当事件发生时调用对应的函数。
  • #include #include #include // 定义事件类型typedef enum {    EVENT_KEY_PRESS,      // 按键按下    EVENT_TIMER_TIMEOUT,  // 定时器到点    EVENT_SERIAL_COMMAND, // 串口命令    EVENT_MAX             // 事件类型总数} EventType;// 事件结构体,包含类型和回调函数指针typedef struct {    EventType type;    void (*callback)(void);  // 指向回调函数} Event;// 事件队列(环形缓冲区)#define EVENT_QUEUE_SIZE 10Event eventQueue[EVENT_QUEUE_SIZE];uint8_t eventQueueHead = 0;  // 队列头uint8_t eventQueueTail = 0;  // 队列尾// 全局回调函数表static Event callbacks[EVENT_MAX];// 注册回调函数void registerCallback(EventType type, void (*callback)(void)) {    callbacks[type].type = type;    callbacks[type].callback = callback;}// 事件入队void enqueueEvent(EventType type) {    // 检查队列是否已满    if ((eventQueueHead + 1) % EVENT_QUEUE_SIZE == eventQueueTail) {        printf("Event queue full
    ");        return;    }    eventQueue[eventQueueHead].type = type;    eventQueue[eventQueueHead].callback = callbacks[type].callback;    eventQueueHead = (eventQueueHead + 1) % EVENT_QUEUE_SIZE;}// 处理事件队列void processEvents(void) {    while (eventQueueTail != eventQueueHead) {        Event event = eventQueue[eventQueueTail];        if (event.callback != NULL) {            event.callback();  // 调用回调函数        }        eventQueueTail = (eventQueueTail + 1) % EVENT_QUEUE_SIZE;    }}// 示例回调函数void keyPressHandler(void) {    printf("Key pressed: toggle light
    ");}void timerTimeoutHandler(void) {    printf("Timer timeout: adjust brightness
    ");}void serialCommandHandler(void) {    printf("Serial command received: remote control
    ");}// 中断服务函数(模拟硬件触发)void EXTI0_IRQHandler(void) {    // 按键中断    enqueueEvent(EVENT_KEY_PRESS);}void TIM2_IRQHandler(void) {    // 定时器中断    enqueueEvent(EVENT_TIMER_TIMEOUT);}void USART1_IRQHandler(void) {    // 串口中断    enqueueEvent(EVENT_SERIAL_COMMAND);}// 主函数int main(void) {    // 注册回调函数    registerCallback(EVENT_KEY_PRESS, keyPressHandler);    registerCallback(EVENT_TIMER_TIMEOUT, timerTimeoutHandler);    registerCallback(EVENT_SERIAL_COMMAND, serialCommandHandler);    // 模拟事件触发    EXTI0_IRQHandler();  // 模拟按键按下    TIM2_IRQHandler();   // 模拟定时器到点    USART1_IRQHandler(); // 模拟串口命令    // 事件循环    while (1) {        processEvents();  // 处理事件队列        // 可在此添加其他任务    }    return 0;}
    事件类型和结构体:使用枚举EventType定义事件类型,结构体Event包含事件类型和回调函数指针。
               
    事件队列:使用环形缓冲区实现队列,eventQueue存储待处理事件,enqueueEvent将事件加入队列,processEvents从队列中取出并处理事件。
               
    回调函数:通过registerCallback为每种事件类型绑定一个处理函数(如keyPressHandler),事件发生时,调用对应的回调函数执行具体逻辑。
               
    事件循环:main函数中的while (1)循环不断调用processEvents,确保事件得到及时处理。
               
    中断服务:中断函数(如EXTI0_IRQHandler)模拟硬件触发,将事件加入队列。
               
    代码运行输出:
    C                  
    Key pressed: toggle light                  
    Timer timeout: adjust brightness                  
    Serial command received: remote control
               
               
    四、架构怎么选?
    简单项目:事件驱动就够了,轻快省事。
    复杂系统:分层架构稳如泰山。
    状态多变:状态机一招搞定。
    实时性强:事件驱动冲冲冲。
               
    其实细心又有经验的朋友应该发现了,这几个架构并不是独立的,组合使用才是无敌的存在。
               
    比如我们无际单片机的项目6,就是分层架构+状态机+事件驱动架构的混合。

    nrojyq4giyy640519740.png

    nrojyq4giyy640519740.png

    3个架构组合去做一个复杂的项目,几乎能满足稳定性,扩展性,移植性,和低功耗的要求,符合成年人全要的原则。
               
    总结一下:软件架构是嵌入式开发的灵魂,这篇文章从我的经历出发,聊了三种嵌入式软件架构的痛点和价值,希望你看完能有所启发,在单片机开发的路上越走越顺,越写越牛!
               
    end

    r1tmrsrizol640519840.jpg

    r1tmrsrizol640519840.jpg
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表