STM32与ZP16甲醛传感器采集方案:从ADC串口到LCD显示与蓝牙上报
2026/9/16 19:23:09 网站建设 项目流程

简介:面向嵌入式开发者和物联网爱好者,资源提供了一套完整的室内空气质量监测方案,覆盖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~2V
TXD串口发送接 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~2V0~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 引脚接 STM32
VSSGND
VDD5V 或 3.3V,看模块说明
VO10K 电位器中间脚
RSPB0
RWGND
EPB1
D4PB2
D5PB3
D6PB4
D7PB5

VO 对比度脚不接电位器的话,屏幕上可能什么都看不见,或者满屏黑块。这是最容易被忽视的硬件问题。

四线模式的初始化必须按顺序来,不能直接发 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.121.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 地址是连续的,每行开头地址分别是0x000x40。设置光标时第一行命令参数是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 的优势是价格低、默认波特率 9600,AT 指令直接可用。

模块工作模式分成数据透传和 AT 命令两种。普通上电时模块直接透传,按住模块上的按键再上电,或者把 EN 引脚拉高再上电,才会进入 AT 模式。AT 模式下的波特率不一定是数据透传波特率,HC-05 的 AT 模式常见是 38400,JDY-31 则固定 9600,配置前先按模块丝印确认。

4.2 AT 指令配置:名称、配对密码和波特率

把蓝牙模块的 RX 接 USB 转 TTL 的 TX,TX 接 USB 转 TTL 的 RX,然后接电脑串口助手。进入 AT 模式后发送以下指令,每一条都要回OK

AT 指令响应作用
ATOK基本通信测试
AT+NAME=HCHO-BLEOK修改设备广播名称
AT+ROLE=0OK设为从机模式
AT+PSWD=1234OK设置配对密码
AT+UART=9600,0,0OK9600透传波特率设为 9600

配置完成后重新上电,手机蓝牙里就能搜到对应名称。如果搜索不到,先确认模块是不是真的退出了 AT 模式。

HC-05 的 AT 指令波特率和透传波特率是分开存储的,只改AT+UART不会影响 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 再显示。帧头固定为0xAA0x55,校验用整帧异或,逻辑简单,单片机和手机端都好实现。

手机端收到后,按帧头找同步,再取第 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 模式,发AT+UART=9600,0,0,再发AT+ROLE=0,复位模块。有的模块从机模式默认不能被手机发现,需要检查AT+NAME是否设置成空名称。

5.3 LCD1602 花屏先检查对比度,再查初始化时序

满屏黑块时,先用螺丝刀调 VO 电位器,调到背景块最淡的位置。如果调不出正常对比度,就是初始化时序乱。四线模式下,检查 RS 和 E 的引脚顺序是否和代码一致。E 引脚脉冲宽度至少要 230ns,STM32 主频高时要把延时补上。lcd_write_nibble里如果先拉高 E 再写数据线,数据建立时间不够,就会出现高位数据正确、低位乱码。

5.4 供电不稳,所有模块都在失灵边缘

ZP16 的模拟输出和 LCD1602 背光都吃电流,如果系统用 USB 线直接供电,蓝牙传输瞬间电流波动会把 ADC 参考电压拉偏。建议电源走 5V 输入后,用 AMS1117 降到 3.3V,STM32 的供电和 ZP16 的供电从同一个稳压输出取电。ZP16 的 VREF 和 STM32 的 VDDA 不要分别接两个稳压器,那样会产生地电位差,浓度数据会一帧高一帧低。

最后调蓝牙的时候,AT 模式下的 RX/TX 经常被接反。记住 STM32 的 TX 要进蓝牙模块的 RX,不是进另一个 TX。复位蓝牙后重新进入 AT 模式,直到收到OK再进行下一步。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询