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

做开发1-3年的时候,一些简单的项目,功能基本都能实现,项目复杂度一上来,我的代码就变得乱七八糟了,改一行就崩一片,修个小bug像是拆弹,剪错一根线,整个程序原地爆炸。

后来接触了不少项目,我发现一个规律:那些代码写得牛的人,都有个共同点,很多功能明明很简单,代码却写得弯弯绕绕的,这不是一个全局变量就能搞定吗?为啥搞这么麻烦?

后面独立开发比较复杂项目时,才搞懂他们的精髓所在,像搭积木一样,整齐有序,扩展性和移植性又好,这背后靠的是软件架构。

我开始钻研这个东西,慢慢从"救命,代码炸了"的小白,变成了能淡定应对复杂项目的"老司机。

这些都是架构的功劳,没有架构,代码就是一盘散沙;有了架构,开发效率也能蹭蹭往上走,换一个项目大多数代码Ctrl C+V,然后稍微改改就搞定了。

好了,废话少说,进入正题。

我要介绍的三种嵌入式软件架构,分别是:分层架构、状态机架构、事件驱动架构。

每一种我都会掰开揉碎讲清楚,从原理、优点、到适用场景,还会带点例子,保你一看就懂,拿回去就能在项目里试试水。

一、分层架构

分层架构就像给代码分了"楼层":底层管硬件,中层管逻辑,上层管应用。

每层只跟旁边的层打交道,互不越界。

1.分层架构的优点

1.1 清晰到飞起:每层干啥一目了然,改代码不用满世界找。

1.2 扩展性强:加个新功能?直接在上层插一块就行,下层不动。

1.3 复用性高:底层写好了,别的项目也能拿来用。

2.适用场景

适合那种功能多、跟硬件耦合深的复杂项目,比如我们无际单片机项目6这种物联网网关、工业控制器等等。

3.实战举例

还是拿我们无际单片机的项目6(WiFi+4G+Lora防盗报警主机)为例:

我们一共把程序分为3层:硬件层、中间层、应用层。

硬件层(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("切换到离家布防\n");
            break;
        case EVENT_ARM_HOME:
            system->currentState = STATE_ARM_HOME;
            printf("切换到在家布防\n");
            break;
        default:
            printf("系统处于撤防状态\n");
            break;
    }
}

(2) 离家布防状态

行为:检测所有区域的入侵,若触发则进入报警状态。

可切换到:撤防、报警中。

void stateArmAway(SecuritySystem* system, SecurityEvent event) {
    switch (event) {
        case EVENT_DISARM:
            system->currentState = STATE_DISARMED;
            printf("切换到撤防\n");
            break;
        case EVENT_INTRUSION:
            system->currentState = STATE_ALARMING;
            system->alarmTimer = 30;  // 报警持续30秒
            printf("检测到入侵!切换到报警中\n");
            break;
        default:
            printf("系统处于离家布防状态\n");
            break;
    }
}

(3) 在家布防状态

行为:仅特定区域(如门窗)触发入侵报警,其他区域(如客厅)忽略。

可切换到:撤防。

void stateArmAway(SecuritySystem* system, SecurityEvent event) {
    switch (event) {
        case EVENT_DISARM:
            system->currentState = STATE_DISARMED;
            printf("切换到撤防\n");
            break;
        case EVENT_INTRUSION:
            system->currentState = STATE_ALARMING;
            system->alarmTimer = 30;  // 报警持续30秒
            printf("检测到入侵!切换到报警中\n");
            break;
        default:
            printf("系统处于离家布防状态\n");
            break;
    }
}

(4) 报警中状态

行为:发出警报,等待超时或手动撤防。

可切换到:撤防。

