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资源,提升系统整体效率。

相关推荐
石一峰69938 分钟前
深入理解 Linux 中断三层机制与 1-Wire 时序锁原理
linux·运维·服务器
:-)1 小时前
算法-归并排序
java·开发语言·数据结构·算法·排序算法
无限码农2 小时前
Linux上通过cmake编译uchardet
linux·运维·服务器
ACP广源盛139246256733 小时前
IX8024@ACP# 搭配此芯 AI 服务器 + 爱芯元智产品完整方案
大数据·运维·服务器·人工智能·分布式·嵌入式硬件
ShallWeL4 小时前
NVIDIA Jetson 早期系列演进
人工智能·嵌入式硬件·边缘计算·智能硬件
Hiyajo pray5 小时前
SDN 访问控制性能调优方法
服务器·网络·网络安全
Jerry5 小时前
LeetCode 101. 对称二叉树
算法
huainingning6 小时前
交换机通过ftp下载文件或者传输文件到ftp服务器
运维·服务器·网络
hengcaib6 小时前
防封号电销外包公司怎么选合规线路与风控核查方法
笔记
可编程芯片开发6 小时前
基于MPPT最大功率跟踪的离网光伏发电系统Simulink建模与仿真
算法