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(测试通信)、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 交互流程时序图
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% 的问题都出在这三样上。