MQTT+STM32+云平台+AT命令的编写

一、完整代码

程序设计思路

发送 AT 指令后,程序会等待模块回复,有一个超时时间,并通过返回值判断指令是否发送成功。

二、详细概念解释

cs 复制代码
/* 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),就是在函数刚开始时,先校验用户传入的参数是否合法。

  1. 为什么要做?
    1. 防止程序崩溃:比如用户传了 NULL,调用 strlen(NULL) 会导致崩溃(HardFault)。
    2. 防止逻辑错误:传入空字符串 "",模块不会响应,直接报错更好。
    3. 这是一种职业习惯:假设调用者总会出错,提前处理各种异常,代码更健壮。
  2. 代码示例:

if(cmd == NULL || strlen(cmd) == 0)

这行意思是:"如果指针是空的或字符串长度为0,直接返回错误。"

2. 什么是 xTaskGetTickCount()?

  1. Tick(心跳):FreeRTOS 系统定时器每隔固定时间(如1ms)中断一次,计数值加1,这就是 Tick。
  2. xTaskGetTickCount():获取当前系统 Tick 数。
  3. 计时用法:
    1. 开始前记录:start_tick = 当前时间
    2. 过程中计算:当前时间 - start_tick
    3. 如果差值大于设定的 timeout_ms,说明超时。

3. 什么是 strstr()?

  1. 作用:在一个字符串中查找特定的"关键词"。
  2. 原型:strstr(大海, 针);
  3. 返回值
    1. 找到:返回指向关键词第一个字符的指针(非NULL)
    2. 没找到:返回 NULL
  4. 代码示例:

if(strstr(rec_buf, "OK") != NULL)

表示"如果在 rec_buf 里找到了 OK,说明指令成功。"

4. 为什么最后要加 vTaskDelay(10)?

  1. 如果不加
    1. while循环会疯狂占用CPU,导致系统卡死,其他任务得不到运行时间。
  2. 加了以后
    1. vTaskDelay(pdMS_TO_TICKS(10)) 表示"休息10毫秒,给其他任务让出CPU",系统运行更流畅。

三、函数流程总结

  1. 安检:检查参数合法性。
  2. 清场:清除上次残留的数据。
  3. 喊话:通过串口发送指令。
  4. 等待:监听接收缓冲区(FIFO)。
  5. 判断
    1. 收到 "OK" → 成功
    2. 收到 "ERROR" → 失败
    3. 超时未收到 → 超时失败

发送指令超时时间设置建议

发送指令的超时时间,应大于一次阻塞的时间,否则可能会导致任务还未得到响应就已经判定超时。

如果阻塞时间设置为10,且超时时间也是10ms,就会出现超时问题。

因为你给定超时时间10ms,但任务本身阻塞了10 Tick,必定会超时。

ESP8266 回复举例

你发送:

AT\r\n

ESP8266 的标准回复

AT\r\n ← 回显:模块先原样返回你发的内容

\r\nOK\r\n ← 应答:真正的执行结果

所以 rec_buf 里存的是:

"AT\r\n\r\nOK\r\n"

打印出来效果:

AT ← 回显

← 空行(两个 \r\n)

OK ← 真正的OK

中间多了一行空行(因为有两个连续的 \r\n)。可以通过关闭 ESP32 的回显来解决这个问题。

我们还可以使用信号量(Semaphore)来保证数据的完整性。

这样就可以了,但缓冲区依然可能有两个脏数据,不过一般不影响正常使用。

四、问题解析

为什么 strstr(..., "OK") != NULL 能跳过前面的回显?

1. strstr 的原理

strstr 会在整个字符串中查找目标子串(如 "OK"),只要存在就返回指向 "O" 的指针,否则返回 NULL。

2. 实例解析

rec_buf 可能内容如下:

内存: 'A' 'T' '\r' '\n' '\r' '\n' 'O' 'K' '\r' '\n' '\0'

地址: 0x100 ... 0x10A

执行:

char *p = strstr(rec_buf, "OK");

此时 p 指向 'O',即 0x106,p != NULL 成立。

打印 p:

printf("%s\r\n", p);

会输出:

OK

这样就跳过了前面的回显和空行。

五、FreeRTOS延时与超时机制

1. 代码片段回顾

while((xTaskGetTickCount() - stasic_tick) < pdMS_TO_TICKS(timeout_ms))

{

// 读FIFO数据...

vTaskDelay(5);

}

2. 时间片与超时关系

  1. FreeRTOS 的 vTaskDelay(n) 表示至少阻塞 n 个 Tick,不是精确延时。
  2. 设 configTICK_RATE_HZ = 1000,即 1 Tick = 1ms。

情况1:超时时间 ≤ vTaskDelay 时间

  1. 任务阻塞时间等于或大于超时时间,醒来时直接超时,可能还没来得及处理数据。

情况2:超时时间 > vTaskDelay 时间

  1. 任务有多次机会醒来处理数据,能正常获取模块回复。

3. 多任务调度影响

  1. vTaskDelay(5) 结束后,任务不一定马上运行,可能被高优先级任务抢占,实际阻塞时间可能更长。

4. 最佳实践建议

  1. vTaskDelay 时间建议设置为 1ms,提高响应速度。
  2. 超时时间建议为 vTaskDelay 的 2~5 倍,确保有足够时间处理数据。
  3. 也可以用 taskYIELD() 替代 vTaskDelay,只让出CPU,不阻塞固定时间。

5. 总结

|-------------------------|--------------|
| 现象 | 原因 |
| 超时时间 ≤ vTaskDelay → 超时 | 任务在休眠,醒来时已超时 |
| 超时时间 > vTaskDelay → 正常 | 任务有机会多次读取数据 |

核心公式:

超时时间 ≥ (vTaskDelay 时间 × 2) + 模块响应时间

按此设置,能有效避免因时间片分配导致的意外超时。

6. 进阶优化

还可以利用 FreeRTOS 的任务通知或事件组,进一步防止任务过度占用CPU资源,提升系统整体效率。

相关推荐
九思十安1 小时前
HNU2026-计算机系统-笔记 6 整数
笔记
上海云盾-小余1 小时前
服务器异常流量排查:攻击识别与快速限流处置指南
运维·服务器·网络
铁皮哥1 小时前
【力扣题解】LeetCode 25. K 个一组翻转链表
java·数据结构·windows·python·算法·leetcode·链表
宵时待雨1 小时前
linux笔记归纳5:进程控制
linux·运维·笔记
LCG元2 小时前
STM32实战:基于STM32F103的触摸屏(TSC2046)驱动与校准
stm32·单片机·嵌入式硬件
集和诚JHCTECH2 小时前
边缘计算 + 机器视觉 | BRAV-7821让农产品智能分拣真正落地
人工智能·嵌入式硬件·边缘计算
国科安芯2 小时前
抗辐射 MCU 赋能商业航天电源系统:基于 AS32S601 的高可靠能量管理控制器设计与辐照验证
stm32·单片机·嵌入式硬件·mcu·risc-v·空间计算
The Shio2 小时前
OptiByte 操练场:面向 IoT/嵌入式的协议可视化调试工具
网络·嵌入式硬件·物联网·c#·.net·业界资讯·iot
中屹指纹浏览器2 小时前
2026浏览器缓存指纹持久化溯源机制与多层级缓存隔离优化方案
经验分享·笔记