1. 从轮询到事件驱动:单片机编程的架构演进
记得我刚开始学单片机编程那会儿,写出来的代码简直是一锅乱炖。所有功能都塞在main函数的大循环里,按键检测、温度读取、LED控制全都挤在一起。代码虽然能跑,但每次修改都提心吊胆,生怕牵一发而动全身。
这种"大杂烩"式的编程方式就是典型的轮询架构。CPU像个勤劳的保安,不停地巡视每个功能模块:"按键有没有按下?温度要不要读取?LED该不该调光?"大部分时间都在空转,效率低下不说,代码还越来越臃肿。
后来接触到事件驱动架构,简直是打开了新世界的大门。CPU不用再不停地巡视了,而是可以"睡觉",等到有实际事件发生(比如按键按下、定时器到期)时才被唤醒工作。这种架构不仅省电,响应速度也快得多。
事件驱动的核心思想其实很生活化:就像小区的门卫不用挨家挨户问"需要送快递吗",而是等快递来了再通知住户。单片机的中断系统就是天然的"门卫",硬件事件发生时自动通知CPU,这才是高效的工作方式。
2. 状态机:管理复杂逻辑的利器
2.1 状态机的基本概念
状态机是我在单片机编程中用过最实用的工具之一。它特别适合处理那些有明确状态划分的场景,比如按键处理(按下、保持、释放)、电机控制(停止、正转、反转)、通信协议(空闲、接收中、校验)等等。
状态机的本质是把复杂的业务流程分解成一个个明确的状态,每个状态都知道自己该做什么,以及什么条件下该切换到下一个状态。这样写出来的代码特别清晰,就像给流程图画上了明确的路线图。
我最早用状态机是做按键处理。传统的按键检测要处理消抖、长短按、连按等复杂逻辑,代码写出来像一团乱麻。用状态机后,每个状态只关心自己的事情:
typedef enum {
KEY_IDLE, // 空闲状态
KEY_DEBOUNCE, // 消抖状态
KEY_PRESSED, // 按下状态
KEY_REPEAT // 连按状态
} KeyState;
void key_process(void) {
static KeyState state = KEY_IDLE;
static uint32_t press_time = 0;
switch(state) {
case KEY_IDLE:
if(按键按下) {
state = KEY_DEBOUNCE;
press_time = get_tick();
}
break;
case KEY_DEBOUNCE:
if(get_tick() - press_time > 20) { // 20ms消抖
if(按键仍然按下) {
state = KEY_PRESSED;
on_key_press(); // 处理按键按下
} else {
state = KEY_IDLE;
}
}
break;
// 其他状态处理...
}
}
这种写法比原来的if-else嵌套清晰多了,每个状态的责任都很明确,添加新状态也很容易。
2.2 状态机的进阶技巧
在实际项目中,我总结出几个状态机的使用技巧。第一是状态表驱动法,用表格来定义状态转移关系,这样修改逻辑时不用改代码,只需改表格数据:
typedef struct {
State current_state;
Event event;
State next_state;
void (*action)(void);
} StateTransition;
const StateTransition transition_table[] = {
{STATE_A, EVENT_X, STATE_B, action_x},
{STATE_A, EVENT_Y, STATE_C, action_y}

5237

被折叠的 条评论
为什么被折叠?



