4. 事件驱动架构之串口命令解析
任务定义
本案例实现一个基于事件驱动的串口命令解析器。系统通过串口中断接收字节,将完整命令识别为事件并投递到事件队列,主循环从队列取出事件并分发处理。命令格式定义为:CMD:PARAM\r\n(冒号分隔命令与参数,回车换行结束)。目标命令为LED:ON、LED:OFF、PWM:数值三种,其余输入视为未知命令事件。
事件驱动架构设计
核心思路是将“接收字节”与“业务处理”解耦:串口中断只负责把字节放入环形缓冲区并置位标志,不执行耗时操作;主循环检测到完整行后解析并生成事件,通过事件队列传递给处理函数。这样既保证中断响应及时,又使业务逻辑可独立测试。
代码实现
// event_uart_parser.c
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
// ---------- 环形缓冲区 ----------
#define RING_SIZE 256
static uint8_t ring[RING_SIZE];
static volatile uint16_t head = 0, tail = 0;
static bool ring_empty(void) { return head == tail; }
static bool ring_full(void) { return ((head + 1) % RING_SIZE) == tail; }
static bool ring_write(uint8_t byte) {
if (ring_full()) return false;
ring[head] = byte;
head = (head + 1) % RING_SIZE;
return true;
}
static bool ring_read(uint8_t *byte) {
if (ring_empty()) return false;
*byte = ring[tail];
tail = (tail + 1) % RING_SIZE;
return true;
}
// ---------- 事件定义 ----------
typedef enum {
EV_LED_ON,
EV_LED_OFF,
EV_PWM_SET,
EV_UNKNOWN_CMD,
EV_NONE
} EventId;
typedef struct {
EventId id;
int param; // PWM数值或保留
char raw[32]; // 原始命令文本
} Event;
// ---------- 事件队列 ----------
#define EV_QUEUE_SIZE 8
static Event ev_queue[EV_QUEUE_SIZE];
static uint8_t ev_head = 0, ev_tail = 0, ev_count = 0;
static bool ev_queue_push(Event *ev) {
if (ev_count >= EV_QUEUE_SIZE) return false;
ev_queue[ev_tail] = *ev;
ev_tail = (ev_tail + 1) % EV_QUEUE_SIZE;
ev_count++;
return true;
}
static bool ev_queue_pop(Event *ev) {
if (ev_count == 0) return false;
*ev = ev_queue[ev_head];
ev_head = (ev_head + 1) % EV_QUEUE_SIZE;
ev_count--;
return true;
}
// ---------- 行解析状态机 ----------
typedef enum {
ST_IDLE,
ST_CMD,
ST_PARAM,
ST_CR
} ParseState;
static ParseState pstate = ST_IDLE;
static char line_buf[32];
static uint8_t line_len = 0;
// 将一行文本解析为事件
static void parse_line(char *line, Event *ev) {
char *colon = strchr(line, ':');
if (colon == NULL) {
ev->id = EV_UNKNOWN_CMD;
strncpy(ev->raw, line, sizeof(ev->raw) - 1);
ev->raw[sizeof(ev->raw) - 1] = '\0';
return;
}
*colon = '\0';
char *cmd = line;
char *param = colon + 1;
if (strcmp(cmd, "LED") == 0) {
if (strcmp(param, "ON") == 0) {
ev->id = EV_LED_ON;
} else if (strcmp(param, "OFF") == 0) {
ev->id = EV_LED_OFF;
} else {
ev->id = EV_UNKNOWN_CMD;
}
} else if (strcmp(cmd, "PWM") == 0) {
ev->id = EV_PWM_SET;
ev->param = atoi(param);
} else {
ev->id = EV_UNKNOWN_CMD;
}
strncpy(ev->raw, line, sizeof(ev->raw) - 1);
ev->raw[sizeof(ev->raw) - 1] = '\0';
}
// 串口中断中调用:接收一个字节
void uart_rx_isr(uint8_t byte) {
ring_write(byte);
}
// 主循环中调用:从缓冲区取字节并做行解析
void parser_process_byte(void) {
uint8_t b;
while (ring_read(&b)) {
switch (pstate) {
case ST_IDLE:
if (b == '\r') {
pstate = ST_CR;
} else if (b != '\n') {
line_buf[0] = (char)b;
line_len = 1;
pstate = ST_CMD;
}
break;
case ST_CMD:
if (b == ':') {
line_buf[line_len++] = ':';
pstate = ST_PARAM;
} else if (b == '\r') {
line_buf[line_len] = '\0';
pstate = ST_CR;
} else if (line_len < sizeof(line_buf) - 1) {
line_buf[line_len++] = (char)b;
}
break;
case ST_PARAM:
if (b == '\r') {
line_buf[line_len] = '\0';
pstate = ST_CR;
} else if (line_len < sizeof(line_buf) - 1) {
line_buf[line_len++] = (char)b;
}
break;
case ST_CR:
if (b == '\n') {
Event ev;
memset(&ev, 0, sizeof(ev));
parse_line(line_buf, &ev);
ev_queue_push(&ev);
pstate = ST_IDLE;
} else {
pstate = ST_IDLE; // 格式错误,丢弃
}
break;
}
}
}
// ---------- 事件处理 ----------
static void handle_led_on(void) { /* 实际工程中点亮LED */ }
static void handle_led_off(void) { /* 实际工程中熄灭LED */ }
static void handle_pwm(int val) { /* 实际工程中设置PWM占空比 */ }
static void handle_unknown(const char *raw) {
printf("未知命令: %s\n", raw);
}
// 主循环事件分发
void event_dispatcher(void) {
Event ev;
while (ev_queue_pop(&ev)) {
switch (ev.id) {
case EV_LED_ON: handle_led_on(); break;
case EV_LED_OFF: handle_led_off(); break;
case EV_PWM_SET: handle_pwm(ev.param); break;
case EV_UNKNOWN_CMD: handle_unknown(ev.raw); break;
default: break;
}
}
}
// ---------- 测试入口 ----------
int main(void) {
// 模拟串口接收:一次中断一个字节
const char *test1 = "LED:ON\r\n";
const char *test2 = "PWM:128\r\n";
const char *test3 = "FOO:BAR\r\n";
for (const char *p = test1; *p; p++) uart_rx_isr((uint8_t)*p);
parser_process_byte();
event_dispatcher();
for (const char *p = test2; *p; p++) uart_rx_isr((uint8_t)*p);
parser_process_byte();
event_dispatcher();
for (const char *p = test3; *p; p++) uart_rx_isr((uint8_t)*p);
parser_process_byte();
event_dispatcher();
return 0;
}
编译与验证
在PC上编译运行:gcc -o event_parser event_uart_parser.c && ./event_parser。程序依次模拟接收三条串口命令,每条命令逐字节送入中断函数,再调用解析与分发。预期输出为:未知命令: FOO:BAR(前两条命令的LED与PWM处理函数为空操作,不产生输出)。若将handle_led_on等函数内加入printf,可观察到对应事件被正确触发。
判定标准:程序无编译错误;运行后仅输出未知命令: FOO:BAR;修改handle_led_on添加打印后,输入LED:ON能打印对应信息,证明事件解析与分发链路完整。