从传感器到上位机:ESP32数据采集系统全链路实战指南
2026/9/13 10:13:45 网站建设 项目流程

简介:这是一套面向工业数据采集与DCS控制系统从业者的完整教程,围绕传感器、软件、硬件三条主线展开,适合需要从零搭建或优化工业采集方案的工程师。传感部分涵盖热电偶测温、压阻式测压、光电传感等常见类型的选择、安装、校准与维护,软件部分讲解SCADA系统中的实时监控、报警设置与历史数据记录,硬件部分则聚焦数据采集模块、PLC/PC控制器、以太网与串口通信等组件的集成设计,并补充DCS分布式控制在生产流程中的实际应用。内容按传感器篇、软件篇、硬件篇组织,知识点密度较高。资源为RAR压缩包,大小122.35MB,页面未列出具体文件总数与类型明细,不影响按章节学习。已有154人学习该教程,覆盖从基础原理到工程落地的关键环节,可作为工业自动化与智能制造的参考材料。

1. 一套完整数据采集系统为什么难在传感器、软件与硬件的咬合

做数据采集系统最难的不是买传感器,也不是建数据库,而是把传感器、软件与硬件三层的咬合理顺。传感器输出的是电压、电流、脉冲或特定协议字节,硬件要把信号调成单片机能读的范围,软件要把原始码值换算成有物理意义的量并落到存储。任一层假设错位,都会得到“能跑但不可信”的数据:量程选错、参考电压不匹配或波特率不一致,都要花数倍时间排查。

下面以 ESP32 为硬件示例,从选型、信号调理、固件采集到上位机解析、滤波、存储与校准,把完整数据采集系统的落地路径走一遍。无论做传感器课程设计、设备监测还是嵌入式硬件原型,先把最小链路打通,再逐步加告警和数据库最省时间。

2. 数据采集系统的传感器与硬件层:信号选型、分压接线与 ESP32 硬件调通

2.1 先把传感器输出类型分清楚,再谈“读数据”

采购传感器时,商详页上最引人注意的是测量范围、精度和供电电压,但真正决定采集改造难度的,是它的输出类型。同一款“光电传感器”,有的输出 NPN 开关量,有的输出 0-10V 模拟量,接入方式完全不同。输出类型大体分三类:

  • 模拟输出:0-3.3V、0-5V、0-10V、4-20mA。读取简单,但线损敏感、易受干扰。
  • 脉冲/频率输出:旋转编码器、涡轮流量计、部分光电传感器。需要计数器或中断输入来测频率或周期。
  • 数字总线输出:UART、Modbus RTU、I2C、CAN。有的传感器带自定义通讯协议,例如胎压监测传感器这类设备,数据按帧发送,必须先搞清楚帧结构。

常见传感器的输出与采集方式如下表:

传感器类型输出类型采集方式典型用途
光电传感器NPN/PNP 开关量GPIO/中断到位检测、计数
反射式传感器电压/比较器输出ADC/GPIO循迹、测距感知
霍尔传感器开关量/模拟量GPIO/ADC转速测量、磁性编码
浊度传感器模拟电压ADC水质监测
热电偶/PT100电压/电阻专用 AD 芯片温度采集
压力变送器4-20mA电流转电压+ADC工业过程监测

说一句“反射式传感器工作原理”:发射管发出红外光,接收管根据被测物反射回来的光强改变输出电流或电平,光强到阈值后输出翻转。霍尔传感器则是根据磁场强度或极性翻转输出,很多转速检测和电机换向都用它。五路循迹传感器的优点在于,单个探头对灰度判断容易误判,五个并排安装后对边界和交叉口有冗余判断,这也说明做数据采集系统时,“多路冗余”往往比一只高精度探头更稳。

2.2 信号调理的常见做法:分压、电流转电压与参考电压匹配

ADC 输入范围一般不能超过参考电压。ESP32 的 ADC 参考电压典型为 3.3V,极限输入范围是 0-3.3V;0-10V 模拟传感器必须先分压,4-20mA 需要一个采样电阻把电流转成电压。

分压电阻常见做法是固定下臂阻值,反算上臂。用一段 Python 计算:

vin_max = 10.0 vout_max = 3.3 r_bottom = 10_000 # 下臂 10k r_top = r_bottom * (vin_max - vout_max) / vout_max print(round(r_top))

逻辑说明:分压公式是 Vout = Vin * R_bottom / (R_top + R_bottom)。代码固定下臂 10kΩ,算出上臂约 20.3kΩ,可以用 20kΩ 串 300Ω 实现,实际 Vout 最大约 3.29V。更稳的做法是先加一级运放跟随,再接分压网络,避免 ADC 输入阻抗把电压拉低。

