MQTT+STM32+云平台+AT命令的编写
本文介绍了一个AT指令发送函数的实现方法。该函数通过串口向WiFi/蓝牙模块发送AT指令,并等待模块返回"OK"响应。函数包含参数检查、FIFO清空、指令发送、超时等待等步骤,通过strstr()函数检测响应内容,返回0表示成功,-1表示超时,-2表示参数错误,-3表示指令错误。文章详细解释了入参检查、Tick计时、字符串查找等关键概念,并分析了FreeRTOS延时机制与超时判
一、完整代码
程序设计思路
发送 AT 指令后,程序会等待模块回复,有一个超时时间,并通过返回值判断指令是否发送成功。
二、详细概念解释
/* USER CODE BEGIN Application */
// 原有函数...
// ================================================================================
// 函数名: AT_SendCmd
// 功能描述: 发送AT指令给WiFi/蓝牙模块,并等待模块回复"OK"
// 这是一个"阻塞式"函数,在收到回复或超时之前,不会返回
//
// 输入参数:
// cmd -> AT指令字符串 (例如 "AT" 或 "AT+CWMODE=1")
// timeout_ms -> 超时时间,单位毫秒 (例如 1000 表示最多等1秒)
//
// 返回值: 0 -> 成功 (收到了模块回复的 "OK")
// -1 -> 失败 (超时了,模块没理我们)
// -2 -> 失败 (你传进来的指令是空的,或者是NULL指针)
// -3 -> 失败 (模块回复了 "ERROR",说明指令格式错了)
// ================================================================================
int AT_SendCmd(char *cmd, int timeout_ms)
{
// ==============================================
// 第一步:入参检查 (Defensive Programming)
// ==============================================
// 检查指针是否为空,或者字符串长度是否为0
// 防止程序 crash(崩溃)
if(cmd == NULL || strlen(cmd) == 0)
{
return -2; // 返回错误码-2,表示参数无效
}
// ==============================================
// 第二步:定义局部变量
// ==============================================
uint8_t rec_data; // 临时变量:用来存从FIFO读出的1个字节
char rec_buf[128] = {0}; // 接收缓冲区:用来存完整的应答字符串,初始化为全0
uint16_t rec_idx = 0; // 索引:记录当前存到缓冲区第几个位置了
uint32_t start_tick = xTaskGetTickCount(); // 记录当前的系统时间(心跳数)
// ==============================================
// 第三步:清空旧的接收FIFO
// ==============================================
// 这是一个空循环,作用是把FIFO里残留的旧数据全部读出来扔掉
// 避免上一次指令的回复干扰这一次的判断
while(UART_FIFO_ReadByte(&uart1_rx_fifo, &rec_data));
// ==============================================
// 第四步:发送AT指令
// ==============================================
// 先发送你传进来的指令内容 (比如 "AT")
USART1_SendStr(cmd);
// 自动补回车换行符 (\r\n)
// 因为所有AT指令都必须以回车结尾,模块才能识别
USART1_SendStr("\r\n");
// ==============================================
// 第五步:超时循环,等待应答
// ==============================================
// 循环条件:(当前时间 - 开始时间) < 超时时间
// pdMS_TO_TICKS(): 这是FreeRTOS的宏,把毫秒转换成系统心跳数(Ticks)
while((xTaskGetTickCount() - start_tick) < pdMS_TO_TICKS(timeout_ms))
{
// 内层循环:拼命读取FIFO里的数据
// 只要FIFO里有数据,就读出来
while(UART_FIFO_ReadByte(&uart1_rx_fifo, &rec_data))
{
// 保护机制:防止存的数据太多,溢出缓冲区
// sizeof(rec_buf)-1 是为了给字符串结束符 '\0' 留位置
if(rec_idx < sizeof(rec_buf)-1)
{
rec_buf[rec_idx++] = rec_data; // 把读到的字节存入缓冲区
rec_buf[rec_idx] = '\0'; // 手动在末尾加一个结束符,保证它是合法的C语言字符串
}
// 【核心逻辑】检查是否收到 "OK"
// strstr(): C语言标准库函数,在 rec_buf 里找 "OK" 这两个字
// 如果找到了,说明指令执行成功,我们可以凯旋了!
if(strstr(rec_buf, "OK") != NULL)
{
// 调试用:把完整的应答打印到电脑串口
printf("应答: %s\r\n", rec_buf);
return 0; // 返回0,表示成功,函数直接结束
}
// 【附加逻辑】检查是否收到 "ERROR"
// 如果收到ERROR,说明指令发错了,不用等超时了,直接退出
if(strstr(rec_buf, "ERROR") != NULL)
{
printf("指令错误: %s\r\n", rec_buf);
return -3; // 返回-3,表示收到错误回复
}
}
// 【非常重要】延时10毫秒
// 如果不加这句,这个while循环会以100%的速度跑,把CPU占满
// 加了 vTaskDelay,FreeRTOS就能切换去干别的事(比如刷新OLED)
vTaskDelay(pdMS_TO_TICKS(10));
}
// ==============================================
// 第六步:超时处理
// ==============================================
// 如果代码跑到了这里,说明上面的while循环结束了,也就是时间到了还没收到OK
printf("指令超时: %s\r\n", cmd);
return -1; // 返回-1,表示超时失败
}
/* USER CODE END Application */
1. 什么是“入参检查”?
入参检查(Parameter Checking),就是在函数刚开始时,先校验用户传入的参数是否合法。
if(cmd == NULL || strlen(cmd) == 0)
这行意思是:“如果指针是空的或字符串长度为0,直接返回错误。”
2. 什么是 xTaskGetTickCount()?
3. 什么是 strstr()?
if(strstr(rec_buf, "OK") != NULL)
表示“如果在 rec_buf 里找到了 OK,说明指令成功。”
4. 为什么最后要加 vTaskDelay(10)?
三、函数流程总结
发送指令超时时间设置建议
发送指令的超时时间,应大于一次阻塞的时间,否则可能会导致任务还未得到响应就已经判定超时。
如果阻塞时间设置为10,且超时时间也是10ms,就会出现超时问题。
因为你给定超时时间10ms,但任务本身阻塞了10 Tick,必定会超时。
ESP8266 回复举例
AT\r\n ← 回显:模块先原样返回你发的内容
AT ← 回显
← 空行(两个 \r\n)
中间多了一行空行(因为有两个连续的 \r\n)。可以通过关闭 ESP32 的回显来解决这个问题。
我们还可以使用信号量(Semaphore)来保证数据的完整性。
这样就可以了,但缓冲区依然可能有两个脏数据,不过一般不影响正常使用。
四、问题解析
为什么 strstr(..., "OK") != NULL 能跳过前面的回显?
1. strstr 的原理
strstr 会在整个字符串中查找目标子串(如 "OK"),只要存在就返回指向 "O" 的指针,否则返回 NULL。
2. 实例解析
内存: 'A' 'T' '\r' '\n' '\r' '\n' 'O' 'K' '\r' '\n' '\0'
char *p = strstr(rec_buf, "OK");
此时 p 指向 'O',即 0x106,p != NULL 成立。
五、FreeRTOS延时与超时机制
1. 代码片段回顾
while((xTaskGetTickCount() - stasic_tick) < pdMS_TO_TICKS(timeout_ms))
{
// 读FIFO数据...
vTaskDelay(5);
2. 时间片与超时关系
情况1:超时时间 ≤ vTaskDelay 时间
情况2:超时时间 > vTaskDelay 时间
3. 多任务调度影响
4. 最佳实践建议
- vTaskDelay 时间建议设置为 1ms,提高响应速度。
- 超时时间建议为 vTaskDelay 的 2~5 倍,确保有足够时间处理数据。
- 也可以用 taskYIELD() 替代 vTaskDelay,只让出CPU,不阻塞固定时间。
5. 总结
超时时间 ≥ (vTaskDelay 时间 × 2) + 模块响应时间
6. 进阶优化
更多推荐










所有评论(0)