STM32与ESP8266串口通信实战:从AT指令到数据透传
📅 2026/7/14 20:14:35
👁️ 阅读次数
📝 编程学习
1. 硬件连接与基础配置
第一次玩STM32和ESP8266串口通信时,我踩过的最大坑就是硬件连接。ESP8266的TX必须接STM32的RX,RX接TX,这个反接逻辑新手特别容易搞错。记得有次调试两小时没反应,最后发现是线接反了,血压直接拉满。
ESP8266的工作电压是3.3V,而STM32的IO口虽然标称兼容5V,但实测用3.3V通信更稳定。建议用AMS1117-3.3稳压模块单独给ESP8266供电,避免因电流不足导致模块频繁重启。硬件连接清单如下:
必须连接的4根线:
- ESP8266 TX → STM32 USART_RX(如PB11)
- ESP8266 RX → STM32 USART_TX(如PB10)
- ESP8266 GND → STM32 GND
- ESP8266 VCC → 3.3V电源(电流≥500mA)
关键控制引脚:
- CH_PD接3.3V(使能模块)
- RST可接STM32 GPIO做硬件复位
波特率建议统一用115200,这是ESP8266出厂默认值。初始化代码里记得开启串口接收中断,我常用的HAL库配置如下:
// STM32CubeMX生成的USART3初始化代码 huart3.Instance = USART3; huart3.Init.BaudRate = 115200; huart3.Init.WordLength = USART_WORDLENGTH_8B; huart3.Init.StopBits = USART_STOPBITS_1; huart3.Init.Parity = USART_PARITY_NONE; huart3.Init.Mode = USART_MODE_TX_RX; huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE; HAL_UART_Init(&huart3); // 开启接收中断 HAL_UART_Receive_IT(&huart3, &rx_data, 1);2. AT指令交互实战
ESP8266的AT指令就像模块的"语音控制命令",但实际用起来比想象中复杂。刚开始我天真地以为发个"AT"就能返回"OK",结果发现必须严格遵循以下规则:
- 指令格式:每条指令必须以
\r\n结尾(即回车换行) - 响应超时:等待响应建议设500ms-1s
- 大小写敏感:
AT+CWMODE有效,at+cwmode无效
这里分享一个我优化过的AT指令发送函数,加入了重试机制:
#define MAX_RETRY 3 uint8_t ESP8266_Send_AT(const char *cmd, const char *expect, uint32_t timeout) { char response[256]; uint8_t retry = 0; while(retry < MAX_RETRY) { HAL_UART_Transmit(&huart3, (uint8_t*)cmd, strlen(cmd), HAL_MAX_DELAY); HAL_UART_Transmit(&huart3, (uint8_t*)"\r\n", 2, HAL_MAX_DELAY); uint32_t tick = HAL_GetTick(); uint16_t idx = 0; while((HAL_GetTick() - tick) < timeout) { if(HAL_UART_Receive(&huart3, (uint8_t*)&response[idx], 1, 10) == HAL_OK) { if(strstr(response, expect) != NULL) { return 1; // 成功 } idx = (idx + 1) % sizeof(response); } } retry++; } return 0; // 失败 }常用AT指令速查表:
| 指令 | 功能 | 示例响应 |
|---|---|---|
| AT | 测试通信 | OK |
| AT+RST | 重启模块 | ready |
| AT+CWMODE=1 | STA模式 | OK |
| AT+CWJAP="SSID","PWD" | 连接WiFi | WIFI CONNECTED |
| AT+CIPSTART="TCP","ip",port | 建立TCP连接 | CONNECT |
3. 状态机设计与错误处理
直接轮询AT指令的方式在复杂场景下会非常脆弱。后来我改用状态机设计,稳定性提升明显。分享我的状态机实现框架:
typedef enum { ESP_INIT, ESP_RESET, ESP_CONNECT_WIFI, ESP_CONNECT_SERVER, ESP_TRANSPARENT_MODE, ESP_ERROR_HANDLE } ESP8266_State; void ESP8266_StateMachine(void) { static ESP8266_State state = ESP_INIT; static uint32_t last_operation_time = 0; switch(state) { case ESP_INIT: if(ESP8266_Send_AT("AT", "OK", 500)) { state = ESP_RESET; } break; case ESP_RESET: if(ESP8266_Send_AT("AT+RST", "ready", 2000)) { state = ESP_CONNECT_WIFI; } break; case ESP_CONNECT_WIFI: if(ESP8266_Send_AT("AT+CWJAP=\"SSID\",\"PWD\"", "OK", 10000)) { state = ESP_CONNECT_SERVER; } else if(HAL_GetTick() - last_operation_time > 30000) { state = ESP_ERROR_HANDLE; } break; // 其他状态省略... case ESP_ERROR_HANDLE: // 记录错误日志 printf("ESP Error: State=%d\r\n", state); HAL_Delay(5000); state = ESP_INIT; // 重试 break; } last_operation_time = HAL_GetTick(); }对于网络异常处理,我总结了几条实用经验:
- 心跳包机制:每30秒发送心跳数据,超时3次判定断线
- 双重超时:指令响应超时+总操作超时
- 错误分级:区分可恢复错误(如WiFi密码错误)和硬件错误
4. 数据透传优化技巧
当实现基础透传后,我发现直接转发数据会有粘包问题。后来通过添加帧头和校验解决了这个问题。具体方案:
自定义协议格式:
- 帧头:0xAA 0x55
- 长度:1字节(0-255)
- 数据:N字节
- CRC8校验:1字节
发送端代码:
void Send_With_Frame(uint8_t *data, uint8_t len) { uint8_t frame[256]; frame[0] = 0xAA; frame[1] = 0x55; frame[2] = len; memcpy(&frame[3], data, len); frame[3+len] = Calculate_CRC8(data, len); HAL_UART_Transmit(&huart3, frame, 4+len, HAL_MAX_DELAY); }- 接收端解析:
void USART3_IRQHandler(void) { static uint8_t rx_buf[256], state = 0, idx = 0, len = 0; uint8_t data = USART3->DR; switch(state) { case 0: if(data == 0xAA) state++; break; case 1: if(data == 0x55) state++; else state=0; break; case 2: len=data; idx=0; state++; break; case 3: rx_buf[idx++] = data; if(idx == len) state++; break; case 4: if(Calculate_CRC8(rx_buf, len) == data) { Process_Data(rx_buf, len); // 处理有效数据 } state = 0; break; } }实测这个方案在115200波特率下,每秒可稳定传输5KB数据。如果追求更高效率,可以考虑:
- 使用DMA+空闲中断
- 采用二进制协议替代字符串
- 启用ESP8266的MQTT功能
最后提醒几个容易忽略的细节:
- 在高温环境下,ESP8266的功耗会明显上升
- 长时间运行建议开启看门狗
- AT+CIPCLOSE后需要延迟100ms再建立新连接
编程学习
技术分享
实战经验