嵌入式系统分层开发:架构模式与工程实践(四)(状态机的应用和面向对象的编程)
文章目录
- 摘要
- 一、状态概述
- 二、状态机理解
- 三、对象的封装
摘要
在C语言模块化编程中同样要使用面向对象的思想。
在模块化编程中,在系统架构中需要一个专门负责状态管理与状态转换逻辑的抽象层次。它是基于有限状态机(Finite State Machine, FSM)的理论模型,用于简化复杂系统的行为控制,将一个系统的控制状态跳转全部交给这个FSM层处理,此外在设计控制对象中,还需要关注如何构建一个控制体。
一、状态概述
FSM层是系统设计中专责状态转换的核心抽象层,通过状态→事件→动作的链式响应,将复杂行为分解为可控单元。它在资源受限的嵌入式环境或高可靠性场景(如航天控制、工业协议)中尤为关键,是平衡复杂性与可维护性的经典解决方案。
-
状态管理
-
状态(State):代表系统在特定时刻的稳定模式(如“待机”“运行”“错误”)。
-
状态存储:FSM层通过内部变量记录当前状态,作为决策基础。
-
-
事件响应
-
事件(Event):外部输入或内部条件变化(如按键触发、定时器超时、数据到达)。
-
转换逻辑:事件触发状态转移,FSM层根据预设规则更新状态并执行关联动作。
-
-
动作执行
-
动作(Action):状态转换时执行的操作(如启动电机、发送数据、更新界面)。
-
支持进入动作(进入状态时执行)、退出动作(离开状态时执行)等细分逻辑。
-
-
分层设计(HFSM)
复杂系统可能采用分层状态机(Hierarchical FSM),将状态划分为子状态机,例如
-
顶层状态:
运行模式
、休眠模式
; -
子状态:
运行模式
下细分采集数据
、处理数据
等。这种设计减少状态爆炸问题,提升可维护性。
-
-
硬编码实现
-
Switch-Case法:通过
switch(current_state)
分支处理事件,适合简单逻辑。 -
函数指针表:将状态映射到处理函数(如
state_handlers[state]()
),扩展性更优。
-
-
面向对象实现
- 状态模式(State Pattern):每个状态封装为独立类,通过虚函数统一接口(C++/Java常用)。
二、状态机理解
通过 “状态定义→事件响应→动作执行→状态迁移” 的闭环管理
对于状态机需要利用三个状态进行控制。
STATUS_ENTER
、STATUS_RUN
、STATUS_QUIT
:通过结构化生命周期管理解决了复杂系统的行为控制问题。
设计意义:模块化生命周期管理
-
STATUS_ENTER
:承担初始化任务(如外设配置、内存分配、变量复位),确保系统进入稳定起点。示例:STM32系统上电后初始化GPIO和定时器,加载默认参数。
-
STATUS_RUN
:执行核心业务逻辑(如数据采集、算法处理、实时控制),是系统的“主循环”状态。示例:电机控制中持续调整PWM输出,维持转速稳定。
-
STATUS_QUIT
:处理终止与清理(如关闭外设、保存状态、释放资源),保障安全退出。示例:设备关机前保存用户配置到Flash,关闭通信接口。
是将整个系统利用状态机给分离出来。每个状态对应明确的动作集,事件触发时仅需关注当前状态的行为,而非全局条件分支
STATUS_ENTER 初始化配置:独立出来这样构成一个状态,就能做一些事情(虽然现在可能还用不到,但是接口预留出来了)。STATUS_RUN 执行业务逻辑层:也就是系统运行STATUS_QUIT 处理终止与清理。
STATUS_ENTER → 分配资源 → STATUS_RUN → 释放资源 → STATUS_QUIT
避免资源泄漏(如未关闭的中断、未释放的内存)。
错误隔离与恢复机制
- 异常时可强制跳转至
STATUS_QUIT
,执行统一清理后重新进入STATUS_ENTER
,实现软重启。
分层状态机(HFSM)基础:
若业务复杂,每个状态可进一步拆解为子状态机(如STATUS_RUN
内含采集→处理→发送
子状态)。
-
设备启动流程
ENTER
(初始化时钟/外设) →RUN
(执行控制算法) →QUIT
(停机保存日志)。 -
通信协议处理
ENTER
(建立连接) →RUN
(数据传输) →QUIT
(断开并释放Socket)。 -
用户交互系统
ENTER
(加载界面资源) →RUN
(响应用户输入) →QUIT
(退出动画及资源释放)。
状态 | 核心职责 | 触发条件 | 典型动作 | 转换方向 |
---|---|---|---|---|
**STATUS_ENTER ** | 初始化与准备 | 系统启动/复位 | 资源分配、寄存器配置 | → STATUS_RUN |
**STATUS_RUN ** | 核心业务执行 | 事件驱动(如定时器触发) | 数据处理、控制算法、通信响应 | → 自身或STATUS_QUIT |
**STATUS_QUIT ** | 清理与安全退出 | 关机指令/严重错误 | 资源释放、状态保存、关闭外设 | → 系统停止或重新初始化 |
这三种状态构成了嵌入式系统的最小完备生命周期模型,通过强制划分执行阶段,解决了裸机开发中常见的“初始化与业务逻辑混杂”“异常处理不彻底”等痛点。结合状态表驱动和事件队列(如RTOS消息),可进一步构建高可靠嵌入式框架。
而在状态机下又包含不同的子状态机:如按键状态机、系统状态状态机等
状态(State)
系统在特定时刻的行为模式,例如:
- 按键扫描中的
空闲(IDLE)
、消抖(DEBOUNCE)
、按下(PRESSED)
- 设备运行中的
初始化(INIT)
、运行(RUN)
、故障(FAULT)
特点:状态互斥(同一时刻仅一种状态),且可枚举(有限集合)。
事件(Event)
触发状态转移的外部或内部信号,例如:
- 硬件事件:按键按下、串口数据到达、定时器超时
- 软件事件:数据校验完成、错误标志置位
作用:驱动状态机从“现态”向“次态”迁移。
动作(Action)
状态进入、退出或转移时执行的操作,例如:
- 进入运行状态时启动传感器采样
- 退出故障状态时复位错误计数器
关键:动作与状态绑定,而非嵌入条件分支。
转移(Transition)
事件触发后状态变化的规则。
在嵌入式开发过程中,只要芯片上电就会运行程序,这样通过交互就能实现不同的功能。此时我们就需要定义为上电状态,为什么不是关机状态,这是因为和关机状态处于一个层级的还有自检状态。那么由于存在关机状态和自检状态是一个级别,那么就需要再定义一个状态去管理这两个状态,那上电状态由此产生。所以基于这种思想我们是不是可以理解为在分析状态的时候或者状态跳转的时候我们不是凭空想想的,而是基于一定的逻辑去设置状态,这种逻辑就是产品本身所必须的逻辑,基于产品的功能和逻辑去设计状态才是核心,只有把状态细分以后,然后再去定义具体的功能怎么实现,才是正确的思路。
也就是实现通过 “状态定义→事件响应→动作执行→状态迁移” 的闭环管理。
三、对象的封装
图标的显示:
图标的显示是一种轮询,也就是说每一个图标都要轮询一下是否需要显示。在这个图标下面,需要使用状态机,什么状态显示什么,什么状态不应该显示,我们都要规定。也就是这个地方是和按键是一样的,按键也是根据什么状态才能触发什么事件,那显示也是根据状态确定显示状态。依次类推可以认为大部分的内容都可以根据状态来?
对于状态,我们有一个FSM层,这个层也叫有限状态机层,这个就是处理各种状态的切换的,例如腔体的状态切换、风机的状态切换等等。
所以按照图标依照状态进行显示推算,首先是确定系统状态、接着是腔体状态、再接着是设置状态。
系统状态分为:
1.上电初期、2.关机状态、3.待机状态(开机)、4.故障状态、5.显示板自检状态、6.电源板自检状态、7.演示模式、8.智能化产测模式、10.电源板长测、等还有其他状态。
腔体状态分为:
1.不工作、2.预热、3.预热暂停、4.工作、5.工作暂停、6.工作结束、7.预约状态、8.故障状态、9.设置状态、10.预约暂停状态。
设置状态分为:
1.模式设置、2.时间设置、3.温度设置等
其实这是也是一种状态机嵌套,因为设置状态只有在一部分的系统状态中是有效的,因此难点在于对系统工作状态的合理划分,要避免把程序的某个“动作”当做是一种状态处理,如何区分“动作”和“状态”?可以从本质出发:
1、“动作”是不稳定的,即使没有条件触发,“动作”一旦执行完毕就结束了。
2、“状态”是相对稳定的,如果没有外部条件触发,一个状态会一直持续下去。
还是应该在实际项目中去感受。
腔体状态:
进入状态(STATUS_ENTER):进入状态的初始化阶段(如资源分配、变量复位)
退出状态(STATUS_QUITE):退出状态的清理阶段(如释放资源、保存数据)
运行状态(STATUS_RUN):运行状态,执行该状态的核心逻辑(如数据采集、控制输出)
如果有了解过FreeRTOS,那么这三个状态明显的区分了任务中间切换的区别。但是在FreeRTOS中任务是自己调度的,而裸机开发是没有的,因此我们可以将任务认为的分为三个状态,进行区分。在FreeRTOS中任务状态分为运行态、非运行态:阻塞状态、就绪状态、挂起状态。但是由于裸机是不需要挂起状态的,因此就相当于是运行状态、就绪状态、阻塞状态,这只是腔体类比。相反任务状态也是类比,任务状态分为:暂停、运行、休眠,并且运行中的任务才可以暂停,休眠中的定时器才可以恢复。
腔体的目的是处理各自业务逻辑,对于产品功能来说就是经典快蒸、热风烘烤等各自功能的使用,并且各自功能的开启又需要引起风机和加热管的动作。对于产品功能来说只有日常洗、超快洗等各自功能的选择,而下面的负载是不需要关注的,因为在电源板上,对于产品来说负载的动作还是复杂的。
所以对于腔体功能看成一个对象,需要包含各自属性,类比于按键,按键需要包含这一个按键的属性,而每一个功能也是对象,这个地方应该怎么思考?或者说怎么抽象,一方面是产品的功能,一个功能是一个对象。当我们遇到一个新的需求,个人理解这个需求下面会有多个参数,那么这个需求就可能应该封装为一个对象,相反当一个个参数共同觉得某个功能的时候,那么这个功能就应该封装为一个对象,那么内容就是决定它的参数。或者排除法当移除多个参数的时候一些功能用不了,那么这多个参数就是该功能的内容,这也是需求分析阶段需要理解的。
例如最关键的每个模式:
那么每个模式运行前需要确定的对象也就是设置状态需要确定的,不管任何产品都会存在该功能吧,具体会有差异,以接触产品为例需要包含:模式的选择、
可以理解为一个句柄,用于控制该模式或者该功能。
如果觉得我的内容对您有帮助,希望不要吝啬您的赞和关注,您的赞和关注是我更新优质内容的最大动力。
专栏介绍
《嵌入式通信协议解析专栏》
《PID算法专栏》
《C语言指针专栏》
《单片机嵌入式软件相关知识》
《FreeRTOS源码理解专栏》
《嵌入式软件分层架构的设计原理与实践验证》
文章源码获取方式:
如果您对本文的源码感兴趣,欢迎在评论区留下您的邮箱地址。我会在空闲时间整理相关代码,并通过邮件发送给您。由于个人时间有限,发送可能会有一定延迟,请您耐心等待。同时,建议您在评论时注明具体的需求或问题,以便我更好地为您提供针对性的帮助。
【版权声明】
本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议。这意味着您可以自由地共享(复制、分发)和改编(修改、转换)本文内容,但必须遵守以下条件:
署名:您必须注明原作者(即本文博主)的姓名,并提供指向原文的链接。
相同方式共享:如果您基于本文创作了新的内容,必须使用相同的 CC 4.0 BY-SA 协议进行发布。
感谢您的理解与支持!如果您有任何疑问或需要进一步协助,请随时在评论区留言,笔者一定知无不言,言无不尽。