关注公众号,回复“入门资料”获取单片机入门到高级开挂教程
开发板带你入门,我们带你飞
文 | 无际(微信:2777492857)
全文约3555字,阅读大约需要 10 分钟
之前直播,有人让我讲讲状态机,因为涉及内容比较多,当时一时半会没想好,口头表述不够系统。
而且要有具体需求去设计比较直观些,今天这篇文章,就以我们无际单片机的项目《WiFi+4G+LoRa 防盗报警主机》为例,讲解下状态机的设计。
报警主机这个设备是智能家居和安防系统的核心,功能丰富、技术多样,非常适合用来学习嵌入式软件设计的全流程。
我们将从需求分析开始,一步步梳理功能、设计状态机,最后用代码实现,确保你看完就能明白整个过程,并能动手实践。文章会用通俗易懂的语言,配上代码和注释,全文3000字以上,耐心看完,受益匪浅。
1.项目背景与需求分析
防盗报警主机是安防系统的大脑,它的任务是检测非法入侵并及时通知用户。
我们这个项目的主机支持 WiFi、4G和 LoRa 三种通信方式,并带锂电池应急供电,确保无论环境如何都能保持可靠的警报传递。
WiFi 和 4G 负责连接云平台,实现远程监控和通知;LoRa 是一种低功耗广域网技术,用于和家里的本地设备(如门磁探测器、遥控器、红外、烟感等)组网通信。
在动手设计之前,我们先把需求理清楚,满血版的功能,实在太多了,像通信、防盗报警、探测器组网、电话、短信、OTA等等。
所以,我只提取其中一个防盗报警模式功能,去教大家入门状态机设计,方便理解。
通信功能:主机需要通过 WiFi 和 4G 与云平台交互,发送警报或接收用户指令;通过 LoRa 与门磁探测器、遥控器通信,获取传感器信号或用户控制信号。
防盗报警模式:系统支持多种工作模式,包括“撤防”(不检测入侵)、“在家布防”(只检测外部入侵)、“离家布防”(检测所有区域入侵)和“报警中”(触发警报并通知用户)。
状态机是一种非常适合嵌入式系统的设计方法,能让复杂的逻辑变得有条理,代码也更容易维护。
2.为什么要用状态机?
在嵌入式开发中,像防盗报警主机这样的设备需要根据用户指令、传感器信号和通信状态不断切换工作模式。如果用一堆 if-else 判断来写代码,逻辑很快就会乱成一团,调试和维护都特别痛苦。状态机(State Machine)就是专门解决这种问题的利器。
状态机的核心思想是把系统的工作模式定义为有限的“状态”,然后通过“事件”触发状态之间的切换。这样,代码逻辑变得清晰,每次只处理当前状态下的事件,既直观又容易扩展。
比如,我们可以定义“撤防”和“报警中”两个状态,当收到“入侵检测”事件时,从“撤防”切换到“报警中”,同时触发蜂鸣器和发送通知。这样的设计是不是一目了然?
接下来,我们就从需求出发,设计这个防盗报警主机的状态机,包括状态定义、事件设计和转移规则,最后再用代码实现。
3.状态机的设计过程
定义状态
根据需求,防盗报警主机有几种明显的工作模式,我们直接把它们定义为状态:
?Idle(撤防):系统不检测任何入侵,用户可以随意活动,家里有人时常用这个模式。
?Armed_Home(在家布防):只检测外部区域(比如门窗)的入侵,忽略内部活动,适合晚上睡觉时使用。
?Armed_Away(离家布防):检测所有区域的入侵,家里没人时用这个模式。
?Alarm(报警中):检测到入侵后进入这个状态,触发蜂鸣器并发送警报通知。
这样,系统的状态就定下来了:Idle、Armed_Home、Armed_Away 和 Alarm。这四个状态覆盖了所有核心功能。
设计事件
状态机靠事件驱动,事件就是触发状态转换的“信号”。根据需求,我们定义以下事件:
?Arm_Home_Command:用户通过遥控器或手机发送“在家布防”命令。
?Arm_Away_Command:用户发送“离家布防”命令。
?Disarm_Command:用户发送“撤防”命令。
?Intrusion_Detected:门磁探测器等传感器检测到入侵。
?Timeout:警报持续一段时间后自动停止。
这些事件会推动系统在不同状态之间切换,接下来我们就定义这些切换规则。
状态转移规则
状态机的核心逻辑是“当前状态 + 事件 = 下一个状态”。我们逐个状态来看看它们在不同事件下的反应。
Idle 状态
?用户发送 Arm_Home_Command,系统进入 Armed_Home,开始监控外部区域。
?用户发送 Arm_Away_Command,系统进入 Armed_Away,监控所有区域。
在这个状态下,系统不检测入侵,所以 Intrusion_Detected 不会触发任何动作。
Armed_Home 状态
?收到 Intrusion_Detected(仅外部区域),系统进入 Alarm,触发警报。
?收到 Disarm_Command,系统回到 Idle,停止监控。
在家布防模式下,内部活动不会触发警报,这是和离家布防的关键区别。
Armed_Away 状态
?收到 Intrusion_Detected(任何区域),系统进入 Alarm。
?收到 Disarm_Command,系统回到 Idle。
离家布防模式下,所有入侵都会触发警报,保护力度最大。
Alarm 状态
?收到 Disarm_Command,系统回到 Idle,关闭警报。
?收到 Timeout,警报持续一段时间后自动停止,系统回到 Idle。
在这个状态下,系统会持续触发蜂鸣器并尝试通过 WiFi、4G 发送警报通知。
动作与行为
状态切换时,系统还需要执行一些动作,比如:
?进入 Alarm 时:启动蜂鸣器、发送警报通知到云平台。
?回到 Idle 时:关闭所有警报和指示灯。
这些动作可以在状态转换时执行,也可以在进入状态时处理,具体实现时可以根据硬件需求调整。
4.代码实现
设计好状态机后,我们用 C 语言把它实现出来。嵌入式开发中,状态机通常用 switch-case 或函数指针实现,这里我们选简单的 switch-case,方便理解。
定义状态和事件
先定义状态和事件的枚举类型:
#include // 定义状态typedef enum { STATE_IDLE, // 撤防 STATE_ARMED_HOME, // 在家布防 STATE_ARMED_AWAY, // 离家布防 STATE_ALARM // 报警中} State;// 定义事件typedef enum { EVENT_ARM_HOME, // 在家布防命令 EVENT_ARM_AWAY, // 离家布防命令 EVENT_DISARM, // 撤防命令 EVENT_INTRUSION, // 入侵检测 EVENT_TIMEOUT // 超时} Event;// 当前状态,初始为撤防State current_state = STATE_IDLE;
实现状态机逻辑
接下来写一个函数 handle_event,根据当前状态和事件处理状态转换:
void handle_event(Event event) { switch (current_state) { case STATE_IDLE: if (event == EVENT_ARM_HOME) { printf("进入在家布防模式
"); current_state = STATE_ARMED_HOME; } else if (event == EVENT_ARM_AWAY) { printf("进入离家布防模式
"); current_state = STATE_ARMED_AWAY; } break; case STATE_ARMED_HOME: if (event == EVENT_INTRUSION) { printf("外部入侵检测到,触发报警
"); current_state = STATE_ALARM; // 假设外部入侵检测逻辑已区分 } else if (event == EVENT_DISARM) { printf("从在家布防撤防
"); current_state = STATE_IDLE; } break; case STATE_ARMED_AWAY: if (event == EVENT_INTRUSION) { printf("入侵检测到,触发报警
"); current_state = STATE_ALARM; } else if (event == EVENT_DISARM) { printf("从离家布防撤防
"); current_state = STATE_IDLE; } break; case STATE_ALARM: if (event == EVENT_DISARM) { printf("从报警状态撤防
"); current_state = STATE_IDLE; } else if (event == EVENT_TIMEOUT) { printf("报警超时,回到撤防
"); current_state = STATE_IDLE; } break; default: printf("未知状态
"); break; }}这段代码用 switch-case 实现了状态机的逻辑。每个状态下,根据传入的事件,系统会打印当前行为并更新 current_state。在实际项目中,printf 可以替换为硬件操作,比如控制蜂鸣器或发送网络数据。
测试状态机
我们来模拟一个使用场景:用户布防、检测到入侵、撤防、报警超时。主函数如下:
int main() { printf("初始状态:撤防
"); handle_event(EVENT_ARM_AWAY); // 用户发送离家布防命令 handle_event(EVENT_INTRUSION); // 传感器检测到入侵 handle_event(EVENT_DISARM); // 用户撤防 handle_event(EVENT_TIMEOUT); // 超时(此时无作用) return 0;}
运行结果会是:
初始状态:撤防
进入离家布防模式
入侵检测到,触发报警
从报警状态撤防
| 这个输出清晰地展示了状态机的每一步转换,完全符合我们设计的需求。
当然,这个只是展示一个框架模型,无际单片机这个项目的功能,远远比这个复杂,细节很多,从头到尾开发的话,哪怕有经验的情况下,也要一年半载的。
h13bphtxbic6403418713.png
光状态机还不够,需要多种架构的组合,比如表驱动法+状态机。
5.状态机的优势与优化
通过上面的设计和代码,我们可以看到状态机的几个优点:
?逻辑清晰:每个状态和事件都独立处理,代码结构一目了然,后面每个功能修改都互不影响。
?易于扩展:如果想加个“低电量警告”状态,只需在 State 枚举中加一项,再增加对应的 case 分支。
?调试方便:打印当前状态就能快速定位问题。
不过,在实际应用中,还有一些细节需要注意。比如,如果状态和事件太多,可能导致“状态爆炸”,这时可以考虑用子状态机,把复杂状态拆分成更小的模块。
另外,每个状态下的事件要尽量覆盖全面,避免遗漏;比如在 Alarm 状态下,可以加个定时器检查 Timeout,确保警报不会无限鸣响。
通过这个防盗报警主机的项目,我们从需求分析开始,梳理了防盗模式的功能需求,接着设计了状态机,定义了状态和事件,最后用 C 语言实现了完整的逻辑。这个过程不仅展示了状态机的强大之处,也提供了一个可以直接套用到其他嵌入式项目的模板。
在实际开发中,你可以根据硬件特性调整代码,比如用中断处理传感器信号,用 FreeRTOS 任务管理通信模块。状态机是个灵活的工具,只要掌握了它的设计思路,就能轻松应对各种复杂需求。希望这篇文章能给你带来启发,如果你有自己的项目想法,不妨试试用状态机设计一下,效果绝对超出预期!
end
nm5ebsh1c1s6403418813.jpg
下面是更多无际原创的个人成长经历、行业经验、技术干货。
1.电子工程师是怎样的成长之路?10年5000字总结
2.如何快速看懂别人的代码和思维
3.单片机开发项目全局变量太多怎么管理?
4.C语言开发单片机为什么大多数都采用全局变量的形式?
5.单片机怎么实现模块化编程?实用程度让人发指!
6.c语言回调函数的使用及实际作用详解
7.手把手教你c语言队列实现代码,通俗易懂超详细!
8.c语言指针用法详解,通俗易懂超详细! |