菜鸟科技网

MCU如何使用at命令通信?

at 命令最初是 Hayes调制解调器(Modem)的命令集,因其简洁的 "AT"(Attention)前缀而得名,它已经成为一种非常通用的、用于与嵌入式设备(如MCU)进行串口通信的协议,尤其是在物联网模块、GPS模块、蓝牙模块等外设中。

MCU如何使用at命令通信?-图1
(图片来源网络,侵删)

AT命令的核心思想

at 命令的核心是一种基于文本的、同步的、命令-响应的通信模式。

  • 文本格式:命令和响应都是可读的ASCII字符串,便于调试和人工输入。
  • 命令-响应:MCU(作为从机)等待主机(如电脑、手机)发送一条命令,执行后返回一条或多条响应。
  • 同步:一条命令执行完毕并收到响应后,下一条命令才能被发送。

AT命令的基本语法

一个标准的AT命令通常遵循以下格式:

AT<Command>[=<Parameter>][<CR>]
  • ATat:命令前缀,用于唤醒MCU的命令解析器,不区分大小写。
  • <Command>:命令本身,通常是一个或两个字母,如 AT+CMD
  • [=<Parameter>]:可选的参数,可以是数值、字符串等。
  • [<CR>]:命令结束符,通常是回车符(\r, 十进制13)或换行符(\n, 十进制10),或者两者组合(\r\n)。这是最关键的部分,必须严格遵守模块的规范。

示例:

  • AT\r\n:最简单的测试命令,检查设备是否在线。
  • AT+RESET\r\n:重启设备。
  • AT+CWJAP="MyWiFi","12345678"\r\n:连接到指定的Wi-Fi网络。
  • AT+CIPSEND=0,13\r\n:准备通过连接0发送13个字节的数据。

响应格式:

MCU如何使用at命令通信?-图2
(图片来源网络,侵删)
  • 成功响应:通常以 OK
  • 错误响应:通常以 ERROR
  • 信息响应:返回具体的数据,如 AT+GMR 会返回固件版本信息。
  • 中间响应:在多行响应中,可能会有 +<Command>: ... 这样的行,最后以 OKERROR 结束。

在MCU上实现AT命令的两种主要方式

根据MCU和外设(如Wi-Fi模块)的连接方式,主要有两种实现架构。

MCU作为AT指令的“转发器” (Passthrough Mode)

这是最常见、最简单的方式,尤其适用于MCU本身处理复杂网络协议能力较弱的情况(使用一个不带Wi-Fi的MCU+一个ESP8266/ESP32模块)。

工作流程:

  1. 用户/主机 通过串口(如USB转串口)向 MCU 发送AT命令。
  2. MCU 的固件不做任何解析,直接将收到的AT命令原封不动地通过另一个硬件串口(如UART1)转发给 外设模块(如ESP8266)。
  3. 外设模块 执行命令,并将响应数据通过UART1发送回 MCU
  4. MCU 同样不做解析,直接将响应数据原封不动地转发回给 用户/主机

MCU固件的核心任务:

MCU如何使用at命令通信?-图3
(图片来源网络,侵删)
  • 初始化两个串口(UART0用于与PC通信,UART1用于与模块通信)。
  • 在一个主循环中,不断检查UART0是否有数据,有则将其写入UART1的发送缓冲区。
  • 不断检查UART1是否有数据,有则将其写入UART0的发送缓冲区。
  • (可选)实现简单的流控,避免缓冲区溢出。

优点:

  • 实现简单:MCU固件逻辑非常简单,几乎是“透明”的。
  • 开发快速:不需要在MCU上实现复杂的AT协议栈,只需处理串口数据流。
  • 利用成熟模块:直接使用模块厂商已经调试好的固件,稳定可靠。

缺点:

  • 占用MCU资源:即使逻辑简单,串口中断和数据处理也会占用一部分CPU时间和RAM。
  • 无法深度优化:MCU无法根据模块的响应做出智能决策,所有逻辑都在主机端完成。

适用场景

  • 任何需要将现有设备(如电脑、树莓派)通过串口与AT模块连接的场景。
  • MCU只是作为一个“桥梁”,不关心AT命令的具体内容。

MCU作为AT指令的“解析器” (Parser Mode)

在这种架构下,MCU本身就是核心,它直接控制一个网络接口(如以太网、Wi-Fi)或者本身就是AT协议的执行者。

工作流程:

  1. 用户/主机 通过串口向 MCU 发送AT命令。
  2. MCU 的固件接收到数据后,解析出命令、参数等。
  3. MCU 根据解析出的命令,执行相应的内部操作(调用TCP/IP栈函数、控制GPIO、读写Flash等)。
  4. MCU 生成相应的响应数据(如 OK, ERROR, 或查询结果),并通过串口发送回 用户/主机

