资讯详情

STM32+ESP8266天气时钟:从硬件选型到软件调试的完整实战

📅 2026/10/14 1:18:30 | 华诺云谱 👁 阅读
STM32+ESP8266天气时钟:从硬件选型到软件调试的完整实战
1. 项目缘起与整体设计思路1.1 为什么选择STM32加ESP8266这套组合做嵌入式项目的人大概都有一种执念就是希望自己焊的板子能跟真实世界产生交互。天气预报系统算是这个执念的经典落地形态——它既有传感器数据采集又有网络通信还有显示交互麻雀虽小五脏俱全。我前后做过三版天气时钟从最早的纯ESP8266单芯片方案到后来的STM32F103加ESP8266双芯片架构中间踩了不少坑也积累了一些心得。先说选型逻辑。市面上做联网天气显示的项目主控方案大致分三类纯ESP8266/ESP32单芯片、STM32加ESP8266双芯片、以及树莓派这类Linux方案。纯ESP方案成本最低一块板子十几块钱就能跑起来但它的GPIO资源和ADC精度有限如果你还想接温湿度传感器、光敏电阻、蜂鸣器引脚很快就捉襟见肘了。树莓派方案性能过剩功耗高开机慢而且成本摆在那里做一个桌面天气站有点杀鸡用牛刀的意思。STM32加ESP8266的组合恰好卡在中间。STM32F103C8T6这颗芯片72MHz主频64KB Flash20KB SRAM价格不到十块钱用来驱动屏幕、读取本地传感器、管理按键交互绰绰有余。ESP8266则专职负责网络通信通过AT指令或者透传模式跟STM32串口对接。两者分工明确各司其职这是这套架构最核心的设计哲学。注意ESP8266的供电一定要独立处理。我第一版图省事直接用STM32板子上的3.3V LDO给ESP8266供电结果WiFi连接瞬间电流冲到300mA以上LDO直接拉垮STM32跟着复位。后来换成独立的AMS1117-3.3输入输出各加470uF和100uF电容问题才解决。1.2 系统功能边界与需求拆解这个项目要解决的核心问题很明确在本地设备上实时显示当前天气和未来预报不依赖手机或电脑。具体拆解下来有这么几个功能模块。第一是网络时间同步。天气数据要跟时间绑定设备本身没有RTC电池的话每次上电时间就丢了所以必须从网络获取时间。第二是天气数据拉取包括当前温度、湿度、天气状况、未来几天预报。第三是本地数据显示用TFT彩屏或者OLED屏把信息呈现出来。第四是本地环境感知加一个DHT11或者SHT30采集室内温湿度跟室外天气形成对比。第五是交互控制至少要有按键切换显示页面或者手动刷新。这些功能模块之间是有依赖关系的。网络时间同步和天气数据拉取都依赖ESP8266的联网状态所以系统启动流程必须是先初始化串口和屏幕然后等待WiFi连接成功再进行数据请求。如果WiFi连不上系统要能降级显示本地传感器数据而不是白屏死等。这个降级逻辑在实际使用中非常重要因为路由器偶尔会抽风没有降级处理的话设备就变成砖了。1.3 数据流向与任务调度设计整个系统的数据流是这样的ESP8266从天气API获取JSON格式的数据通过串口发给STM32STM32解析JSON提取需要的字段然后刷新屏幕显示。同时STM32定时读取本地DHT11的数据也显示在屏幕上。这里有个关键设计决策JSON解析放在STM32端做还是让ESP8266解析好再传简单字符串我两种都试过。让ESP8266解析JSON的话STM32端只需要接收类似25.3,60,晴这样的简单字符串代码量小很多。但缺点是ESP8266的固件要烧录Lua或者Arduino程序灵活性差一些。让STM32解析JSON的话需要移植cJSON库占用大概几KB的Flash空间但好处是ESP8266只需要跑AT指令固件STM32端可以灵活决定要哪些字段。我最终选择了STM32端解析JSON的方案。原因有两个一是AT指令固件是ESP8266出厂自带的不需要额外烧录降低了复现门槛二是cJSON库移植一次之后以后换其他API也不用改ESP8266端。虽然STM32F103的20KB SRAM有点紧张但天气API返回的JSON通常只有1-2KB完全够用。任务调度方面我没有上RTOS用的是前后台架构。主循环里轮询几个任务标志位串口接收完成标志、定时刷新标志、按键触发标志。每个任务执行时间都很短不会互相阻塞。这种架构对于这个项目来说足够用了上FreeRTOS反而增加复杂度。2. 核心硬件选型与电路设计要点2.1 主控芯片与最小系统STM32F103C8T6是这个项目最主流的选择也就是常说的蓝板或最小系统板。它有三个USART我们至少要用两个一个跟ESP8266通信一个预留调试输出。SPI接口用来驱动TFT屏I2C接口可以接OLED或者SHT30。定时器资源丰富可以拿一个来做DHT11的微秒级延时。最小系统板上的晶振是8MHz经过PLL倍频到72MHz。如果你自己画板子注意晶振的负载电容要选对通常是20pF但具体要看晶振规格书。我遇到过晶振不起振的情况换了负载电容就好了。复位电路用标准的10K上拉加100nF电容BOOT0和BOOT1各接10K下拉电阻保证从Flash启动。电源部分最小系统板通常自带AMS1117-3.3但这个LDO的输出能力有限最大800mA左右。如果ESP8266和屏幕同时工作峰值电流可能接近500mA加上STM32本身和其他外设余量就不大了。所以建议ESP8266单独用一个LDO供电或者至少给AMS1117加散热片。2.2 ESP8266模块的选择与接线ESP8266模块市面上主要有ESP-01、ESP-01S、ESP-12F这几种。ESP-01最便宜但引脚少只有8个引脚而且早期版本没有板载LED。ESP-01S是改进版引脚定义更合理。ESP-12F是贴片封装引脚多适合自己画板子集成。我推荐用ESP-01S因为它体积小直接插在排母上就行更换方便。接线方面ESP-01S的TX接STM32的RXRX接STM32的TX这是交叉接法。CH_PD引脚要接3.3V使能否则模块不工作。GPIO0悬空或者接高电平GPIO2悬空这两个引脚在启动时决定工作模式。实操心得ESP-01S的TX引脚输出是3.3V电平可以直接接STM32的RX不需要电平转换。但STM32的TX输出也是3.3V接ESP8266的RX也没问题。如果你用的是5V的Arduino那就必须加电平转换电路了。串口波特率方面ESP8266出厂固件默认是115200但有些模块是9600。如果你发AT指令没反应先试试这两个波特率。我遇到过一批模块默认波特率是74880那是启动时的调试波特率正常通信还是115200。2.3 显示模块的对比与选型显示模块的选择直接决定了项目的视觉效果和代码复杂度。常见的有0.96寸OLED、1.3寸OLED、1.8寸TFT、2.4寸TFT这几种。0.96寸OLED分辨率128x64I2C接口只需要两根线驱动简单显示效果清晰但尺寸小能显示的信息有限。1.3寸OLED分辨率也是128x64但像素点更大看起来更舒服。TFT屏色彩丰富可以显示图标和图形但需要SPI接口驱动代码复杂一些而且刷新速度受SPI时钟限制。我最终用的是1.8寸TFT屏ST7735驱动分辨率128x160SPI接口。选择它的理由是第一可以显示彩色天气图标比OLED的单色显示直观很多第二128x160的分辨率足够分区域显示时间、温度、湿度、天气状况第三ST7735的驱动库很成熟移植工作量不大。接线方面TFT屏需要SCK、MOSI、CS、DC、RES、BLK这几根线。SCK和MOSI接STM32的SPI1CS和DC用普通GPIO控制RES接复位BLK接背光控制。如果你不想控制背光BLK直接接3.3V也行但那样就没法调亮度了。2.4 本地传感器的接入DHT11是最便宜的温湿度传感器但精度一般温度±2℃湿度±5%而且采样频率不能高于1Hz。SHT30是I2C接口精度高很多温度±0.3℃湿度±3%但价格贵一些。如果你只是做室内环境参考DHT11够用了。如果追求精度上SHT30。DHT11的数据线需要接一个4.7K到10K的上拉电阻否则读出来的数据不稳定。我试过不加电阻有时候能读出来有时候读不出来加了10K上拉之后就稳定了。供电方面DHT11工作电压3.3V到5.5V接3.3V就行。注意DHT11的时序非常敏感读取的时候要关中断否则会被其他中断打断导致读取失败。我在主循环里读DHT11的时候先__disable_irq()读完再__enable_irq()这样就不会被串口中断干扰了。3. 软件架构与关键代码实现3.1 STM32端串口通信与AT指令封装STM32跟ESP8266的通信是整个项目的核心环节。我用的方案是串口2接ESP8266串口1做调试输出。串口2的接收用中断加环形缓冲区的方式避免数据丢失。AT指令的封装我写了一个简单的状态机。发送指令后等待预期响应如果超时就重试重试三次还失败就报错。比如连接WiFi的流程是先发AT测试模块是否在线然后ATCWMODE1设置为Station模式再ATCWJAPSSID,PASSWORD连接路由器。每一步都要等OK响应。// AT指令发送与等待响应 uint8_t ESP_SendAT(char *cmd, char *expected, uint16_t timeout) { uint8_t retry 3; while(retry--) { USART2_SendString(cmd); USART2_SendString(\r\n); if(WaitForResponse(expected, timeout) SUCCESS) return SUCCESS; Delay_ms(500); } return FAIL; }这里有个细节ESP8266返回的响应里可能包含回显的AT指令本身所以WaitForResponse函数要在接收缓冲区里搜索预期字符串而不是比较整个缓冲区。我一开始就是比较整个缓冲区结果一直匹配不上后来改成strstr查找子串就好了。3.2 天气API的请求与JSON解析天气数据源我用的是一个公开的免费API返回JSON格式。请求流程是先建立TCP连接ATCIPSTARTTCP,api.example.com,80然后ATCIPSEND发送HTTP GET请求最后接收响应数据。HTTP请求头要包含Host和Connection: close否则服务器可能保持连接不返回数据。请求行是GET /weather?citybeijing HTTP/1.1后面跟Host和空行。发送的时候要注意ATCIPSEND后面要等提示符出现再发数据否则数据会丢失。// 发送HTTP请求 void SendHTTPRequest(char *city) { ESP_SendAT(ATCIPSTART\TCP\,\api.example.com\,80, CONNECT, 5000); char cmd[64]; sprintf(cmd, ATCIPSEND%d, strlen(http_request)); ESP_SendAT(cmd, , 2000); USART2_SendString(http_request); }JSON解析用cJSON库移植很简单把cJSON.c和cJSON.h加到工程里就行。解析的时候先cJSON_Parse整个字符串然后cJSON_GetObjectItem逐层提取。注意cJSON_Parse返回的指针用完要cJSON_Delete释放否则内存泄漏。// 解析天气JSON void ParseWeatherJSON(char *json_str) { cJSON *root cJSON_Parse(json_str); if(root NULL) return; cJSON *main cJSON_GetObjectItem(root, main); cJSON *temp cJSON_GetObjectItem(main, temp); cJSON *humidity cJSON_GetObjectItem(main, humidity); weather.temperature temp-valuedouble; weather.humidity humidity-valueint; cJSON_Delete(root); }实操心得cJSON解析失败最常见的原因是JSON字符串不完整。串口接收的时候如果缓冲区满了或者超时设置太短就会截断数据。我的做法是接收完数据后先检查是否包含}字符如果没有就继续等直到超时或者收到完整JSON。3.3 TFT屏幕驱动与界面布局ST7735的驱动我移植的是网上开源的库主要改了几个地方SPI初始化、GPIO定义、延时函数。驱动起来之后界面布局就是画矩形和写文字。我的界面分三个区域顶部显示时间和日期中间显示当前天气和温度底部显示室内温湿度和未来预报。时间用大号字体温度用特大号字体其他信息用小号字体。颜色方面背景用深色文字用白色和黄色天气图标用彩色。// 绘制主界面 void DrawMainUI(void) { TFT_FillScreen(BLACK); TFT_DrawString(10, 10, 2025-01-15, WHITE, BLACK, 16); TFT_DrawString(10, 30, 14:30:25, YELLOW, BLACK, 24); TFT_DrawString(10, 70, 25.3C, WHITE, BLACK, 32); TFT_DrawString(10, 110, Humidity: 60%, CYAN, BLACK, 16); TFT_DrawString(10, 130, Indoor: 22.1C 55%, GREEN, BLACK, 16); }刷新策略上我没有每次全屏刷新而是只刷新变化的区域。比如时间每秒变一次就只刷新时间区域。温度几分钟变一次就只刷新温度区域。全屏刷新太慢而且闪烁明显。3.4 本地传感器读取与数据融合DHT11的读取时序是主机拉低数据线至少18ms然后拉高20-40us然后释放总线。DHT11会响应一个80us的低电平和一个80us的高电平然后开始传输40位数据。每位数据以50us低电平开始高电平持续26-28us表示0持续70us表示1。// 读取DHT11 uint8_t DHT11_Read(void) { uint8_t data[5] {0}; // 主机发送开始信号 DHT11_IO_OUT(); DHT11_LOW(); Delay_ms(20); DHT11_HIGH(); Delay_us(30); DHT11_IO_IN(); // 等待DHT11响应 if(Wait_Level(0, 100) ! SUCCESS) return ERROR; if(Wait_Level(1, 100) ! SUCCESS) return ERROR; // 读取40位数据 for(int i 0; i 40; i) { if(Wait_Level(0, 100) ! SUCCESS) return ERROR; Delay_us(40); data[i/8] 1; if(DHT11_READ() 1) data[i/8] | 1; if(Wait_Level(1, 100) ! SUCCESS) return ERROR; } // 校验 if(data[4] data[0] data[1] data[2] data[3]) { indoor.temperature data[2]; indoor.humidity data[0]; return SUCCESS; } return ERROR; }数据融合方面我把室外天气和室内传感器数据放在同一个结构体里管理显示的时候对比展示。这样用户一眼就能看出室内外温差决定要不要开空调或者加衣服。4. 系统调试与常见问题排查4.1 ESP8266连接失败的排查思路ESP8266连不上WiFi是最常见的问题排查要按顺序来。先确认硬件接线TX和RX有没有接反CH_PD有没有接3.3V供电是否充足。然后确认串口波特率发AT看有没有OK返回。如果没有OK试试其他波特率或者检查串口线是否松动。如果AT有返回但连不上WiFi先确认SSID和密码是否正确注意大小写和特殊字符。然后确认路由器是不是2.4GHz频段ESP8266不支持5GHz。如果路由器开了MAC过滤要把ESP8266的MAC地址加进去。MAC地址可以用ATCIPSTAMAC?查询。注意ESP8266连接WiFi的时候如果密码错误会返回CWJAP:1 FAIL然后可能反复重试。这时候要发ATCWQAP断开再重新连接否则模块会卡在重试循环里。4.2 JSON解析乱码与内存溢出JSON解析乱码通常有两个原因一是串口接收的数据不完整二是字符编码问题。串口接收不完整的话检查接收缓冲区大小和超时设置。我的缓冲区是512字节超时是5秒一般够用了。如果JSON特别大可以加大缓冲区或者分片接收。内存溢出的话检查cJSON_Parse之后有没有cJSON_Delete。每次解析都会分配内存不释放的话很快就会耗尽。STM32F103只有20KB SRAMcJSON解析1KB的JSON大概需要2-3KB的堆空间所以堆大小要设置够。在启动文件里把Heap_Size改成0x800或者0x1000。// 启动文件中的堆栈设置 Heap_Size EQU 0x00000800 Stack_Size EQU 0x000004004.3 屏幕显示异常与刷新闪烁屏幕显示异常最常见的是白屏或者花屏。白屏一般是初始化失败检查SPI接线和复位时序。花屏可能是SPI时钟太快把SPI预分频调大试试。我一开始用SPI时钟18MHz花屏降到9MHz就好了。刷新闪烁是因为全屏刷新太慢人眼能感觉到。解决办法是局部刷新只更新变化的区域。另外可以在刷新前先画一个同色矩形覆盖旧内容再画新内容这样过渡更自然。4.4 常见问题速查表问题现象可能原因排查方法解决方案ESP8266无响应供电不足测量3.3V电压独立LDO供电加滤波电容AT指令返回ERROR波特率不对尝试115200和9600用ATUART_DEF修改波特率WiFi连接失败密码错误或频段不对检查SSID和密码确认2.4GHz频段重新连接JSON解析失败数据不完整打印接收缓冲区加大缓冲区延长超时屏幕白屏初始化失败检查SPI接线确认复位时序降低SPI时钟DHT11读数失败上拉电阻缺失检查数据线电压加4.7K-10K上拉电阻系统频繁复位电源纹波大示波器看3.3V波形加LC滤波加大电容时间显示不准未同步网络时间检查NTP请求定时同步加RTC电池4.5 稳定性优化的几个实操技巧第一个技巧是看门狗。STM32自带独立看门狗IWDG在主循环里定时喂狗一旦程序跑飞就能自动复位。IWDG的时钟是40kHz预分频64的话重装载值设625就是1秒超时。我设的是2秒超时足够主循环跑好几轮了。第二个技巧是串口接收加超时。ESP8266有时候会主动发一些状态信息比如WIFI CONNECTED如果接收函数一直等预期响应可能会被这些信息干扰。我的做法是接收函数带超时超时后清空缓冲区重新发指令。第三个技巧是数据缓存。天气数据不需要每次刷新都请求我设的是10分钟请求一次。请求失败的话用上一次的数据继续显示同时屏幕角落显示一个刷新图标表示数据可能不是最新的。这样即使网络暂时断了设备也不会变成砖。第四个技巧是电源监控。用STM32的ADC监测3.3V电压如果低于3.0V就在屏幕上显示低电压警告。这个功能在电池供电的场景下特别有用。5. 功能扩展与进阶玩法5.1 增加语音播报功能加一个SYN6288或者DFPlayer Mini语音模块就能实现天气播报。SYN6288是串口控制发送文本就能合成语音接线简单。DFPlayer Mini是播放MP3文件需要提前把语音文件存到SD卡里灵活性差一些但音质好。我的做法是每天早上7点自动播报一次天气按一下按键也可以手动播报。播报内容就是今天天气晴温度25度湿度60%适合外出。SYN6288的串口波特率是9600跟ESP8266错开用不同的串口就行。5.2 接入更多传感器除了温湿度还可以加气压传感器BMP280、光照传感器BH1750、空气质量传感器MQ-135。BMP280是I2C接口可以测气压和温度结合气压变化还能做简单的天气预测。BH1750测光照强度可以自动调节屏幕亮度。MQ-135测空气质量显示PM2.5或者CO2浓度。这些传感器都是I2C接口可以挂在同一条总线上用不同的地址区分。STM32的I2C1可以接多个设备代码上就是分别初始化、分别读取。5.3 数据上云与远程查看如果想在手机上看数据可以把STM32采集的数据通过ESP8266上传到云平台。常见的方案是MQTT协议连接一个公共MQTT服务器STM32发布数据手机订阅。或者用HTTP POST把数据发到自己的服务器用网页展示。MQTT的话ESP8266有AT指令支持MQTT但配置比较麻烦。更简单的方案是STM32直接发HTTP POST把数据以JSON格式发到服务器。服务器端用PHP或者Python写一个简单的接口存到数据库网页从数据库读取展示。5.4 低功耗优化与电池供电如果想让设备用电池供电低功耗优化就很重要了。STM32F103的待机模式电流可以降到2uA左右但ESP8266的待机电流也有20uA而且唤醒后连接WiFi需要几秒钟。我的做法是STM32平时在Stop模式RTC定时唤醒唤醒后给ESP8266上电连WiFi请求数据刷新屏幕然后断电继续睡。这样平均电流可以降到1mA以下用2000mAh的电池能撑好几个月。但代价是刷新频率不能太高我设的是30分钟刷新一次。屏幕也要选低功耗的OLED比TFT省电很多。实操心得ESP8266断电再上电重新连接WiFi需要3-5秒这段时间STM32要等着。如果嫌慢可以让ESP8266一直保持连接只让STM32休眠。但ESP8266保持连接的电流有70mA左右电池撑不了多久。所以还是断电方案更实际。5.5 外壳设计与桌面摆放最后说说外壳。3D打印是最方便的画一个简单的盒子前面开屏幕窗口后面开散热孔和USB孔。如果没有3D打印机用亚克力板切割也行网上有很多现成的图纸。或者直接用透明塑料盒自己开孔。摆放位置建议放在床头或者书桌上视角要舒服。屏幕亮度不要太高晚上刺眼。我加了一个光敏电阻自动调亮度晚上自动变暗白天变亮体验好很多。这个项目从第一版到现在前后改了十几次代码从最初的几百行到现在两千多行。最大的体会是嵌入式项目跟纯软件开发不一样硬件的问题往往比软件更棘手。一个虚焊的引脚能让你查一整天一个电容选错能让系统随机复位。但正是这些坑让最后跑通的那一刻特别有成就感。如果你也在做类似的项目建议先把最小系统跑通再一步步加功能不要想着一次把所有东西都焊上去。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