简介:面向嵌入式与物联网课程设计场景,这份资源以STM32F103ZET6为主控、ESP8266为无线模块、LabVIEW作上位机,构成一套完整的温度采集与远程传输方案,适合需快速完成课设或入门IoT开发的学习者。压缩包共288个文件,大小约2.03MB,以h和c源码为主体,包含STM32标准外设库的timer、flash、rcc、adc等驱动模块,同时附有o、elf、hex、map等编译产物与工程配置,便于直接分析或重新编译;另有LabVIEW的vi文件及PDF说明,可辅助理解上位机界面和联调方式。目前已有2503人学习下载,参考价值得到验证。资源完整呈现了从温度传感器读取、STM32串口转发、ESP8266 Wi-Fi发送,到LabVIEW实时显示的完整数据链路,并覆盖硬件连接、固件编写、AT指令配置、UI设计等关键环节,能帮助学习者梳理嵌入式、无线通信与可视化软件开发之间的协作关系,也为课设答辩和二次开发提供了可复用的工程模板。
1. 先搞清楚这套采集链路:ESP8266、STM32ZET6、LabVIEW 各自该干嘛
写 STM32 课设最怕的不是传感器驱动,而是三块板子凑在一起谁都不理谁。这套 ESP8266+STM32ZET6+LabVIEW 温度采集方案,链路很清晰:STM32 读 DS18B20 → 串口发给 ESP8266 → WiFi TCP 上传 → LabVIEW 显示实时温度曲线。听起来三段式,实际调起来大部分时间耗在 ESP8266 的 AT 应答、模块瞬间供电、上位机粘包三个地方。我拆过的几套同款课设,凡是在最后一周疯狂熬夜的,问题基本都出在这些环节。下面按硬件接线、下位机代码、LabVIEW 上位机、联调避坑四段展开,给能直接抄的参数和代码,适合做 STM32 课设或物联网课程作业的读者,也适合想快速搭一套“板子采集 + WiFi 传输 + PC 显示”链路的同学。
2. 硬件接线与通信协议:电平匹配、引脚分配与独立供电
很多人拿到板子第一件事就是怼杜邦线,先把数据线接上,再接电源,结果 ESP8266 反复重启、串口打印乱码。硬件接线看着简单,实际上是整个项目里最容易埋雷的环节。先把接线和供电理清楚,后面写代码会顺畅很多。
2.1 引脚分配:串口、单总线、ADC 各归各位
温度传感器选择上,这套方案用的是 DS18B20。它属于单总线器件,一根数据线既能供电通讯又能挂多个节点,12 位分辨率下测温精度 0.0625℃,范围 -55℃ 到 +125℃,课设和日常环境监测完全够用。选它的另一个理由是代码省事,不需要额外的 ADC 参考电压计算。
通信资源分配上,我一般把 USART1 给 ESP8266,USART2 留作调试打印。USART1 挂在 APB2 高速总线上,波特率上到 115200 也稳定;调试信息走 USART2,可以避免日志和 AT 应答混在一条串口线上,排查问题的时候不用猜哪段是模块回的、哪段是自己打印的。
| 外设/信号 | STM32 引脚 | 对端 | 接线说明 |
|---|---|---|---|
| DS18B20 DQ | PB0 | DS18B20 中间引脚 | 开漏输出,外接 4.7kΩ 上拉到 3.3V |
| ESP8266 RXD | PA9(USART1_TX) | ESP8266 RXD | 注意是交叉接,不是 TX 对 TX |
| ESP8266 TXD | PA10(USART1_RX) | ESP8266 TXD | 交叉接线 |
| GND | GND | ESP8266 GND | 必须共地,否则电平参考不一致 |
| CH_PD / EN | 3.3V | ESP8266 EN | 模块使能脚,悬空会导致不开机 |
接线顺序有个血泪经验:先接电源和地,再接信号线。ESP8266 上电瞬间如果信号线已经插好而电源还没到位,STM32 的串口 TX 引脚会通过模块内部的 ESD 保护二极管给它反向供电,导致模块上电时序混乱,表现为偶尔能连上 WiFi、偶尔完全无响应,非常像玄学。先供好电再插信号线,能排除掉这一大类问题。
选型上,ESP-01S 够用,但要注意它没有板载稳压,买回来最好配一个面包板适配器,把引脚引出来再接。ESP-12F 引脚多、天线性能好,但对课设来说接线反而变多,没必要。无论哪种模块,上电前确认 CH_PD 被拉高到 3.3V 是必须的。
2.2 供电与电平:3.3V 直连电源线为什么会翻车
ESP8266 在 WiFi 发射瞬间电流能冲到 200 到 300mA,而且是脉冲式的。STM32 板载的 3.3V LDO 设计时只考虑了 MCU 的功耗,余量很小,模块一发射,电压瞬间跌落,表现为模块正在连接路由器突然重启,或者串口打印一堆乱码。这个坑非常经典。
解决方案是给 ESP8266 独立供电,常见做法是用一个独立 3.3V LDO 或者 5V 转 3.3V 的降压模块,电源从 USB 或外部电源取,GND 和 STM32 共地。同时模块 VCC 引脚附近就近并一个 100uF 电解电容和一个 0.1uF 陶瓷电容,一个管低频跌落,一个管高频噪声,加完这两个电容后很多随机重启就消失了。
电平匹配方面,STM32 和 ESP8266 都是 3.3V 逻辑,TXD 和 RXD 可以直连,不需要电平转换。如果你的板子是 5V 单片机,那 RX 线上必须加电阻分压,否则长期使用会烧模块。判断电平是否匹配,可以万用表量模块 RX 引脚的静态电压,DVM 显示在 3.0V 以上基本没问题。
注意:给 ESP8266 上电时先确认 CH_PD(EN)引脚被拉高到 3.3V,否则模块串口一点反应都没有,很多人误以为模块坏了,其实是使能脚没接。
3. 下位机代码:DS18B20 读取与 ESP8266 透传封装
硬件接好后写代码,我建议顺序是先驱动 DS18B20,再做 ESP8266 透传,最后把两者拼起来。不要一上来就写大循环,那样出问题没法定位。STM32 侧用 HAL 库,代码层次清楚,方便移植到其他型号。
3.1 DS18B20 驱动:单总线时序与 85℃ 陷阱
DS18B20 的单总线协议才是这个传感器真正的复杂度所在。一根 DQ 线上同时承担复位、写、读三种时序,所有延时都在微秒级别。普通 GPIO 模拟即可,不需要占用定时器。读一次温度分四步:复位总线并检测存在脉冲 → 跳过 ROM → 启动温度转换 → 再次复位 → 读暂存器前两个字节。
// ds18b20.c —— 读取一次温度,返回放大 10 倍的整数值,0xFFFF 表示错误 uint16_t ds18b20_read_temp(void) { uint8_t data[2] = {0}; // 1. 复位总线,等待从机应答 if (ds18b20_reset() != HAL_OK) return 0xFFFF; // 2. 跳过 ROM(单点采集不需要匹配地址),启动温度转换 ds18b20_write_byte(0xCC); ds18b20_write_byte(0x44); HAL_Delay(750); // 12 位分辨率转换时间最长 750ms // 3. 再次复位,跳过 ROM,读暂存器 ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); data[0] = ds18b20_read_byte(); data[1] = ds18b20_read_byte(); // 4. 拼装原始值并转成摄氏度,放大 10 倍保留一位小数 int16_t raw = (data[1] << 8) | data[0]; if (data[1] & 0x80) // 负温度补码,这里直接标记错误 return 0xFFFF; return (uint16_t)(raw * 0.625f * 10); }函数内部的四个步骤对应单总线的完整操作序列。raw是 16 位有符号数,低 12 位是温度数据,每个 LSB 对应 0.0625℃,所以乘以 0.625 是 0.0625 × 10 的结果,直接把温度放大 10 倍返回。上位机收到后除以 10 就可以显示,避免在串口里传浮点数。
ds18b20_write_byte和ds18b20_read_byte内部实现没有展开,这两个函数就是单总线的核心时序:写 1 是拉低 1us 后释放,写 0 是拉低 60us,读时序是拉低 1us 释放后在 15us 内采样电平。所有微秒延时不能直接用HAL_Delay,因为它是毫秒级函数,最短只能等 1ms,所以必须自己写一个基于系统时钟或空循环的微秒延时。这个细节很多新人会栽跟头。
如果你发现温度永远显示 85℃,先别怀疑代码,这是 DS18B20 上电复位后暂存器的默认值 0x0550,对应 85℃。读到这个值说明总线上压根没有器件应答,通常就是上拉电阻没接、DQ 接错引脚、或者复位时序的拉低时间不足 480us。
3.2 ESP8266 初始化与透传:AT 指令怎么封装才不卡死
ESP8266 官方 AT 固件下,模块状态切换靠串口发指令。初始化顺序有一个容易搞反的地方,很多资料写成“先开透传再建立连接”,实际必须先AT+CIPSTART成功后再进入透传模式,否则AT+CIPMODE=1会返回 ERROR。
先说本次项目里与 TCP 服务器建立连接的完整指令序列,PC 端在局域网内开一个 TCP Server 监听某个端口,ESP8266 作为客户端主动连接:
| 目的 | 指令 | 预期返回 | 超时建议 |
|---|---|---|---|
| 测试模块 | AT | OK | 500ms |
| 设为 Station 模式 | AT+CWMODE=1 | OK | 500ms |
| 连接路由器 | AT+CWJAP="ssid","password" | WIFI GOT IP | 10000ms |
| 建立 TCP 连接 | AT+CIPSTART="TCP","192.168.1.100",8080 | CONNECT OK | 5000ms |
| 开启透传 | AT+CIPMODE=1 | OK | 500ms |
| 进入透传发送状态 | AT+CIPSEND | > | 1000ms |
为什么强调用透传模式而不是每条数据都发一遍 AT 指令?透传建立后,串口收到的所有数据会被直接转发到 TCP 连接的另一端,周期性温度采集正好是这种持续数据流场景。如果每条数据都走“CIPSTART → CIPSEND → 等待 → 断开”,连接开销大,路由器侧稍微有点波动就容易断链。
// esp8266.c —— 发送一条 AT 指令并等待期望关键字返回,带超时保护 uint8_t esp_at_send(const char *cmd, const char *expect, uint32_t timeout_ms) { uint8_t buf[64] = {0}; uint16_t idx = 0; uint32_t start = HAL_GetTick(); // 清空串口接收缓冲,避免上次残留的回复干扰匹配 __HAL_UART_CLEAR_OREFLAG(&huart1); __HAL_UART_CLEAR_NEFLAG(&huart1); // 发送指令,注意命令后面必须带 \r\n HAL_UART_Transmit(&huart1, (uint8_t *)cmd, strlen(cmd), 200); // 逐字节累加,直到出现期望关键字或超时 while (HAL_GetTick() - start < timeout_ms) { if (HAL_UART_Receive(&huart1, &buf[idx], 1, 100) == HAL_OK) { if (strstr((char *)buf, expect) != NULL) return 1; idx++; if (idx >= sizeof(buf) - 1) idx = 0; } } return 0; }HAL_UART_Receive每次接收一个字节并等待最多 100ms,返回后把字节累积进buf,然后用strstr判断是否出现期望的关键字。buf只有 64 字节,对 AT 指令的回复来说足够,因为 AT 固件的返回不像 HTTP 那样长。timeout_ms根据指令不同区别对待:测试指令给 500ms,连接 WiFi 要 10 秒,建立 TCP 给 5 秒,这样程序设计不会因为某条指令慢而卡住整个流程。
透传模式的数据发送更简单,直接往串口写温度帧即可:
// 透传模式下发送一帧温度数据,帧格式: $T1,25.6# char frame[24]; snprintf(frame, sizeof(frame), "$T1,%.1f#", temp_c); HAL_UART_Transmit(&huart1, (uint8_t *)frame, strlen(frame), 200);帧结构里,$是帧头,T1表示通道 1 的温度数据,25.6是实际温度,#是帧尾。上位机解析时只取$到#之间的内容,天然免疫串口粘包问题。snprintf里格式化用的%.1f保证浮点数只输出一位小数,长度可控。
退出透传模式的指令是+++,注意这三个加号前后各 500ms 内不能发送其他串口数据,这是 AT 固件内部的保护机制,防止普通数据流里出现+++误触退出。项目里如果需要恢复 AT 指令模式,必须把这个时序考虑进去。
4. LabVIEW 上位机:VISA 串口读取与帧解析
LabVIEW 是图形化编程,不需要像 C 语言那样手写收发函数,但背后的串口阻塞机制、缓冲区机制和帧同步逻辑是一样的。这套上位机要做三件事:从串口读字节流 → 按帧解析出温度 → 实时画曲线并报警。
4.1 用 VISA 搭串口读取循环:实时曲线先跑起来
LabVIEW 里串口通信统一走 VISA,它屏蔽了底层驱动差异。搭建一个最小读取循环的步骤如下:
- 前面板放置一个波形图表(Waveform Chart),纵轴范围设成 20 到 40℃,横轴自动滚动显示时间。
- 程序框图放置 VISA Configure Serial Port,配置串口资源名、波特率、数据位、停止位和校验。
- 放置 While 循环,循环内先读取“Bytes at Serial Port”属性节点,判断当前缓冲区有多少字节。
- 如果字节数大于 0,调用 VISA Read 读取实际的字节数,避免读空缓冲区。
- 将返回的字节数组转成字符串,送入解析逻辑。
- 循环内加 100ms 延时,减少 CPU 占用。
- 循环结束后调用 VISA Close 释放串口。
VISA 的配置参数按这张表来设置,和 STM32 侧的串口参数一一对应,任何一个不匹配都会出现乱码。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 波特率 | 115200 | 与 STM32 的 USART1 保持一致 |
| 数据位 | 8 | ESP8266 透传默认 8 位 |
| 校验 | None | 帧尾#已经承担完整性校验 |
| 停止位 | 1 | 标准配置 |
| VISA 超时 | 1000 ms | 防止 VISA Read 无限期阻塞 |
这里有一个新手很容易踩的卡死问题:VISA Read 默认按指定字节数读取,如果缓冲区里没有足够的字节,它会一直等到超时。所以读取前必须用属性节点查询实际字节数,只读真实存在的数据。很多人在 LabVIEW 里直接用 VISA Read 读一个固定长度,结果界面一运行就假死,原因就在这里。
波形图表用 Chart 而不是 Graph,Chart 自带时间轴自动滚动,适合实时温度曲线;Graph 更适合一次性显示整段历史波形。如果做课设答辩,展示 Chart 的实时更新效果会更直观。
4.2 帧解析与超限报警:串口字节流变成温度线
串口读回来的数据是字节流,接收时机不同,可能一次读到多帧,也可能一帧被拆成两次收到,所以解析时必须用帧头$和帧尾#做截取,而不是按固定位置切字符串。LabVIEW 里用正则表达式“匹配模式”函数,模式写成\$T\d+\.?\d*#,注意$和#都是正则里的特殊字符,需要转义。
匹配成功后,中间部分类似T1,25.6,用“扫描字符串”函数按逗号拆分,得到通道号和温度值。拆分后的数值做一次“除以 10”,因为下位机发送时已经放大 10 倍。这一步做完,把数值送进波形图表的输入端子,曲线就会滚动起来。
超限报警用条件结构实现:数值大于阈值时,前面板的报警指示灯从绿色变红色,同时可以在数值显示控件上把字体颜色改成红色。阈值不要写死在程序里,前面板放一个数值输入控件,答辩时可以现场改阈值演示。对于 DS18B20 典型应用,通道 1 设 30℃,通道 2 设 45℃,具体根据自己的测温场景调。
另外提一个联调的小技巧:第一次跑通串口链路时,先用串口助手软件代替 LabVIEW 接收数据。在 PC 上打开串口助手,波特率设成 115200,如果能看到连续的$T1,25.6#这样的数据流,说明 STM32 和 ESP8266 链路没问题,再切到 LabVIEW 排查解析逻辑,避免两头同时怀疑。
5. 联调避坑与排查:五个真实翻车现场
这个项目单看每一段都不难,但联调时坑特别集中。下面五条是我在调同款方案时反复遇到过的,按现象、原因、解决三步写清楚,每条都能对上号。
5.1 下位机侧:三个最先遇到的坑
坑 1:AT 指令卡死或返回 ERROR。
现象:esp_at_send函数一直超时,或者AT+CIPMODE=1返回 ERROR。
原因:最常见的是 TCP 连接没有建立成功就切透传,AT 固件要求先连接后透传;其次是串口缓冲区里残留上一次的回复,关键字匹配被干扰;有些模块出厂不是 AT 固件而是其他固件,AT 指令根本不存在。
解决:先清空串口缓冲再发指令;用串口助手逐条发送 AT 指令,看模块原生返回内容,不要依赖程序里的封装函数;确认固件版本对应的指令集。如果模块发AT没有响应,进入下载模式重新刷 AT 固件,刷完再做链路测试。
坑 2:WiFi 连上就重启,串口打印乱码。
现象:AT+CWJAP返回WIFI GOT IP后,没几秒模块就重启,日志断断续续。
原因:供电不足。WiFi 发射瞬间电流上升,VCC 电压跌落超过模块复位门槛,模块自我保护重启。
解决:给 ESP8266 独立供电,GND 与 STM32 共地,VCC 旁边并 100uF 和 0.1uF 电容。用示波器看 VCC 波形,如果发射瞬间跌落超过 200mV,还要加大电容或者换输出电流更大的稳压芯片。
坑 3:DS18B20 温度恒为 85℃。
现象:无论怎么测温,上位机都显示 85.00℃。
原因:85℃ 是器件上电后的默认值,复位时序没有真正操作到总线,或者总线上根本没有器件应答。
解决:量 DQ 引脚空闲电压,正常应为 3.3V,如果是 0V 说明上拉电阻没接。再看复位时序,拉低时间要大于 480us,释放后等待 60us 再读应答。注意微秒延时不能用HAL_Delay,要自己写延时函数或直接用 SysTick 计微秒。
5.2 上位机与联调侧:两个容易被忽略的坑
坑 4:LabVIEW 一运行界面就卡死,点停止没反应。
现象:前面板转圈,程序无法正常退出。
原因:VISA Read 在串口缓冲区没有足够字节时阻塞等待,同时循环里没有延时,CPU 被占满,界面响应不过来。
解决:用“Bytes at Serial Port”属性节点先查数据量,有数据才读;循环内加 100ms 延时;VISA Configure Serial Port 的超时参数设成 1000ms。这三处改完,卡死问题基本消失。
坑 5:波形图跳变到离谱值,或者字符串控件出现乱码。
现象:温度显示 100℃ 以上,或者面板上一堆不可见字符。
原因:串口两侧波特率不一致;帧尾丢失导致解析错位;进入透传模式后模块把OK、>这些 AT 回复也转发了出来,混进数据流。
解决:统一两端波特率,STM32 和 LabVIEW 都用 115200。解析用帧头帧尾截取,不匹配的字符直接丢弃。进入透传后延时 200ms 再发送温度数据,让模块把状态回复清干净,避免>干扰首帧。
6. 进阶:从单点采集到多节点:自定义帧格式与自动重连
基础链路跑通后,课设拿高分一般要加两个东西:多节点扩展和断线自动重连。这两件事其实是一件事——把传输协议设计好。
单节点的$T1,25.6#帧结构太简单,扩展多节点时我建议升级成带地址和校验的帧:$A1,T,1,25.6*3F#。帧头$,A1是节点地址,T表示温度类型,1是通道号,25.6是数据,*3F是校验字节,#是帧尾。上位机解析时先校验再取数,无效帧直接丢弃,不会污染波形。
断线自动重连是 ESP8266 链路最影响体验的问题。路由器重启、TCP 连接断开后,如果程序里没有重连逻辑,数据流就永远停了。最有效的方案是状态机,而不是在 main 里堆一堆 if:
// wifi_task.c —— 四态状态机,任何步骤失败都回退重试 typedef enum { WIFI_IDLE, WIFI_CONNECTING, WIFI_LINKED, WIFI_TXRX } wifi_state_t; void wifi_task(void) { static wifi_state_t state = WIFI_IDLE; static uint32_t last_send = 0; switch (state) { case WIFI_IDLE: // 模块在线检测,AT 返回 OK 才进入下一步 if (esp_at_send("AT\r\n", "OK", 500)) state = WIFI_CONNECTING; break; case WIFI_CONNECTING: // 连接路由器,等待 GOT IP,失败回到 IDLE if (esp_at_send("AT+CWJAP=\"ssid\",\"pwd\"\r\n", "GOT IP", 10000)) state = WIFI_LINKED; else state = WIFI_IDLE; break; case WIFI_LINKED: // 建立 TCP 连接,失败重新连 WiFi if (esp_at_send("AT+CIPSTART=\"TCP\",\"192.168.1.100\",8080\r\n", "CONNECT OK", 5000)) state = WIFI_TXRX; else state = WIFI_CONNECTING; break; case WIFI_TXRX: // 周期性发送温度帧,发送失败说明链路断开,回到 IDLE if (HAL_GetTick() - last_send > 2000) { if (!send_temp_frame()) state = WIFI_IDLE; last_send = HAL_GetTick(); } break; } }这个状态机的核心设计是“失败即回退”,任何一步失败都回到更早的状态重新尝试,而不是原地卡死。WIFI_LINKED失败回到WIFI_CONNECTING,网络环境恢复后能自动再把 TCP 连接建起来。注意透传模式断开后要重新执行AT+CIPMODE=1和AT+CIPSEND,所以在WIFI_TXRX发送失败时直接回WIFI_IDLE,强制走完全部初始化流程,模块才能真正恢复。
验证这套机制,不要在板子上直接试,用 PC 端的 TCP 调试工具打开服务器监听,观察 ESP8266 的连接和重连日志。如果工具里能看到连续的帧且断开路由器后能自动恢复,整个项目的联调就到位了。
我调这套链路时吃过最大的亏就是改完代码直接接 WiFi,结果分不清问题在传感器、模块还是上位机。从那以后每次下位机改动,我都强制走一遍“串口助手验证帧格式 → TCP 工具验证链路 → 接 LabVIEW”三步再联调,效率高很多,希望帮到你。
本文还有配套的精品资源,点击获取