MCU固件的核心任务:

  1. 串口接收中断:配置串口接收中断,当收到一个字节时,将其存入一个环形缓冲区。
  2. 命令解析器:在一个主循环中,检查环形缓冲区中是否有完整的命令(即是否收到了结束符\r\n)。
  3. 状态机:如果发现完整命令,则启动一个状态机来解析命令字符串。
    • 检查是否以 ATat 开头。
    • 提取命令码(如 +CWJAP)。
    • 提取参数(如 "MyWiFi""12345678")。
  4. 命令分发器:根据解析出的命令码,调用对应的处理函数(一个巨大的 if-else ifswitch-case 结构)。
  5. 响应生成器:命令处理函数执行完毕后,根据结果生成响应字符串(如 OK\r\nERROR\r\n)。
  6. 串口发送:将生成的响应字符串通过串口发送出去。

优点:

  • 高度集成:所有功能都在MCU内部,无需额外模块,成本更低,PCB更简单。
  • 性能更高:MCU可以直接响应,无需等待外部模块,响应延迟更低。
  • 资源占用更少:不需要两个独立的串口及其相关资源。
  • 灵活性强:可以自由定义和扩展AT命令集,使其完全服务于自己的应用。

缺点:

  • 开发复杂:需要自己实现一个健壮的命令解析器和命令处理器。
  • 调试困难:调试一个状态机和字符串解析器比转发数据要复杂得多。
  • 工作量大:需要为每个支持的命令编写单独的处理函数。

适用场景

  • 自定义的网关设备。
  • 需要远程控制和监控的嵌入式设备。
  • MCU本身功能强大,足以处理所有任务(如使用STM32 + LwIP)。

MCU端实现AT解析器的关键步骤(代码示例)

以下是一个伪代码/简化代码示例,展示如何在MCU上实现一个简单的AT解析器。

#include <string.h>
#include <stdio.h> // 用于 sprintf
// 定义环形缓冲区
#define RX_BUFFER_SIZE 128
char rx_buffer[RX_BUFFER_SIZE];
volatile uint16_t rx_head = 0;
volatile uint16_t rx_tail = 0;
// 串口接收中断服务函数
void USARTx_IRQHandler(void) {
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        char data = USART_ReceiveData(USART1);
        rx_buffer[rx_head] = data;
        rx_head = (rx_head + 1) % RX_BUFFER_SIZE;
        // 防止溢出
        if (rx_head == rx_tail) {
            rx_tail = (rx_tail + 1) % RX_BUFFER_SIZE;
        }
    }
}
// 检查并处理命令
void process_at_commands() {
    char *line_ptr;
    char command[16];
    char param1[32];
    char param2[32];
    // 查找完整的行 (以 \r\n 
    line_ptr = strstr(rx_buffer + rx_tail, "\r\n");
    if (line_ptr != NULL) {
        // 标记此行为已处理
        uint16_t line_length = (line_ptr - (rx_buffer + rx_tail)) + 2; // +2 for \r\n
        rx_tail = (rx_tail + line_length) % RX_BUFFER_SIZE;
        // 去掉 \r\n,方便解析
        *line_ptr = '\0';
        // 解析命令
        if (sscanf(rx_buffer + rx_tail, "AT%15[^=]=%31[^,],%31s", command, param1, param2) == 3) {
            // 处理带两个参数的命令,如 AT+CWJAP="ssid","pass"
            if (strcmp(command, "+CWJAP") == 0) {
                printf("Attempting to connect to WiFi: SSID=%s, PASS=%s\r\n", param1, param2);
                // 在这里调用实际的Wi-Fi连接函数
                // ...
                printf("OK\r\n");
            }
        } else if (sscanf(rx_buffer + rx_tail, "AT%15s", command) == 1) {
            // 处理无参数的命令,如 AT 或 AT+GMR
            if (strcmp(command, "AT") == 0) {
                printf("OK\r\n");
            } else if (strcmp(command, "+GMR") == 0) {
                printf("Firmware Version: 1.0.0\r\n");
                printf("OK\r\n");
            }
        } else {
            // 命令格式错误
            printf("ERROR\r\n");
        }
    }
}
int main(void) {
    // ... 硬件初始化 (时钟, GPIO, 串口) ...
    USART_Init(...);
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    NVIC_EnableIRQ(USART1_IRQn);
    while (1) {
        process_at_commands();
        // ... 其他任务 ...
    }
}
特性 转发器 解析器
架构 MCU <--(串口)--> AT模块 MCU <--(串口)--> Host
MCU角色 透明网桥,数据中转 协议执行器,命令处理器
开发难度
灵活性 低,受限于模块AT指令集 ,可自定义AT指令
成本/复杂度 较高(需外接模块) (单芯片方案)
性能 较低(有通信延迟) (直接响应)
适用场景 快速原型、利用现有模块 定制产品、集成度高、成本敏感型

对于初学者或快速项目,方式一(转发器) 是最佳选择,对于追求高性能、低成本和高度定制化的产品,则需要投入精力实现方式二(解析器)

分享:
扫描分享到社交APP
上一篇
下一篇