3. 串口命令解析器

串口命令解析器 学习卡片

任务说明

实现一个串口命令解析器,接收形如 LED_ON、LED_OFF、PWM 128 的文本命令,解析后分发到对应处理函数。核心目标是让主程序只负责接收与分发,不包含具体业务逻辑,从而降低模块间耦合。

实现思路

采用命令表驱动方式:每个命令对应一个处理函数指针,解析器将命令字符串与命令表匹配后直接调用处理函数。新增命令时只需在表中增加一项,无需修改主循环或解析逻辑。

关键完整代码

#include <stdio.h>
#include <string.h>
#include <stdint.h>

/* 命令处理函数原型 */
typedef void (*cmd_handler_t)(const char *args);

/* 各命令的具体处理函数 */
static void led_on_handler(const char *args) {
    printf("[执行] LED_ON, 参数: %s\n", args ? args : "(无)");
}

static void led_off_handler(const char *args) {
    printf("[执行] LED_OFF, 参数: %s\n", args ? args : "(无)");
}

static void pwm_handler(const char *args) {
    int duty = 0;
    if (args && sscanf(args, "%d", &duty) == 1) {
        printf("[执行] PWM 设置占空比: %d%%\n", duty);
    } else {
        printf("[错误] PWM 需要整数参数\n");
    }
}

/* 命令表:命令名 + 处理函数 */
typedef struct {
    const char *name;
    cmd_handler_t handler;
} cmd_entry_t;

static const cmd_entry_t cmd_table[] = {
    {"LED_ON",  led_on_handler},
    {"LED_OFF", led_off_handler},
    {"PWM",     pwm_handler},
};

#define CMD_TABLE_SIZE (sizeof(cmd_table) / sizeof(cmd_table[0]))

/* 解析并分发单条命令 */
static void parse_and_dispatch(char *line) {
    /* 去除末尾换行/回车 */
    line[strcspn(line, "\r\n")] = '\0';

    /* 分离命令名与参数(空格分隔) */
    char *cmd_name = strtok(line, " ");
    char *args = strtok(NULL, "");  /* 剩余部分作为参数 */

    if (cmd_name == NULL) return;

    for (uint32_t i = 0; i < CMD_TABLE_SIZE; i++) {
        if (strcmp(cmd_name, cmd_table[i].name) == 0) {
            cmd_table[i].handler(args);
            return;
        }
    }
    printf("[错误] 未知命令: %s\n", cmd_name);
}

/* 模拟串口接收:从 stdin 逐行读取 */
int main(void) {
    char line[128];
    printf("串口命令解析器就绪,输入命令 (LED_ON / LED_OFF / PWM <数值>)\n");

    while (fgets(line, sizeof(line), stdin)) {
        parse_and_dispatch(line);
    }
    return 0;
}

编译与运行

gcc -o cmd_parser cmd_parser.c
./cmd_parser

验证方法

依次输入以下命令,观察输出是否符合预期:

  • 输入 LED_ON → 输出 [执行] LED_ON, 参数: (无)
  • 输入 PWM 128 → 输出 [执行] PWM 设置占空比: 128%
  • 输入 PWM abc → 输出 [错误] PWM 需要整数参数
  • 输入 UNKNOWN → 输出 [错误] 未知命令: UNKNOWN

以上输出为代码逻辑直接推出的预期结果。若实际输出与之一致,说明解析与分发流程正确。

架构要点

  • 主程序瘦身:main() 只负责读取行数据并调用 parse_and_dispatch(),不包含任何命令业务逻辑。
  • 低耦合扩展:新增命令时,只需编写处理函数并在 cmd_table 中增加一项,解析器与主循环代码零修改。
  • 参数透传:命令后的参数以字符串形式传给处理函数,由各函数自行解析,互不干扰。