摘要:本文介绍基于 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 连接 |
4 | TCP/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字节,如下:
| 字节 | 值 | 含义 |
|---|---|---|
| 0 | 0x55 | 帧头 |
| 1 | 0x44 | 帧头 |
| 2 | 0x01 | 时间功能码 |
| 3 | data->get_time | 时间有效位,0x00无效 /0x01有效 |
| 4 | 0x02 | 地区码功能码 |
| 5 | data->area_index | 地区码内容,0x00不更新,0x01~0x03对应地区 |
| 6 | 0x00 | 保留 |
| 7 | 0x00 | 保留 |
| 8 | 0x33 | 帧尾 |
| 9 | 0x22 | 帧尾 |
接收帧设计为固定16字节
| 字节 | 内容 | 说明 |
|---|---|---|
| 0 | 0x55 | 帧头 |
| 1 | 0x44 | 帧头 |
| 2 | 0x01 | 时间功能码 |
| 3 | 0x00/0x01 | 时间有效位 |
| 4~10 | 时间数据 | 7 字节 BCD码 |
| 11 | 0x02 | 地点功能码 |
| 12 | 0x00/0x01 | 地点有效位 |
| 13 | 地点数据 | 0x01/0x02/0x03 |
| 14 | 0x33 | 帧尾 |
| 15 | 0x22 | 帧尾 |
程序使用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, ¬ifyValue, portMAX_DELAY) == pdTRUE) { Data_pack(send_data); uart1_send_bytes(send_buf, 10); } vTaskDelay(pdMS_TO_TICKS(10)); } }实验结果:如图发送两条不同的地区配置消息,消息发送效果符合预期。