news 2026/9/30 9:22:32

ESP32-01S在STM32+FreeRTOS+OLED时钟项目的使用学习笔记

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32-01S在STM32+FreeRTOS+OLED时钟项目的使用学习笔记

摘要:本文介绍基于 STM32 与 ESP01s 的联网时钟校准方案。针对 STM32 RTC 因晶振限制导致时间漂移的问题,通过 ESP01s 联网获取实时时间实现自动校准,并同步获取当地天气。文章详细说明了初始化流程、AT 指令响应判断、WiFi 异常状态检测、通信帧格式设计以及基于 FreeRTOS 任务通知的收发实现,最后给出实验结果验证方案有效性。

使用STM32来做RTC时钟因为其晶振的限制在准确性上会出现逐渐漂移的情况,基于这个问题引入ESP01s来联网获取实时时间实现自动校准,同时还可以获取当地天气体现智能化的理念。

设计初始化流程如下,作为任务启动后首先执行的​​​​​​内容

AT命令对应的回复判断如下所示,ai写代码太爽辣,只用管逻辑。

int wait_response(const char *expect, uint32_t timeout_ms) { uint8_t buf[128]; TickType_t start = xTaskGetTickCount(); while((xTaskGetTickCount() - start) < pdMS_TO_TICKS(timeout_ms)) { size_t n = xStreamBufferReceive(uartStreamBuffer, buf, sizeof(buf) - 1, pdMS_TO_TICKS(100)); if(n > 0) { buf[n] = '\0'; if(strstr((char *)buf, expect) != NULL) return 1; // 收到预期响应 if(strstr((char *)buf, "ERROR") != NULL || strstr((char *)buf, "FAIL") != NULL) return -1; // 出错 } } return 0; // 超时 } // 返回值说明: // 3 -> STATUS:3 (已建立 TCP/UDP 连接) // 1 -> STATUS:1 或 5 (未连接 WiFi / 未获取 IP) // 2 -> STATUS:2 或 4 (已获取 IP 但未建立 TCP,或连接已断开) // -1 -> 模块返回 ERROR 或 FAIL // 0 -> 超时 int wait_connect_response(uint32_t timeout_ms) { uint8_t buf[256]; // 累积缓冲区 size_t total = 0; // 已累积字节数 TickType_t start = xTaskGetTickCount(); while ((xTaskGetTickCount() - start) < pdMS_TO_TICKS(timeout_ms)) { uint8_t temp[128]; size_t n = xStreamBufferReceive(uartStreamBuffer, temp, sizeof(temp), pdMS_TO_TICKS(100)); if (n > 0) { // 防止累积缓冲区溢出 if (total + n > sizeof(buf) - 1) { total = 0; // 溢出则丢弃旧数据,重新开始累积 } memcpy(buf + total, temp, n); total += n; buf[total] = '\0'; // 确保字符串函数安全 // 至少要有 4 个字节才能检查结尾 if (total >= 4) { // 1. 检查结尾是否为 OK\r\n (0D 0A) if (buf[total - 4] == 'O' && buf[total - 3] == 'K' && buf[total - 2] == '\r' && buf[total - 1] == '\n') { // 关键修改:在缓冲区中查找 "STATUS:",而不是要求它在开头 char *p = strstr((char *)buf, "STATUS:"); if (p != NULL) { // "STATUS:" 长度为 7,状态码在 p[7] // 检查 p[7] 是否在有效范围内 if ((uint8_t *)p + 7 < buf + total) { char status = p[7]; if (status == '3') return 3; if (status == '1' || status == '5') return 1; if (status == '2' || status == '4') return 2; } } } // 2. 检查 ERROR 的尾部 OR\r\n (0D 0A) if (buf[total - 4] == 'O' && buf[total - 3] == 'R' && buf[total - 2] == '\r' && buf[total - 1] == '\n') { return -1; } // 3. 检查 FAIL 的尾部 IL\r\n (0D 0A) if (buf[total - 4] == 'I' && buf[total - 3] == 'L' && buf[total - 2] == '\r' && buf[total - 1] == '\n') { return -1; } } } } return 0; // 超时 }

实际需要执行的任务内容分为两部分:定时连接状态检查、串口内容收发。设计WiFi异常状态检测执行流程如下图。异常检测功能围绕AT+CIPSTATUS指令设计,根据其返回的5种状态码代表的不同状态执行不同操作。循环执行直到连接成功。连接检查代码如下,这里留一个形参是用来判断当前是初始化时调用还是连接检查时调用,避免多余的执行。

1已连接 Wi-Fi,但未获取 IP
2已连接 Wi-Fi 并获取 IP,但未建立 TCP/UDP 连接
3已建立 TCP/UDP 连接
4TCP/UDP 连接已断开
0未连接 Wi-Fi

void connect_init(int status) { int i=status; int retry = 0; uart1_send_bytes("+++",3);//开启透传模式 wait_response("OK\r\n",1000); while(i != 3 || retry < 3) { if(i == 0) { // 1. 基础配置 uart1_send_at("ATE0"); wait_response("OK\r\n", 1500); uart1_send_at("AT+UART=115200,8,1,0,1"); wait_response("OK\r\n", 1500); uart1_send_at("AT+CWMODE=1"); wait_response("OK\r\n", 1500); i++; } if(i == 1 && retry < 3) { uart1_send_at("AT+CWJAP=\"TP-LINK_3372\",\"12345678\""); wait_response("OK\r\n",2000); i++; } if(i == 2 && retry < 3) { uart1_send_at("AT+CIPSTART=\"TCP\",\"192.168.1.103\",7755"); wait_response("OK\r\n",2000); i++; } uart1_send_at("AT+CIPSTATUS");//检查连接状态 i = wait_connect_response(3000); retry++; } uart1_send_at("AT+CIPMODE=1");//开启透传模式 wait_response("OK\r\n",1000); uart1_send_at("AT+CIPSEND");//进入透传 wait_response(">",1000); }

通信帧上设置固定发送帧10字节,如下:

字节值含义
00x55帧头
10x44帧头
20x01时间功能码
3data->get_time时间有效位,0x00无效 /0x01有效
40x02地区码功能码
5data->area_index地区码内容,0x00不更新,0x01~0x03对应地区
60x00保留
70x00保留
80x33帧尾
90x22帧尾

接收帧设计为固定16字节

字节内容说明
00x55帧头
10x44帧头
20x01时间功能码
30x00/0x01时间有效位
4~10时间数据7 字节 BCD码
110x02地点功能码
120x00/0x01地点有效位
13地点数据0x01/0x02/0x03
140x33帧尾
150x22帧尾

程序使用FreeRTOS的任务通知作为信号,通知由 API 自动管理。

void send1(void) { // Implementation for sending area code send_data.area_index = 0x01; xTaskNotify(xPrintTaskHandle, 0x01, eSetValueWithOverwrite); }
//wifi发送任务 void print_task(void *pvParameters) { uint8_t buf[100]; uint32_t notifyValue; connect_init(0); while(1) { // Receive = xStreamBufferReceive(uartStreamBuffer, buf, sizeof(buf), portMAX_DELAY); // 从 StreamBuffer 中接收数据 if(xTaskNotifyWait(0, 0, &notifyValue, portMAX_DELAY) == pdTRUE) { Data_pack(send_data); uart1_send_bytes(send_buf, 10); } vTaskDelay(pdMS_TO_TICKS(10)); } }

实验结果:如图发送两条不同的地区配置消息,消息发送效果符合预期。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 9:22:11

C++高精度算法从入门到实战:突破整型上限的大数运算模板

C里搞高精度算法&#xff0c;说白了就是绕开 int、long long 这些内置整型的长度限制&#xff0c;用数组、字符串或者 vector 把大数拆开一位一位存&#xff0c;再按手算竖式的思路模拟加减乘除。不少入门的朋友一听到“突破整型限制”就觉得是是什么高大上的数学技巧&#xff…

作者头像 李华
网站建设 2026/9/30 9:21:24

RT-Thread Studio实战指南:从图形化配置到调试排错

以前用 Keil 做 RT-Thread 开发&#xff0c;最折磨人的不是写业务代码&#xff0c;而是配环境。一个组件要开要关&#xff0c;得去 rtconfig.h 里翻宏定义&#xff0c;手动改#define&#xff1b;软件包从 GitHub 拉下来之后&#xff0c;还得自己往工程里加源码路径&#xff1b;…

作者头像 李华
网站建设 2026/9/30 9:21:13

Java基础学习与面试通关:从数据类型到HashMap底层原理

只要打开招聘软件搜“Java开发”&#xff0c;你大概率会看到二十条JD里有十八条写着“Java基础扎实”。这句话听着像废话&#xff0c;可在面试的时候&#xff0c;基础扎实和不扎实的人&#xff0c;三两轮就试出来了。我这些年以面试官身份见过不少候选人&#xff0c;简历上写满…

作者头像 李华
网站建设 2026/9/30 9:21:05

5.9GB模型仅占2.7GB显存:8G显卡自养Agent优化全记录

开年后一直在折腾自己的 Agent 项目&#xff0c;所谓"自养"就是完全自托管、自维护&#xff0c;不依赖任何云端推理服务的那种。折腾到现在&#xff0c;最让我有成就感的倒不是某个花哨功能&#xff0c;而是一个扎扎实实的数字&#xff1a;一个文件体积 5.9GB 的模型…

作者头像 李华
网站建设 2026/9/30 9:17:56

移动端AI创作工作流:豆包App两段式口令实现文案生图一条龙

移动端做内容创作最烦的是什么&#xff1f;不是没灵感&#xff0c;是灵感来了&#xff0c;你得在三个App之间来回跳——备忘录写文案、修图软件调参数、生图工具等排队。一套流程走完&#xff0c;热情凉了一半。我最近把豆包App的文案生成和生图能力串成了一条两段式的口令链路…

作者头像 李华
网站建设 2026/9/30 9:17:47

降AI率:万方AIGC检测超标4.8元快速达标完整处理方案2026

降AI率&#xff1a;万方AIGC检测超标4.8元快速达标完整处理方案2026 用嘎嘎降AI&#xff08;www.aigcleaner.com&#xff09;处理过万方AIGC检测降AI率降AI率&#xff0c;降AI率一次达标。完整降AI率方案和注意事项都在下面。 降AI率平台特点分析 不同AIGC检测平台&#xff0…

作者头像 李华