在 STM32 与 ESP8266 的串口通信中,网络波动或模块忙乱常导致 AT 指令无响应。为了保证系统的稳定性,必须设计一套完善的超时检测与重传机制。以下是详细的解决方案,包含状态机设计、代码实现及调试建议。
一、 问题分析与设计思路
AT 指令通信通常采用"请求-响应"模式。若发送指令后未在规定时间内收到预期的回复(如 OK 或 ERROR),则视为通信超时。
核心设计逻辑:
- 发送指令:通过 UART 发送 AT 指令。
- 开启计时:启动定时器或利用系统滴答定时器(SysTick)进行倒计时。
- 接收解析:在超时时间内持续检查接收缓冲区,判断是否包含期望的回复字符串。
- 超时判定 :
- 成功:收到预期回复,清除超时标志,执行下一步逻辑。
- 失败 :倒计时结束仍未收到,触发重传机制。
- 重传限制:设置最大重试次数(如 3 次),超过次数后报错并复位模块,防止死循环。
二、 软件状态机与代码实现
以下代码基于 STM32 标准库,展示如何封装一个带有超时和重传功能的 AT 指令发送函数。
- 定义全局变量与宏
c
#include "stm32f10x.h"
#include <string.h>
#include <stdio.h>
#define ESP8266_USART USART2 // 假设使用串口2
#define MAX_RETRY_COUNT 3 // 最大重试次数
#define TIMEOUT_MS 2000 // 超时时间 2000ms
// 简单的延时函数(需根据实际环境实现)
void Delay_ms(uint32_t ms) {
uint32_t i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 7990; j++); // 粗略延时,建议使用SysTick
}
// 串口发送字符串
void USART_SendString(USART_TypeDef* USARTx, char *str) {
while(*str) {
while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
USART_SendData(USARTx, *str++);
}
}
// 接收缓冲区及相关标志
volatile uint8_t rx_buffer[512];
volatile uint16_t rx_index = 0;
volatile uint8_t rx_done = 0; // 接收完成标志
- 封装核心 AT 指令发送函数(带重传)
这是解决超时重传问题的核心函数。它利用指针操作在接收缓冲区中查找期望的回复字符串。
c
/**
* @brief 发送AT指令并等待回复(支持超时与重传)
* @param cmd: 发送的AT指令字符串
* @param resp: 期望的回复字符串(如 "OK", "SEND OK")
* @param timeout: 超时时间(毫秒)
* @retval 1: 成功收到回复; 0: 失败(超时且重试次数耗尽)
*/
uint8_t ESP8266_SendCmd(char *cmd, char *resp, uint16_t timeout) {
uint8_t retry_count = 0;
uint16_t tick_start = 0; // 模拟时间戳计数
uint16_t current_tick = 0;
while (retry_count < MAX_RETRY_COUNT) {
// 1. 清空接收缓冲区
rx_index = 0;
memset((void*)rx_buffer, 0, sizeof(rx_buffer));
rx_done = 0;
// 2. 发送指令
USART_SendString(ESP8266_USART, cmd);
// 3. 等待响应(模拟超时检测)
// 注意:实际项目中建议使用 HAL_GetTick() 或 SysTick 计时
for (current_tick = 0; current_tick < timeout; current_tick++) {
Delay_ms(1); // 1ms 延时
// 检查是否收到数据(这里假设串口中断负责填充 rx_buffer)
// 如果使用查询方式,这里需要调用 USART_ReceiveData
// 4. 判断缓冲区是否包含期望的回复
if (strstr((const char*)rx_buffer, resp) != NULL) {
return 1; // 成功
}
// 可选:如果收到 "ERROR" 或 "FAIL" 可立即重试,不必等超时
if (strstr((const char*)rx_buffer, "ERROR") != NULL) {
break;
}
}
// 超时或收到错误,进行重试
retry_count++;
printf("Retry %d/%d...\r
", retry_count, MAX_RETRY_COUNT);
Delay_ms(500); // 重试前稍作等待
}
return 0; // 失败
}
三、 应用实例:连接 WiFi
利用上述封装好的函数,可以非常稳健地执行连接 WiFi 的操作。即使网络环境差导致第一次 AT 指令丢失,程序也会自动重试。
c
void ESP8266_Connect_Wifi(void) {
// 1. 关闭回显,简化接收解析
if (ESP8266_SendCmd("ATE0\r
", "OK", 500) == 0) {
printf("ATE0 Failed!\r
");
return;
}
// 2. 设置 Wi-Fi 模式为 Station
// ESP8266_SendCmd 内部会自动处理超时和重试
if (ESP8266_SendCmd("AT+CWMODE=1\r
", "OK", 1000) == 0) {
printf("Set Mode Failed!\r
");
return;
}
// 3. 连接目标路由器 (SSID: TPLINK, PWD: 12345678)
// 连接过程较慢,建议超时时间设长一些,如 10秒-20秒
// 指令: AT+CWJAP="SSID","PASSWORD"
if (ESP8266_SendCmd("AT+CWJAP=\"TPLINK\",\"12345678\"\r
", "WIFI GOT IP", 20000) == 0) {
printf("Connect WiFi Failed!\r
");
// 这里可以添加复位 ESP8266 的逻辑
} else {
printf("WiFi Connected Successfully!\r
");
}
}
四、 进阶优化建议
在实际工程中,除了简单的超时重传,还需要注意以下几点以提高系统的鲁棒性:
| 优化点 | 说明 | 实施建议 |
|---|---|---|
| 接收中断处理 | 不要在主循环中死等查询接收,应使用串口中断(USART_IT_RXNE)将数据存入 rx_buffer。 |
在中断服务函数中置位 rx_done 标志或直接更新缓冲区索引。 |
| DMA 传输 | ESP8266 透传大数据时,CPU 频繁处理中断会影响效率。 | 使用串口 DMA 接收,利用空闲中断(IDLE)判断一帧数据接收完毕。 |
| 模块复位 | 若连续多次 AT 指令无响应,说明模块可能死机。 | 在重试次数耗尽后,拉低 ESP8266 的 RST 引脚 100ms 进行硬件复位。 |
| 心跳检测 | 长时间运行可能发生静默断连。 | 定时发送 AT 指令进行心跳检测,无回复则触发重连流程 。 |
通过这种"封装+重试+复位"的层级防御策略,可以有效解决 STM32 与 ESP8266 通信中的不稳定问题,确保智能农业或其他 IoT 系统长期在线。