2、AT指令通信协议基础:从格式到实战

AT指令,说白了就是“Attention”。它是我们跟模组对话的通用语言。你发一条指令过去,模组回一条响应回来,就这么简单。但简单背后,藏着不少细节。

我刚开始做嵌入式通信时,觉得AT指令不就是发几个字符串嘛,有什么难的?结果第一次调ESP8266,就因为少了个回车符,折腾了一下午。嗯,从那以后,我对AT指令的格式就再也不敢马虎了。

2.1 AT指令的基本格式

一条完整的AT指令,由三部分组成:

  • 前缀:固定为 AT 或 at,大小写均可,但建议统一大写
  • 主体:命令名 + 参数,比如 +CIPSTART、+CWJAP
  • 终止符:回车换行 \r\n(CR+LF),这是最容易忽略的地方

标准格式示例:

AT\r\n                    // 测试指令,返回 OK
AT+CWMODE=1\r\n          // 设置WiFi模式为Station
AT+CIPSTART="TCP","192.168.1.100",8080\r\n  // 建立TCP连接

模组的响应也有一套固定格式。正常情况下,它会先回显你发的指令(如果回显开启),然后返回结果码。结果码最常见的就是 OK 或 ERROR。

我的习惯: 调试阶段一定开启回显(ATE1),这样你能在串口助手里看到完整的收发过程。量产固件里再关掉回显(ATE0),省带宽。

2.2 基本指令 vs 扩展指令

AT指令分两大类,我画了张图帮你快速理解:

AT指令分类体系 AT指令 基本指令(Basic Command) 扩展指令(Extended Command) AT(测试) AT+<x>=?(查询) ATE0/1(设置) AT+<x>?(读) AT+<x>=<...>(写) AT+<x>(执行) 基本指令:无参数或简单参数,如 AT、ATE0、AT+RST 扩展指令:带 + 号,支持读写查询执行四种操作

基本指令就是那些不带 + 号的,比如 AT(测试通信)、ATE0(关闭回显)、AT+RST(重启模组)。它们功能单一,参数简单。

扩展指令带 + 号,功能更丰富。它有四种操作类型:

操作类型 格式 含义 示例
测试 AT+<x>=? 查询指令支持的参数范围 AT+CWMODE=?
查询 AT+<x>? 读取当前参数值 AT+CWMODE?
设置 AT+<x>=<...> 写入参数或执行配置 AT+CWMODE=1
执行 AT+<x> 执行无参数的动作 AT+CIPSTATUS

注意: 不是所有扩展指令都支持四种操作。比如 AT+CIPSTART 只有执行模式,你没法用 AT+CIPSTART? 去查询它。用之前先看模组手册的指令表。

2.3 S3C2440 与 ESP8266 的 AT 交互实战

理论说完了,咱们来点实际的。我用 S3C2440 开发板通过 UART 控制 ESP8266,完成一次完整的 WiFi 连接和数据发送。这是物联网项目里最经典的组合之一。

2.3.1 硬件连接

S3C2440 有 3 个 UART 口,我一般用 UART0 做调试串口,UART1 专门跟 ESP8266 通信。连接方式很简单:

S3C2440 (UART1) ESP8266 说明
GPA1 (TXD1) RXD 2440 发送,8266 接收
GPA0 (RXD1) TXD 8266 发送,2440 接收
GND GND 共地,必须接
VCC 3.3V VCC 注意 ESP8266 是 3.3V 供电

避坑指南: 我曾经因为 ESP8266 的 CH_PD(EN)引脚没拉高,折腾了半小时模组都没反应。这个引脚必须接 3.3V 才能正常工作。另外,ESP8266 的 RXD 引脚容忍 5V,但 TXD 输出是 3.3V,所以 S3C2440 这边不用加电平转换。

2.3.2 代码实现

下面是一段精简的交互代码。我把它拆成几个关键函数,方便你理解流程。

// 串口发送字符串(带 \r\n 终止符)
void uart_send_string(const char *cmd) {
    while (*cmd) {
        while (!(UTRSTAT1 & 0x02));  // 等待发送缓冲区空
        UTXH1 = *cmd++;
    }
    // 发送终止符
    while (!(UTRSTAT1 & 0x02));
    UTXH1 = '\r';
    while (!(UTRSTAT1 & 0x02));
    UTXH1 = '\n';
}

