STM32与ESP8266串口通信实战:从AT指令到数据透传

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",结果发现必须严格遵循以下规则:

  1. 指令格式:每条指令必须以\r\n结尾(即回车换行)
  2. 响应超时:等待响应建议设500ms-1s
  3. 大小写敏感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=1STA模式OK
AT+CWJAP="SSID","PWD"连接WiFiWIFI 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(); }

对于网络异常处理,我总结了几条实用经验:

  1. 心跳包机制:每30秒发送心跳数据,超时3次判定断线
  2. 双重超时:指令响应超时+总操作超时
  3. 错误分级:区分可恢复错误(如WiFi密码错误)和硬件错误

4. 数据透传优化技巧

当实现基础透传后,我发现直接转发数据会有粘包问题。后来通过添加帧头和校验解决了这个问题。具体方案:

  1. 自定义协议格式

    • 帧头:0xAA 0x55
    • 长度:1字节(0-255)
    • 数据:N字节
    • CRC8校验:1字节
  2. 发送端代码

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); }
  1. 接收端解析
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功能

最后提醒几个容易忽略的细节:

  1. 在高温环境下,ESP8266的功耗会明显上升
  2. 长时间运行建议开启看门狗
  3. AT+CIPCLOSE后需要延迟100ms再建立新连接