void stateAlarming(SecuritySystem* system, SecurityEvent event) {
    if (event == EVENT_TIMEOUT) {
        system->currentState = STATE_DISARMED;
        printf("报警超时,切换到撤防\n");
    } else if (event == EVENT_DISARM) {
        system->currentState = STATE_DISARMED;
        printf("报警中手动撤防\n");
    } else {
        printf("系统处于报警中\n");
    }
}

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 <stdio.h>

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\n", events[i]);
        runSecurityStateMachine(&system, events[i]);
    }

    return 0;
}

输出示例:

事件: 0
切换到离家布防
事件: 1
检测到入侵!切换到报警中
事件: 2
报警超时,切换到撤防
事件: 3
切换到在家布防
事件: 4
切换到撤防

5.总结

这个防盗报警系统的状态机包含四种模式:

离家布防:全面监控,入侵即报警。

在家布防:部分监控,灵活应对。

撤防:关闭监控,无报警。

报警中:触发警报,自动或手动结束。

通过状态机设计,系统逻辑清晰、易于扩展,非常适合管理防盗报警模式的复杂状态切换。

三、事件驱动架构

事件驱动就是有事做事,没事睡觉。系统等着事件(按键、传感器、定时器等)触发,一旦有动静,立马跑去处理,像个反应超快的客服。

在嵌入式系统中,这种架构通常通过事件循环、回调函数或消息队列实现。在单片机开发中,中断机制是最常见的事件驱动实现方式。

1.事件驱动架构的优点

快如闪电:实时性强,绝不拖泥带水。

省电省资源:没事时可以睡大觉,功耗低到感人,如果研究过TI蓝牙协议栈就知道,他们用的就是事件驱动架构。

模块化强:每个事件独立,互不干扰。

2.适用场景

适合实时性要求高、事件多的系统,比如RTOS应用、智能家居、GUI界面。

3.实战举例

按键按下:中断触发,灯开关。

定时器到点:检查光线传感器,自动调亮度。

串口收到命令:执行远程控制。

下面以代码示例模型,来直观感受下以事件驱动架构的实现。

设计思路

事件循环:主程序通过一个无限循环持续检查并处理事件队列中的事件。

回调函数:为每种事件类型定义一个处理函数,当事件发生时调用对应的函数。

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

// 定义事件类型
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 10
Event 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\n");
        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\n");
}

void timerTimeoutHandler(void) {
    printf("Timer timeout: adjust brightness\n");
}

void serialCommandHandler(void) {
    printf("Serial command received: remote control\n");
}

// 中断服务函数(模拟硬件触发)
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)模拟硬件触发,将事件加入队列。

代码运行输出:

Key pressed: toggle light
Timer timeout: adjust brightness
Serial command received: remote control

四、架构怎么选?

简单项目:事件驱动就够了,轻快省事。

复杂系统:分层架构稳如泰山。

状态多变:状态机一招搞定。

实时性强:事件驱动冲冲冲。

其实细心又有经验的朋友应该发现了,这几个架构并不是独立的,组合使用才是无敌的存在

比如我们无际单片机的项目6,就是分层架构+状态机+事件驱动架构的混合

3个架构组合去做一个复杂的项目,几乎能满足稳定性,扩展性,移植性,和低功耗的要求,符合成年人全要的原则。

总结一下:软件架构是嵌入式开发的灵魂,这篇文章从我的经历出发,聊了三种嵌入式软件架构的痛点和价值,希望你看完能有所启发,在单片机开发的路上越走越顺,越写越牛!


最近很多粉丝问我单片机怎么学,我根据自己从业十年经验,累积耗时一个月,精心整理一份「单

片机最佳学习路径+单片机入门到高级教程+工具包」全部无偿分享给铁粉!!!

除此以外,再含泪分享我压箱底的22个热门开源项目,包含源码+原理图+PCB+说明文档,让你迅速进阶成高手

教程资料包和详细的学习路径可以看我下面这篇文章的开头

单片机入门到高级开挂学习路径(附教程+工具)

单片机入门到高级开挂学习路径(附教程+工具)

单片机入门到高级开挂学习路径(附教程+工具)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值