// 串口接收响应(带超时)
int uart_recv_response(char *buf, int max_len, int timeout_ms) {
    int i = 0;
    unsigned long start = get_tick();
    while (get_tick() - start < timeout_ms) {
        if (UTRSTAT1 & 0x01) {  // 接收缓冲区有数据
            char c = URXH1;
            if (i < max_len - 1) {
                buf[i++] = c;
            }
            // 检测到 OK 或 ERROR 就提前返回
            if (strstr(buf, "OK") || strstr(buf, "ERROR") || strstr(buf, "FAIL")) {
                break;
            }
        }
    }
    buf[i] = '\0';
    return i;
}

// 发送AT指令并等待响应
int at_cmd(const char *cmd, char *resp, int timeout_ms) {
    uart_send_string(cmd);
    return uart_recv_response(resp, 256, timeout_ms);
}

// 主流程:连接WiFi并发送数据
void wifi_demo() {
    char resp[256];

    // 1. 测试模组是否在线
    at_cmd("AT", resp, 2000);
    // 期望响应:AT\r\n\r\nOK

    // 2. 设置Station模式
    at_cmd("AT+CWMODE=1", resp, 2000);
    // 期望响应:AT+CWMODE=1\r\n\r\nOK

    // 3. 连接WiFi热点
    at_cmd("AT+CWJAP=\"MyWiFi\",\"password123\"", resp, 10000);
    // 期望响应:WIFI CONNECTED\r\nWIFI GOT IP\r\n\r\nOK

    // 4. 建立TCP连接
    at_cmd("AT+CIPSTART=\"TCP\",\"192.168.1.100\",8080", resp, 5000);
    // 期望响应:CONNECT\r\n\r\nOK

    // 5. 发送数据
    at_cmd("AT+CIPSEND=5", resp, 3000);
    // 模组返回 > 提示符后,发送数据内容
    uart_send_string("HELLO");
    // 期望响应:SEND OK
}

关键点:

  • 每条指令必须以 \r\n 结尾,少一个都不行
  • 接收响应时一定要做超时处理,否则模组死机你的程序也会卡死
  • AT+CIPSEND 后面跟的是数据长度(字节数),发送的数据必须跟这个长度一致

2.3.3 交互流程时序图

S3C2440 ↔ ESP8266 AT交互时序 S3C2440 ESP8266 ① 发送 AT\r\n 响应:\r\nOK ② 发送 AT+CWMODE=1\r\n 响应:\r\nOK ③ 发送 AT+CWJAP="...","..."\r\n 响应:WIFI CONNECTED\r\nWIFI GOT IP\r\nOK ④ 发送 AT+CIPSEND=5\r\n 响应:\r\n> (提示符) ⑤ 发送 HELLO(5字节)

2.4 常见错误码解析

调 AT 指令,最烦的就是看到 ERROR。但别慌,模组其实给了你线索。常见的错误码和原因我整理了一下:

错误码/信息 含义 常见原因 解决办法
ERROR 通用错误 指令格式不对、参数越界 检查指令末尾是否有 \r\n,参数是否在范围内
FAIL 操作失败 连接超时、DNS解析失败 检查网络状态,重试或增加超时时间
DNS FAIL 域名解析失败 DNS服务器不可达、域名不存在 改用IP地址直连,或检查WiFi联网状态
CONNECT FAIL 连接建立失败 目标IP/端口不可达、防火墙拦截 用PC先ping一下目标地址,确认网络通
ALREADY CONNECTED 已经连接 重复调用 CIPSTART 先发 CIPCLOSE 关闭旧连接,再重新建立
SEND FAIL 数据发送失败 连接已断开、发送缓冲区满 检查连接状态,等待缓冲区空闲再发
+IPD 数据乱码 接收数据异常 波特率不匹配、数据长度解析错误 确认双方波特率一致,检查 +IPD 后的长度值

我曾经踩过的坑: 有一次 ESP8266 一直返回 ERROR,我查了半天指令格式,最后发现是 WiFi 密码里有个特殊字符 # 没转义。ESP8266 的 AT 固件对某些特殊字符敏感,建议密码只用字母和数字。如果必须用特殊字符,查一下对应模组的手册看是否需要 URL 编码。

还有一个常见问题:模组返回 busy p... (busy processing)。这说明模组正在处理上一条指令,还没忙完。这时候你再发新指令,它就会忽略。解决办法很简单——等收到上一条指令的响应后,再发下一条。我习惯在每条指令之间加一个 100ms 的延时,虽然粗暴但有效。

调试小技巧: 用串口助手手动发指令调试时,记得勾选“发送新行”或手动加上 \r\n。我见过太多人直接在发送框里打 AT 就点发送,然后抱怨模组没反应。嗯,模组其实在等你那个回车符。

最后说一句:AT 指令调试,耐心是第一位的。遇到 ERROR 别急着怀疑模组坏了,先检查格式、参数、时序。90% 的问题都出在这三样上。