STM32与ZP16甲醛传感器采集方案:从ADC串口到LCD显示与蓝牙上报
简介面向嵌入式开发者和物联网爱好者资源提供了一套完整的室内空气质量监测方案覆盖STM32与ZP16甲醛传感器的I2C/SPI通信、数据校准与温度补偿、蓝牙BLE无线传输以及LCD1602液晶显示。资源包总大小10.12MB包含325个文件以C源文件、头文件、工程配置文件、编译输出文件和原理图文件为主清晰呈现了从传感采集到无线传输的完整代码结构可直接打开工程查看电路连接并重新编译烧录。压缩包内还保留了多个工程备份与调试记录有助于学习断点设置和报错排查。通过该实战项目可同时掌握传感器驱动、蓝牙透传与人机交互的联合开发思路。已有306人学习下载适合需要完整案例参考的STM32进阶用户。1. 为什么是 STM32 ZP16甲醛传感器输出先选对再谈显示新房装修后测甲醛ZP16 比普通温湿度传感器更贴近需求它是电化学原理能把甲醛浓度映射成模拟电压或串口数字帧。STM32 在这里要干三件事用 ADC 或串口读传感器、把浓度推到 LCD1602 本地显示、再交给蓝牙模块无线发送。很多入门项目卡在“传感器有数值但显示不准”根子不是代码而是没搞清 ZP16 的量程、预热时间和供电方式。这篇文章写给手里有 STM32 最小系统板想给鱼缸或新屋加一套无线监测的开发者按模拟量和串口两条路讲清楚最后落到联调时真正会踩的坑。2. ZP16 信号采集用 STM32 ADC 还是串口读接线和代码分开写ZP16 模块的输出方式不能默认必须把模块背面的丝印或手册看一遍再写代码。不同批次、不同商家给出的量程和电压斜率并不完全一致这也是很多项目第一次上电读数是 0.1ppm 和 2.0ppm 差的来源。2.1 ZP16 的两种输出方式决定后面所有接线电化学传感器本质上是把空气中的甲醛分子在电极上反应产生的微弱电流放大ZP16 模块内部完成了信号调理对外提供模拟电压和串口两种读取途径。常见的引脚定义如下但仍要以实际模块为准。引脚方向说明VIN电源输入通常 5V 或 3.3V查手册确认GND电源地必须和 STM32 共地AOUT模拟输出电压随浓度变化常用 0~2VTXD串口发送接 STM32 的 RX 引脚RXD串口接收接 STM32 的 TX 引脚选模拟量还是串口取决于你手头 STM32 的资源。模拟量接线最少一根 GPIO 加 ADC 就能采样但长距离布线容易被电机、继电器干扰串口是数字信号抗干扰好而且浓度值直接以数字帧给出省去斜率换算代价是占一个 USART还得先确定波特率。另外电化学传感器有一个共性上电后需要一定时间让电极极化稳定少的几十秒多的几个小时。如果刚上电就采样读出来的电压一直在跳那不是代码问题是传感器没进入稳定工作区。2.2 用 STM32 ADC 读电压接线与线性换算如果确定走模拟量把 ZP16 的 AOUT 接 STM32 的 PA0 这类支持 ADC 的引脚GND 和 STM32 的 AGND 共地。STM32 的 ADC 输入阻抗不高而 ZP16 的 AOUT 输出内阻未必很低我一般会在 AOUT 和 ADC 引脚之间串一个 100Ω 电阻再接一个 100nF 电容到地形成一阶低通滤波能明显减少采样值的毛刺。假设模块手册写的是 0~2V 对应 0~2ppm那浓度和电压是 1:1 关系。HAL 库配置好 ADC1 单通道后读取代码可以写成float zp16_read_voltage(void) { ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel ADC_CHANNEL_0; sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 100) HAL_OK) { uint32_t raw HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); return raw * 3.3f / 4095.0f; } HAL_ADC_Stop(hadc1); return -1.0f; }采样时间选长一点的 239.5 周期是因为 ZP16 的输出源阻抗不低采样时间短会导致 ADC 电容没充满读数偏低。返回的电压还要做一次平滑比如连续取 8 次去掉最大最小再平均。浓度换算不要写死在一个函数里。用宏或者配置项把0~2V和0~2ppm的关系单独拿出来这样换一批传感器时只改两个数不用动采集逻辑。2.3 用串口读 ZP16 的数字帧抗干扰且免标定部分 ZP16 模块会主动向上位机发送数据帧常见波特率是 9600、8 位数据、无校验、1 停止位。如果你的模块是这种直接接 STM32 的串口 RX 就行STM32 侧初始化一个 USART然后按帧长度接收。我一般用中断接收加状态机把一帧凑齐再解析。最小可用的阻塞版本是这样uint8_t zp16_frame[8]; float zp16_read_ppm(void) { if (HAL_UART_Receive(huart1, zp16_frame, 8, 2000) ! HAL_OK) { return -1.0f; } if (zp16_frame[0] ! 0xFF || zp16_frame[1] ! 0x17) { return -1.0f; } uint16_t raw (zp16_frame[2] 8) | zp16_frame[3]; return raw / 1000.0f; }这里的帧头和浓度字段位置是常见 ZP16 数字模块的格式不同厂家可能把校验和帧长放在不同字节务必以手册为准。如果你的模块是命令应答式不发指令不吐数据那就在接收前先调用HAL_UART_Transmit发送查询命令。串口方案的好处是省掉了电压到浓度的线性标定缺点是占一个串口。后面蓝牙模块也要串口所以如果用了 ZP16 串口再挂 HC-05 时就要启用 STM32 的第二个 USART或者改用软件模拟串口。3. LCD1602 显示浓度四线驱动、初始化时序和动态刷新LCD1602 是字符型液晶只能显示 ASCII 字符和少量日文字符不适合显示汉字。很多新手找“LCD1602 驱动代码下载”其实不需要完整工程四线模式加标准初始化时序就能跑剩下的是格式化输出问题。3.1 LCD1602 四线接法与初始化时序LCD1602 有 16 个引脚实际做项目用 4 线模式最省 IO。除电源和背光外RS、E、D4~D7 五个信号就能完成全部读写RW 引脚固定接地只在写模式下工作。一个常用的接线关系可以这么安排LCD1602 引脚接 STM32VSSGNDVDD5V 或 3.3V看模块说明VO10K 电位器中间脚RSPB0RWGNDEPB1D4PB2D5PB3D6PB4D7PB5VO 对比度脚不接电位器的话屏幕上可能什么都看不见或者满屏黑块。这是最容易被忽视的硬件问题。四线模式的初始化必须按顺序来不能直接发 4 位数据就写0x28。标准流程是void lcd_init(void) { delay_ms(50); lcd_write_nibble(0x03); delay_ms(5); lcd_write_nibble(0x03); delay_us(150); lcd_write_nibble(0x03); lcd_write_nibble(0x02); lcd_write_cmd(0x28); lcd_write_cmd(0x0C); lcd_write_cmd(0x01); lcd_write_cmd(0x06); delay_ms(5); }前面的三次0x03是让液晶从任意上电状态进入 8 位模式然后0x02才切到 4 位模式。接着的0x28是设置 4 位数据线、双行、5×7 点阵。0x0C是开显示不显示光标0x01清屏0x06设置地址指针自增。顺序乱了屏幕就会出现乱码或一个字都打不出来。3.2 显示字符串和浮点数用 sprintf 拼行LCD1602 不能直接输出浮点数需要先把浓度格式化成字符串。第一行可以显示当前浓度第二行留一个显示蓝牙状态或设备标号。打印一行文本的常用写法char line1[17]; snprintf(line1, sizeof(line1), HCHO%4.2fppm, ppm); lcd_set_cursor(0, 0); lcd_print_string(line1);%4.2f表示总宽度最少 4 个字符保留两位小数。这样0.12、1.08显示时宽度一致刷新时不会因为数字位数变化留下残影。sprintf在单片机上是重函数不用-u _printf_float的话浮点格式化可能不被链接。这个在 GCC ARM 工具链里常见第一次显示ppm输出空白的概率很大优先检查链接脚本和编译选项。3.3 动态刷新清屏不是好习惯有的程序每隔一秒调用一次lcd_write_cmd(0x01)清屏再重新打印。清屏命令执行时间很长屏幕还会闪长时间跑还容易造成字符错位。更稳的做法是每次都把整行覆盖。0x28模式下一行 16 个字符写满整行就不会残留旧内容。比如snprintf(line1, sizeof(line1), HCHO%4.2f ppm , ppm);末尾留两个空格把上次可能留下的更长字符盖掉。第二行显示蓝牙状态时同理不要只改几个字符直接把整行打印。LCD1602 的 DDRAM 地址是连续的每行开头地址分别是0x00和0x40。设置光标时第一行命令参数是0x80 | 0x00第二行是0x80 | 0x40也就是lcd_set_cursor(1, 0)时送0xC0。4. 蓝牙模块上报HC-05 的 AT 配置、SPP 透传和数据包格式蓝牙模块的作用是替代调试串口线把 STM32 采集到的数据送到手机或电脑上位机。最常见的是 HC-05它支持 SPP 协议也就是串口透传协议。JDY-31 这类底板完全兼容 HC-05/HC-06 从机模式的模块也可以直接用AT 指令差别不大。4.1 蓝牙模块选型SPP 协议和从机工作模式SPP 协议是最传统的蓝牙串口透传方式手机端不需要二次开发任何“蓝牙串口助手”都能直接看到 STM32 发来的字符串。HC-05 和 HC-06 的区别在于HC-05 可以切换主从角色既能被手机连也能主动去连别的蓝牙设备HC-06 是纯从机只能等别人连。甲醛监测这种应用只需要手机连模块HC-06 或者 JDY-31 就足够JDY-31 的优势是价格低、默认波特率 9600AT 指令直接可用。模块工作模式分成数据透传和 AT 命令两种。普通上电时模块直接透传按住模块上的按键再上电或者把 EN 引脚拉高再上电才会进入 AT 模式。AT 模式下的波特率不一定是数据透传波特率HC-05 的 AT 模式常见是 38400JDY-31 则固定 9600配置前先按模块丝印确认。4.2 AT 指令配置名称、配对密码和波特率把蓝牙模块的 RX 接 USB 转 TTL 的 TXTX 接 USB 转 TTL 的 RX然后接电脑串口助手。进入 AT 模式后发送以下指令每一条都要回OKAT 指令响应作用ATOK基本通信测试ATNAMEHCHO-BLEOK修改设备广播名称ATROLE0OK设为从机模式ATPSWD1234OK设置配对密码ATUART9600,0,0OK9600透传波特率设为 9600配置完成后重新上电手机蓝牙里就能搜到对应名称。如果搜索不到先确认模块是不是真的退出了 AT 模式。HC-05 的 AT 指令波特率和透传波特率是分开存储的只改ATUART不会影响 AT 模式波特率。很多人把模块拿到 STM32 工程里用发现发的AT没有回显其实是 AT 模式下要 38400而 STM32 串口配的是 9600。STM32 端只需把蓝牙模块当作普通串口外设蓝牙的 TTL 电平不能直接接 RS232但可以直接接 3.3V 的 STM32 引脚。如果模块是 5V 供电串口 RX 引脚可能输出 5V 高电平需要串电阻分压再进 STM32或者直接选 3.3V 供电的底板。4.3 STM32 串口发送数据包帧头、长度和校验透传模式下的数据格式最好自己定义一帧不要直接发裸字符串。字符串方便人看但手机上位机解析时要处理小数点位和边界容易错位。最简单的帧格式是帧头、数据、校验void ble_send_hcho(uint16_t ppm_x1000) { uint8_t frame[5]; frame[0] 0xAA; frame[1] 0x55; frame[2] (uint8_t)(ppm_x1000 8); frame[3] (uint8_t)(ppm_x1000); frame[4] frame[0] ^ frame[1] ^ frame[2] ^ frame[3]; HAL_UART_Transmit(huart2, frame, 5, 100); }ppm_x1000是把浮点浓度放大 1000 倍后取整避免传输浮点数。比如0.12ppm传来的是120上位机收到后除以 1000 再显示。帧头固定为0xAA和0x55校验用整帧异或逻辑简单单片机和手机端都好实现。手机端收到后按帧头找同步再取第 3、4 字节合并成浓度值。注意蓝牙串口助手有些默认会把接收到的十六进制显示成 ASCII要在设置里打开 HEX 显示。如果屏幕上出现的是乱码多半不是传输问题而是助手把0xAA按文本输出了。如果是自己写 Android 上位机直接拿 SPP 建立 Socket读流以后按同样的帧格式解析不需要额外驱动。5. 联调时最容易翻车的 4 个细节ZP16 标定、蓝牙配对、LCD 花屏和供电5.1 上电不要立刻看数据ZP16 需要稳定时间电化学传感器上电初期输出漂移很大有的人一通电就开蓝牙调试助手看到数值乱跳就以为是 ADC 坏了。先把系统空置 30 分钟等 LCD1602 显示的浓度曲线基本平缓再做判断。对比零点时把 ZP16 放到密封袋里数值应该在 0.00 附近如果偏高就是传感器零点偏或者被酒精、香水污染了。5.2 蓝牙连不上时先回 USB 串口验证HC-05 连接不上最常见的是波特率不匹配。把 STM32 的 TX 直接接到 USB 转 TTL 的 RX在电脑串口助手里看数据是否正常。如果这里能看到AA 55 ...问题就在蓝牙模块。此时把蓝牙重新进 AT 模式发ATUART9600,0,0再发ATROLE0复位模块。有的模块从机模式默认不能被手机发现需要检查ATNAME是否设置成空名称。5.3 LCD1602 花屏先检查对比度再查初始化时序满屏黑块时先用螺丝刀调 VO 电位器调到背景块最淡的位置。如果调不出正常对比度就是初始化时序乱。四线模式下检查 RS 和 E 的引脚顺序是否和代码一致。E 引脚脉冲宽度至少要 230nsSTM32 主频高时要把延时补上。lcd_write_nibble里如果先拉高 E 再写数据线数据建立时间不够就会出现高位数据正确、低位乱码。5.4 供电不稳所有模块都在失灵边缘ZP16 的模拟输出和 LCD1602 背光都吃电流如果系统用 USB 线直接供电蓝牙传输瞬间电流波动会把 ADC 参考电压拉偏。建议电源走 5V 输入后用 AMS1117 降到 3.3VSTM32 的供电和 ZP16 的供电从同一个稳压输出取电。ZP16 的 VREF 和 STM32 的 VDDA 不要分别接两个稳压器那样会产生地电位差浓度数据会一帧高一帧低。最后调蓝牙的时候AT 模式下的 RX/TX 经常被接反。记住 STM32 的 TX 要进蓝牙模块的 RX不是进另一个 TX。复位蓝牙后重新进入 AT 模式直到收到OK再进行下一步。本文还有配套的精品资源点击获取