4-20mA 的常规接法是外接 250Ω 精密电阻,电流在电阻上产生 1-5V 电压,再按比例缩到 0-3.3V。电阻精度会直接进入测量结果,选 0.1% 精度、低温漂的电阻才靠谱。

提示:ADC 参考电压如果不是稳定 3.3V,测量结果会跟着供电抖动。硬件调试的第一步永远是先用万用表确认 VCC 和参考电压,而不是直接怀疑代码。

2.3 用 ESP32 做最小硬件调通测试

拿到传感器后先别急着解析数据,在 ESP32 上把原始值读出来。以模拟输出传感器接 GPIO34 为例,Arduino 框架下最小程序如下:

#define SENSOR_PIN 34 void setup() { Serial.begin(115200); analogReadResolution(12); // ADC 分辨率 12 位,范围 0-4095 analogSetPinAttenuation(SENSOR_PIN, ADC_11db); // 输入范围约 0-3.3V } void loop() { int raw = analogRead(SENSOR_PIN); float volt = raw * 3.3f / 4095.0f; Serial.printf("raw=%d volt=%.3fV\n", raw, volt); delay(500); }

三点参数说明:

  • analogReadResolution(12) 让返回值落在 0-4095,方便换算电压。
  • ADC_11db 衰减对应约 0-3.3V 输入范围,不要用默认衰减,否则很多开发板读到的电压会提前触顶。
  • GPIO34 是纯输入引脚,不带板载干扰,适合做模拟采集。换到 GPIO2 这类带 LED 的引脚,读数可能受调试串口影响。

跑这个程序时,用手遮挡或靠近传感端,串口里的 raw 和 volt 应稳定变化。如果一直读 0,先查供电;如果一直停在 4095 附近,查是否把 5V 直连进 GPIO,或者分压电阻虚焊。这个“esp32 硬件调通测试”步骤,能让你在十分钟内分清是传感器问题、接线问题还是代码问题。

提示:想观察波形细节,把串口日志接到绘图工具或逻辑分析仪,比盯着 printf 直观很多。

2.4 硬件调试:读数异常时按什么顺序查

硬件工程师排查读数问题的顺序通常是:电源、共地、信号波形、工具链。先用万用表量传感器供电是否达标,再看信号地是否和 ESP32 GND 相连,很多“浮空读数”都是地线没接。之后用示波器看传感器输出端,判断是干净跳变还是毛刺。毛刺多时可在信号线对地加 100nF 电容,或改成差分传输。

遇到 Windows 报“配置信息(注册表中的)不完整或已损坏”,或者“无法验证此设备所需的驱动程序的数字签名”,先别急着拆电路。这类错误多数是 USB 驱动或开发工具与系统版本不匹配:在设备管理器里删掉旧设备,重新插 USB 让系统枚举一次,往往就恢复。安装 Keil 的 pack 时报硬件错误也一样,优先检查 pack 版本是否匹配当前 IDE,而不是怀疑开发板损坏。

2.5 高速传感器:频率测量与计数资源

转速、流量计这类脉冲输出传感器,在高速场景下不能用 delay 加轮询。ESP32 的 PCNT 外设可以在硬件层面累加脉冲,MCU 只定时读计数值,适合连续脉冲计数,也能避免中断风暴。若传感器输出的是连续高频模拟量,就得考虑带 DMA 的 ADC 或外接 ADC 芯片,这一步已经进入硬件设计的深水区。平时用 ESP32 做原型验证时,先把低速链路跑稳定,再往上加资源。

3. 数据采集系统的软件层:固件采集、协议解析与上位机对接

3.1 固件侧先定采集策略:轮询、中断还是 DMA

软件层的第一步不是写协议解析,而是决定怎么取数。低速模拟量可以轮询,例如每 100ms 读一次 ADC;开关量事件必须用中断,否则主循环处理其他任务时会漏事件;频谱较宽的模拟量要用连续 ADC 加 DMA,把数据搬到内存后再统一处理。三种策略的取舍如下:

采集场景策略理由
温度/湿度(<1Hz)轮询采样率低,主循环几行代码即可
光电传感器到位检测GPIO 中断事件只持续几毫秒,轮询会漏
旋转编码器 A/B 相PCNT/正交解码硬件计数避免中断风暴
高速模拟信号ADC+DMA轮询无法满足采样率

定好策略后,固件在固定时间周期内完成“取数、简单滤波、组帧、发串口”,不要把所有逻辑全部塞进主循环。常见做法是用定时器置一个采集标志位,主循环只处理标志位并发送数据。

3.2 传感器通讯协议:从 UART 原始字节到 Modbus 帧解析

