4. 事件驱动架构之串口命令解析

任务定义

本案例实现一个基于事件驱动的串口命令解析器。系统通过串口中断接收字节,将完整命令识别为事件并投递到事件队列,主循环从队列取出事件并分发处理。命令格式定义为:CMD:PARAM\r\n(冒号分隔命令与参数,回车换行结束)。目标命令为LED:ON、LED:OFF、PWM:数值三种,其余输入视为未知命令事件。

事件驱动架构设计

核心思路是将“接收字节”与“业务处理”解耦:串口中断只负责把字节放入环形缓冲区并置位标志,不执行耗时操作;主循环检测到完整行后解析并生成事件,通过事件队列传递给处理函数。这样既保证中断响应及时,又使业务逻辑可独立测试。

串口中断 接收单字节 写入环形缓冲 环形缓冲区 256字节 FIFO 命令解析器 提取CMD与PARAM 生成事件 事件队列 最多8个事件 FIFO 事件分发器 switch-case 调用处理函数 LED_ON / LED_OFF / PWM_Set / Unknown_Cmd 中断上下文 主循环上下文

代码实现


// 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能打印对应信息,证明事件解析与分发链路完整。