资讯动态

鸿蒙小熊派ADC采样与温度补偿:浊度/TDS/pH/DHT11

发布时间:2026/9/11 17:05:39 来源:尧图企业网站定制
简介这套小熊派多传感器采集工程面向物联网入门者与嵌入式开发者帮助解决浊度、溶解性总固体、酸碱度及温湿度四类参数的读取与数据解析问题适合环境监测、水质检测类项目快速起步。压缩包共包含两千个文件以C语言源码与头文件为主体另有超文本说明页面、Python辅助脚本及Markdown文档覆盖驱动配置、数据转换和通信示例的完整代码结构整体大小十六兆左右便于查阅与移植。工程已有156人学习可结合配套文档直接搭建可运行的环境监测原型。资源中提供了传感器初始化、模拟量采集、数值标定及数据上传等关键模块并附带相关脚本与说明可帮助读者理解从硬件信号到应用数据的完整链路二次开发也较为方便。1. 用鸿蒙小熊派读浊度、TDS、pH 和 DHT11先明白谁在给你添乱手里的小熊派如果还只点过灯、跑过串口打印把这个标题拆开看会发现真正的题目是让一块板子同时喂饱四路传感器其中三路必须走 ADC。浊度、TDS、pH 这三支探头输出的都是模拟电压电压值随介质变化但电压不等于你要的 NTU、ppm 和 pH 数值DHT11 虽然是数字传感器走的却是严格依赖微秒级定时的单总线。所以下面按“先解决模拟量再补温度补偿最后调 DHT11 时序”的顺序把鸿蒙小熊派读取这四路数据的最小闭环讲清楚。新手照步骤能跑通老手看参数和坑点也能少试错几轮。2. 浊度、TDS、pH 和 DHT11 的信号类型与接线决定你后续怎么调2.1 先按输出类型把四路传感器分成两组浊度模块内部是红外 LED 加光电池水越浑透过的光越少光电池输出电压跟着变TDS 探头靠持续的电导测量模块把探针间的电阻变化换成模拟电压pH 电极输出的是高阻毫伏信号模块负责放大并整定到 ADC 可读的范围。这三路本质上都是“电压随被测对象变化”而且不是线性关系必须在软件里做二次换算和温度修正。DHT11 则是纯数字单总线数据线被主机拉低再释放后传感器自己用一串微秒级高低电平把 40 位数据吐出来。它不需要 ADC但要求 GPIO 方向和延时都非常精确。传感器输出类型典型输出范围供电信号调理重点浊度模拟电压0~3.3V 或 0~5V3.3~5V电压与 NTU 非线性需查表或拟合TDS 探头模拟电压0~3.3V 或 0~5V3.3~5V温度系数约 0.02/℃TDS 由 EC 换算pH 电极模拟毫伏模块输出 0~3.3V5V高阻输入pH7 对应中点电压DHT11单总线数字0/1 电平3.3~5V微秒级时序必须做校验和校验把四路传感器分成“三路模拟 一路单总线”之后后续所有问题都落在两个地方ADC 通道上的电压稳不稳以及温度变化时换算公式里的系数修不修。2.2 小熊派 ADC 通道与 GPIO 的推荐接线鸿蒙小熊派的 ADC 通道数量有限常见的版本上并不是所有 GPIO 都能做模拟采样。我的习惯是把三路模拟量放在 ADC0、ADC1、ADC2DHT11 放在一个空闲 GPIO避免与 I2C、UART 调试口抢占。功能推荐引脚说明浊度ADC0 / GPIO05尽量远离 WiFi 天线电源噪声小TDSADC1 / GPIO06与浊度分时采样通道间隔至少 20mspHADC2 / GPIO07高阻探头采样端并联 0.1uF 陶瓷电容DHT11GPIO08任意普通 GPIO数据线接 4.7k 上拉到 3.3V代码里把这些映射做成宏后面采样和调参只改一处#define TURBIDITY_ADC_CHANNEL ADC_0 #define TDS_ADC_CHANNEL ADC_1 #define PH_ADC_CHANNEL ADC_2 #define DHT11_GPIO IOT_GPIO_IO_08注意这里ADC_0到ADC_2是对应 SDK 里的AdcChannel枚举不同版本的小熊派扩展板丝印可能有差异接线前先用板卡原理图确认引脚复用关系。不要凭板子上的“A0”丝印直接接很多扩展板的 A0 是经过分压、滤波之后的信号已经不是探头原始电压。2.3 供电与共地模拟量漂移查不出原因的十个里有八个是地没共好三块模拟探头尽量使用同一个电源域传感器的 GND 必须和小熊派的 GND 直接相连。如果传感器用独立电源负极没有共地ADC 读到的不是传感器输出和板端地之间的电位差而是浮动电压数据会随机跳。另一个常被忽略的点是上电时序。TDS 探头和 pH 电极都有极化过程传感器上电后至少要等 30 秒再开始连续采样否则前几百个点会缓慢漂移。如果你把小熊派和传感器同时上电采集任务立刻执行前面一段数据大概率是废的。解决办法是任务里加一个TaskMsleep(1000)的预热循环或者把采样任务延时启动。提示如果模块输出 5V 电平而小熊派 ADC 输入范围低于 5V务必先看模块是否带板载调理电路不要直接把 5V 输出接进 GPIO。3. 用鸿蒙小熊派的 ADC 读三路模拟量滤波和电压换算决定精度3.1 HarmonyOS ADC 读取的最小可用代码鸿蒙小熊派 SDK 里 ADC 的封装比较薄本质上就是读寄存器然后按配置的采样时间返回一个 12 位原始值。最小读取代码如下#include iot_adc.h #include iot_gpio.h static uint16_t ReadAdcValue(AdcChannel channel) { uint16_t data 0; AdcRead(channel, data, ADC_READ_TYPE_2_MS, 8); return data; }这里第一个参数是 ADC 通道枚举第二个参数是输出原始值的指针第三个参数ADC_READ_TYPE_2_MS表示采样窗口长度最后一个8是防止模拟开关切换后电压还没稳定的延时档位。如果读到连续跳变可以把它从 8 调到 16 或 32但代价是单次采样耗时变长。ADC 原始值不是电压这点容易踩。小熊派上常见的 ADC 是 12 位满量程 4096下一步必须把原始值换算成毫伏。换算前先确认你手里板子的参考电压有的版本是 1.8V有的是 3.3V不能看到别人代码写4096 * 3.3就照抄。参数建议值为什么ADC 位数12 bit原始值范围 0~4095参考电压 Vref按板卡实际值错误会导致全量程比例错误单通道采样次数10兼顾速度与稳定通道切换间隔≥20ms等待模块输出端 RC 滤波建立3.2 滑动平均滤波与突变数据剔除模拟量采样不能只读一次水泵运行、气泡经过、探头接触不良都会造成瞬时毛刺。工程上最省资源的是“去极值滑动平均”连续采 10 次去掉一个最大和一个最小剩下 8 次求平均。#define ADC_SAMPLE_NUM 10 static uint32_t FilterAdc(AdcChannel channel) { uint32_t sum 0; uint16_t maxV 0; uint16_t minV 4095; for (int i 0; i ADC_SAMPLE_NUM; i) { uint16_t v ReadAdcValue(channel); sum v; if (v maxV) maxV v; if (v minV) minV v; } sum sum - maxV - minV; return sum / (ADC_SAMPLE_NUM - 2); }这段代码的逻辑是先完整累加再减掉一个最大值和一个最小值最后除以 8。注意没有用排序成本低适合 RTOS 任务里高频调用。如果你要跟踪快速变化的浊度可以把采样数降到 6实时性更好但抗毛刺能力弱一些用在鱼缸、水塔这类慢变场景10 次是稳妥值。3.3 电压换算参考电压和分压比必须同时写清楚拿到滤波后的原始值下一步转毫伏#define ADC_VREF_MV 1800 // 按板子实际改单位 mV #define ADC_RES_MAX 4096 static uint32_t RawToMv(uint32_t raw) { return (raw * ADC_VREF_MV) / ADC_RES_MAX; }这个公式本身很简单真正的风险在前置电路。如果传感器输出 0~3.3V而板载 ADC 参考电压只有 1.8V超过 1.8V 的部分会被削顶pH 碱性段和 TDS 高浓度段全部读到 4095。这种情况下先在模块输出端做电阻分压比如串联 10k、并联 10k 把 3.3V 压低一半软件再把采样电压乘 2 还原。分压比也要做成宏别在公式里到处写魔法数字。注意分压电阻会降低输入阻抗对高阻 pH 电极影响尤其明显。如果 pH 模块输出阻抗较高优先选具备缓冲输出运放跟随的 pH 模块再接分压电路。4. 浊度传感器温度补偿公式与 TDS、pH、DHT11 的工程量换算4.1 浊度传感器温度补偿公式与非线性查表浊度模块给的是电压不是 NTU。同一型号传感器水温和光源衰减都会让电压漂移。常见做法是先用标准浊度液做一条“电压-浊度”标定表再用线性插值如果手里只有 25℃ 下标定好的数据按一阶温度补偿公式折回来NTU NTU25 / (1 0.02 * (T - 25))这里NTU25是 25℃ 下标定的浊度值T是当前水温0.02是水样本的通用浊度温度系数。温度越高水体中颗粒对光的散射越强所以高于 25℃ 时要把读数向下修正。一组典型标定点如下电压(mV)NTU2800022005014003006001000插值代码可以写成这样static float TurbidityMvToNtu(uint32_t mv, float temp) { static const uint16_t cal_mv[4] {600, 1400, 2200, 2800}; static const uint16_t cal_ntu[4] {1000, 300, 50, 0}; float ntu 0; if (mv cal_mv[3]) { ntu cal_ntu[3]; } else if (mv cal_mv[0]) { ntu cal_ntu[0]; } else { for (int i 0; i 3; i) { if (mv cal_mv[i] mv cal_mv[i 1]) { ntu cal_ntu[i] (float)(cal_ntu[i 1] - cal_ntu[i]) * (float)(mv - cal_mv[i]) / (cal_mv[i 1] - cal_mv[i]); break; } } } return ntu / (1.0f 0.02f * (temp - 25.0f)); }这组标定点只演示插值逻辑不同品牌模块差异很大必须用 0 NTU 纯水和已知浊度标准液重新标定。插值比拟合一条二次多项式更稳因为多项式在区间外容易反翘线性插值最多只是斜率不连续。4.2 TDS 的 0.02 温度系数和电极常数TDS 本质是电导率的换算。大多数模块已经把探头电阻变成了电压代码要做的是把电压转换成电导率 EC再做温度补偿最后乘电极系数。电导率随温度升高而升高室温标定的数据在夏天会偏高。最常见的一阶补偿式EC25 ECt / (1 0.02 * (T - 25))其中ECt是当前温度下测到的电导率EC25是折算到 25℃ 的值0.02是淡水的经验温度系数。水越脏、离子浓度越高系数越接近 0.024淡水场景用 0.02 足够。static float CalcTds(float mv, float temp, float mv_zero, float ec_per_mv) { float ec (mv - mv_zero) * ec_per_mv; float ec25 ec / (1.0f 0.02f * (temp - 25.0f)); float tds ec25 * 0.5f; return tds; }参数里mv_zero是电导率为零时模块输出的电压ec_per_mv是模块的电压-电导率斜率这两个值必须用标准液标定。0.5f是 TDS 与 EC 的经验换算系数NaCl 溶液接近 0.5混合盐溶液常见 0.6~0.7。如果不确定水样成分用 0.64 起步再用烘干称重法定标。提示TDS 探头是电化学器件长时间直流激励会极化。模块内部如果已经做了交流激励软件侧不要再给探头供电否则寿命明显缩短。4.3 pH能斯特方程的逐点温度修正pH 电极输出的毫伏值与 pH 值呈线性但斜率是温度的函数。25℃ 下理论斜率为 59.16 mV/pH温度每升高 1℃斜率约增加 0.2%。很多只做常温校准的代码把斜率写死为 59.16冬天和夏天的读值能差 0.3 个 pH。简化后的能斯特修正公式slope 59.16 * (T 273.15) / 298.15 pH 7.0 - (V - V7) / slope代码实现如下static float CalcPh(float mv, float temp, float ph7_mv) { double slope 59.16 * (temp 273.15) / 298.15; double ph 7.0 - ((double)mv - ph7_mv) / slope; return (float)ph; }这里的ph7_mv是探头放进 pH 7.0 缓冲液后读取到的模块输出电压。按上面公式pH7 时电压差为 0pH 输出正好是 7.0pH4 时电压差约为 177mV计算结果是 4.0。注意符号方向如果ph7_mv没校准对整个 pH 区间都会平移。实际使用中两点校准比一点校准可靠。先泡 pH7 缓冲液校准零点再泡 pH4.0 或 pH10.0 缓冲液校准实际斜率。如果标定出来的斜率偏离 59.16 超过 10%说明电极老化或内部参比液污染靠软件修正补偿不了。4.4 DHT11 温湿度传感器单总线时序和校验DHT11 的难点不在读而在时序。规格书要求主机先把数据线拉低至少 18ms再拉高 20~40us 等待响应传感器回一个 80us 低电平和 80us 高电平然后开始吐 40 位数据。每一位由 50us 低电平开始高电平持续 26~28us 表示 0持续 70us 表示 1。判断 0 和 1 的阈值取 50us高电平超过 50us 判 1低于 50us 判 0。最后没有校验位的数据直接丢弃防止传输错误污染温度补偿。DHT11 信号阶段时间要求常见失败现象起始拉低≥18ms传感器不响应等待响应低约 80us一直读到高电平等待响应高约 80us一直读到低电平高电平判 0/150us 阈值全部读成 0 或全部读成 1static int WaitPinLevel(IotGpioValue level, int us_max) { int cnt 0; while (IoTGpioRead(DHT11_GPIO) ! level) { hi_udelay(1); if (cnt us_max) { return 0; } } return 1; } static int DHT11_Read(int8_t *temp, uint8_t *humi) { uint8_t bits[5] {0}; uint16_t cnt 0; IoTGpioSetDir(DHT11_GPIO, IOT_GPIO_DIR_OUT); IoTGpioWrite(DHT11_GPIO, IOT_GPIO_VALUE0); hi_udelay(20000); // 拉低 20ms保证传感器识别起始信号 IoTGpioWrite(DHT11_GPIO, IOT_GPIO_VALUE1); hi_udelay(30); // 拉高 30us IoTGpioSetDir(DHT11_GPIO, IOT_GPIO_DIR_IN); if (!WaitPinLevel(IOT_GPIO_VALUE0, 100)) return -1; if (!WaitPinLevel(IOT_GPIO_VALUE1, 100)) return -1; for (int i 0; i 40; i) { if (!WaitPinLevel(IOT_GPIO_VALUE0, 100)) return -1; cnt 0; while (IoTGpioRead(DHT11_GPIO) IOT_GPIO_VALUE1 cnt 100) { cnt; hi_udelay(1); } bits[i / 8] 1; if (cnt 30) bits[i / 8] | 1; } if ((uint8_t)(bits[0] bits[1] bits[2] bits[3]) ! bits[4]) { return -1; } *humi bits[0]; *temp bits[2]; return 0; }这段代码里hi_udelay是微秒级忙等延时适合在独立采集任务里使用不要在定时器中断或需要严格实时响应的上下文里直接调用20ms 的忙等会把任务调度卡住。DHT11 本体精度一般温度 ±2℃、湿度 ±5%RH测出来只适合做参考不适合拿去修正对精度要求高的 TDS 和 pH 实验。5. 联调顺序、校准方法和串口 JSON 输出让四路数据经得起复测5.1 先校 pH再校 TDS最后校浊度四路传感器一起跑的时候校准顺序不能乱。pH 和 TDS 都需要温度值参与计算所以先用 DHT11 读数确认环境温度稳定浊度的温度补偿同样依赖温度。我的顺序是先把 DHT11 跑通再校 pH接下来校 TDS最后校浊度。pH 校准用 7.00 和 4.00 两种缓冲液记录两个电压算出实际斜率和零点。TDS 校准时先用 1413uS/cm 标准液标出ec_per_mv再用纯水标mv_zero。浊度校准最直接用 0NTU 纯水标最高电压点用已知浊度标液标中段电压低于高点太多时还要检查探头遮光帽是否漏光。5.2 输出一个可解析的串口 JSON 帧调试时不要只打印裸电压把换算后的工程量和原始 ADC 值一起输出。这样上位机既能按 JSON 解析也能在数据异常时回查原始采样值。printf({\ntu\:%u,\tds\:%.1f,\ph\:%.2f,\temp\:%d,\humi\:%u}\r\n, ntu, tds, ph, temp, humi);这里temp和humi是 DHT11 读到的整数温度和百分比湿度ntu是四舍五入后的浊度整数tds和ph保留一位和两位小数。\r\n不能省很多串口上位机按行解析只发\n在某些终端里会粘帧。5.3 用标准液和重复采样验证数据判断数据可靠不是看串口有没有数字输出而是看三点第一pH 在缓冲液里 1 小时内漂移不超过 0.05第二TDS 在标准液里连续读 10 次极差不超过平均值的 5%第三浊度在纯水里读数不跳动超过 5NTU。如果 ADC 原始值频繁出现在 0 或 4095先查供电和分压不是滤波参数的问题。最后留一个调参技巧把FilterAdc的采样次数降到 3临时打印滤波前的原始值观察跳变范围再决定要不要加大中值剔除数量。这样能快速把“传感器本身噪声”和“电路接触不良”区分开避免对着平均窗口调半天。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价