传感器通讯协议五花八门。工业现场最常见的是 Modbus RTU,设备返回一帧二进制数据;消费类传感器常带私有协议,例如部分胎压监测传感器的数据帧是 ID 加数据加校验。不管协议怎么变,处理思路都是同一套:确认帧头、长度、数据区、校验位;缓冲串口字节流;按帧头同步;解析并丢掉坏帧。

以 Modbus RTU 读保持寄存器为例,帧结构如下表:

字段主机发送从机响应
地址0x010x01
功能码0x030x03
寄存器起始地址0x00 0x000x02(字节数)
寄存器数量0x00 0x020x00 0x01 0x02 0x03
CRC16低字节在前低字节在前

帧同步的核心是状态机。下面这段 C 代码示意了最小实现,按固定帧长 6 字节处理:

#define FRAME_HEAD 0xAA #define FRAME_LEN 6 // head + type + data(2) + checksum(2) uint8_t rx_buf[16]; uint8_t rx_cnt = 0; void on_uart_byte(uint8_t c) { if (rx_cnt == 0 && c != FRAME_HEAD) return; // 帧头同步 rx_buf[rx_cnt++] = c; if (rx_cnt >= FRAME_LEN) { process_frame(rx_buf, FRAME_LEN); rx_cnt = 0; } }

代码说明:rx_cnt 为 0 时只有收到帧头才进入累积过程。实际项目必须加超时保护,防止中间丢一字节后永远凑不满长度。变长帧要先用长度字段决定后续收多少字节,不能死等固定长度。

3.3 上位机数据链路:串口轮询、超时与坏帧处理

固件把数据发出后,上位机负责收、解析和存储。最容易犯的错误是“读一次就完事”。串口是字节流,一次 read 可能返回半帧,也可能包含两帧,所以必须先做缓冲。常见 Python 采集代码如下:

import serial import struct ser = serial.Serial('COM3', 115200, timeout=0.5) buffer = bytearray() def parse_frame(buf): if len(buf) < 6 or buf[0] != 0xAA: return None checksum = (buf[-1] << 8) | buf[-2] calc = sum(buf[1:-2]) & 0xFFFF if checksum != calc: return None return buf def read_sensor(): global buffer buffer += ser.read(ser.in_waiting or 1) while True: idx = buffer.find(b'\xAA') if idx == -1: buffer.clear() break if len(buffer) - idx >= 6: frame = parse_frame(buffer[idx:idx+6]) if frame: print("sensor value:", struct.unpack('>H', frame[2:4])[0]) del buffer[:idx+6] else: buffer = buffer[idx:] break while True: read_sensor()

参数说明:

  • timeout=0.5 让 read 在无数据时最多阻塞 0.5 秒,避免上位机卡死。
  • in_waiting or 1 优先读取缓冲区已有字节,没有则阻塞读一个字节,防止空转。
  • buffer.find(b'\xAA') 做帧头同步,找不到就清空缓冲区,让串口流恢复,这是坏帧恢复最简单的办法。

上位机要持续消费数据,不要让数据库写入阻塞这个循环。采集线程和存储线程分开,中间用队列传递解析结果,是更稳妥的结构。

3.4 软件分层与配置外部化:别把所有东西写进主脚本

当采集通道从 1 路加到 8 路时,把采集、解析、存储、告警写在同一个文件里会非常痛苦。常见做法是分四层:采集层读串口或网络,协议层把字节流解析成统一结构体,存储层写入数据库,展示层提供图表。软件架构图可以直接画成“展示层、协议解析层、采集驱动层”三层结构,层与层之间只通过数据对象交互。

配置项如串口号、波特率、量程、报警阈值都应该外置到 JSON:

{ "serial": {"port": "COM3", "baudrate": 115200}, "sensor": {"name": "turbidity", "range": [0, 1000], "unit": "NTU"}, "alarm": {"max": 800} }

改动一个量程或从站地址时不用重新编译上位机。长期运行的系统还要考虑采集进程崩溃后自动重启,简单用 systemd 或计划任务即可,再往后才涉及高可用话题。

4. 数据采集系统的完整数据链路:从传感器原始值到滤波、存储与展示

4.1 工程量换算:ADC 原始值到物理单位

传感器输出和物理量之间最常见的关系是线性:y = k * x + b。这里的 x 可以是 ADC 原始值,也可以是换算后的电压。以浊度传感器为例,浊度值会随水温轻微漂移,实用性较强的浊度传感器温度补偿公式写作:

NTU_comp = NTU_raw * (1 + tc * (T - 25))

其中 tc 是温度系数,不同型号差异很大。正确做法是拿标准液在 15℃、25℃、35℃ 下分别测量,再用最小二乘拟合出 tc,不要直接套用其他型号的系数,因为光源波长和光电二极管特性不同,温度特性差很多。

4-20mA 变送器的换算同样固定:

P = (I - 4) / (20 - 4) * (P_max - P_min) + P_min

在固件或上位机里,先把采样电压转成电流值,再代入公式得到工程量。量程边界 P_max、P_min 要写在配置里,不要写死在多处。

4.2 滤波策略:滑动平均、中值滤波与去极值平均

ADC 原始数据通常带噪声,接工业传感器时更明显。三种常见策略:

  • 滑动平均:对连续 N 个点平均,平滑但会滞后。
  • 中值滤波:取窗口内中间值,能剔脉冲尖刺。
  • 去极值平均:去掉最大最小后求平均,兼顾平滑和抗尖刺。

Python 可执行示例:

from collections import deque def moving_average(window, samples, n=5): window.append(samples) if len(window) > n: window.popleft() return sum(window) / len(window) def median_filter(samples, n=5): return sorted(samples)[len(samples) // 2]

参数说明:窗口 n 越大越平滑,响应越慢。温度这类慢变量取 n=10 没问题;电机转速测量取 n=5 已经能滤掉明显毛刺,滞后再明显就缩小窗口。要抗脉冲尖刺,优先做去极值平均,排序后掐头去尾再平均。

4.3 存储选型:SQLite 还是时序数据库

单机采集量不大时,SQLite 是最省事的方案,单文件、零维护、Python 自带驱动。当每秒写入几百条以上,建议换时序数据库。两者差异如下表:

维度SQLite时序数据库
写入吞吐中等,批量插入可提升高,原生支持高并发写入
存储成本可压缩,但需要自己管理自带压缩与过期策略
查询语法SQL类 SQL 或专用查询语言
适用场景课程设计、单机原型产线级长期监测

SQLite 批量插入示例:

import sqlite3 conn = sqlite3.connect('daq.db') rows = [(ts, sensor_id, value) for ts, sensor_id, value in buffer] conn.executemany( "INSERT INTO samples(ts, sensor_id, value) VALUES(?, ?, ?)", rows, ) conn.commit()

逻辑说明:executemany 一次写入多行,比逐条 execute 快一到两个数量级。采集程序应攒一定行数或每隔几秒批量提交一次,避免频繁开启事务。

4.4 展示与告警

数据要被人看到才有价值。轻量做法是用 Flask 起一个只读接口,把最新数据和最近一小时曲线交给前端;数据量大再考虑 Grafana。告警放采集侧,阈值超过置位,恢复正常才清除,避免每读一次就发一次通知。

def check_alarm(value, max_threshold): if value > max_threshold and not alarm_active: notify("sensor over max") alarm_active = True elif value <= max_threshold * 0.9: alarm_active = False

恢复阈值比报警阈值低一点,可以防止值在阈值附近抖动反复触发。这个小细节能省掉半夜三点的无用告警。

5. 把数据采集系统调“准”的四个实用技巧:硬件调试、滤波、温度补偿与回归验证

5.1 调准前先做静态检查

先看供电和共地,再看信号波形,最后才调软件。用已知电压源代替传感器输入,观测换算后读数是否正确。可以把两个 ADC 通道接同一电压,如果读数差异明显,多半是参考电压被供电网络拉偏,而不是传感器坏了。

5.2 滤波参数要跟着时间常数走

不要照抄别人的窗口大小。测量对象的时间常数决定可容忍的响应时间:室温采集用 n=10 没问题,转速测量 n=5,要看瞬态冲击事件就不要滤波,直接判断阈值。改完滤波参数后必须重新做一次标定验证。

5.3 温度补偿必须自己做系数拟合

浊度传感器温度补偿公式的正确用法,是配置三组不同温度下的标准液,记录读数和差值,再用最小二乘拟合出 tc。没有条件做恒温实验时,至少把补偿功能关掉,并在数据表里保留温度原始值,后续还能离线补算。

5.4 用最小二乘做回归验证

校准的最终检验是拿独立数据回归,看预测值与标准值的残差。用 numpy 直接拟合:

import numpy as np x = np.array([1.02, 2.01, 3.05]) y = np.array([10.1, 20.3, 30.2]) k, b = np.polyfit(x, y, 1) pred = k * x + b residual = pred - y rmse = np.sqrt(np.mean(residual ** 2)) print(k, b, rmse)

k、b 就是最终写进固件或上位机的标定系数,rmse 反映全量程拟合误差。如果 rmse 超过传感器标称精度两倍,回头查供电、接线和滤波时间常数,而不是继续调显示位数。把 (k, b) 写回固件后,再拿同一组标准输入复核一遍,读数偏差在误差带内才收工。

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

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

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

立即咨询