传感器数据采集基础¶
概述¶
传感器数据采集是嵌入式系统与物理世界交互的基础,通过传感器可以获取温度、湿度、压力、光照、加速度等各种物理量。本教程将系统讲解传感器数据采集的基本原理、ADC(模数转换器)的使用方法、传感器校准技术以及数据滤波算法,帮助你掌握从硬件连接到软件实现的完整流程。
为什么需要学习传感器数据采集¶
- 物联网应用的基础:
- 智能家居环境监测
- 工业设备状态监控
- 可穿戴设备健康追踪
-
智慧农业数据采集
-
系统感知能力:
- 获取环境参数
- 监测设备状态
- 实现闭环控制
-
数据分析决策
-
实际应用价值:
- 提高系统智能化水平
- 实现自动化控制
- 优化能源管理
-
预防性维护
-
技能发展:
- 理解模拟电路与数字电路的接口
- 掌握信号处理基础
- 学习数据分析方法
- 培养系统设计能力
本教程内容概览¶
学习路径:
第一部分:传感器基础知识
├── 传感器分类与工作原理
├── 模拟传感器 vs 数字传感器
├── 传感器性能指标
└── 传感器选型原则
第二部分:ADC基础与应用
├── ADC工作原理
├── ADC关键参数
├── STM32 ADC配置
└── ADC数据读取
第三部分:传感器接口设计
├── 模拟传感器接口电路
├── 数字传感器接口(I2C/SPI)
├── 信号调理电路
└── 抗干扰设计
第四部分:数据采集编程
├── ADC单次采集
├── ADC连续采集
├── DMA传输
└── 多通道采集
第五部分:传感器校准
├── 校准原理与方法
├── 单点校准
├── 两点校准
└── 多点校准与曲线拟合
第六部分:数据滤波算法
├── 移动平均滤波
├── 中值滤波
├── 卡尔曼滤波
└── 滤波器性能对比
第七部分:实战项目
├── 多通道数据采集系统
├── 温度监测与报警
├── 传感器数据记录器
└── 实时数据可视化
第八部分:故障排除与优化
├── 常见问题诊断
├── 性能优化技巧
├── 精度提升方法
└── 最佳实践
第一部分:传感器基础知识¶
1.1 传感器的定义与作用¶
传感器(Sensor) 是一种能够感受被测量的信息,并能将感受到的信息按一定规律转换成电信号或其他所需形式的信息输出的装置。
传感器的基本组成:
物理量 → [敏感元件] → [转换元件] → [信号调理] → 电信号输出
(感受) (转换) (处理)
示例:热电偶温度传感器
温度 → 热电偶结点 → 热电势 → 放大器 → 电压信号
传感器的作用:
- 信息获取:将物理量转换为可测量的电信号
- 系统感知:为控制系统提供反馈信息
- 数据采集:记录环境或设备状态变化
- 自动控制:实现闭环控制的基础
1.2 传感器的分类¶
按输出信号类型分类¶
1. 模拟传感器(Analog Sensor)
输出连续变化的模拟信号(电压、电流、电阻等)。
模拟传感器特点:
优点:
✓ 信息量丰富,分辨率高
✓ 电路简单,成本低
✓ 响应速度快
✓ 适合连续监测
缺点:
✗ 易受干扰
✗ 传输距离受限
✗ 需要ADC转换
✗ 精度受ADC影响
常见类型:
- 热敏电阻(NTC/PTC)
- 光敏电阻(LDR)
- 压力传感器(应变片)
- 电位器(位置传感器)
- 热电偶
- 霍尔传感器(模拟输出)
2. 数字传感器(Digital Sensor)
输出数字信号,通常通过I2C、SPI、UART等接口通信。
数字传感器特点:
优点:
✓ 抗干扰能力强
✓ 传输距离远
✓ 精度高,稳定性好
✓ 集成度高,功能丰富
✓ 易于多传感器组网
缺点:
✗ 成本相对较高
✗ 电路复杂
✗ 需要通信协议支持
✗ 功耗可能较高
常见类型:
- 温湿度传感器(DHT22、SHT3x)
- 加速度计(ADXL345、MPU6050)
- 气压传感器(BMP280)
- 光照传感器(BH1750)
- 距离传感器(VL53L0X)
按测量原理分类¶
传感器测量原理分类:
1. 物理传感器
├── 力学量:加速度、压力、位移、力
├── 热学量:温度、热流
├── 电磁量:电流、电压、磁场
├── 光学量:光强、颜色、红外
└── 声学量:声压、超声波
2. 化学传感器
├── 气体传感器:CO2、CO、甲醛
├── 湿度传感器
├── pH传感器
└── 离子传感器
3. 生物传感器
├── 心率传感器
├── 血氧传感器
├── 葡萄糖传感器
└── 生物电传感器
1.3 传感器的性能指标¶
理解传感器的性能指标对于正确选型和使用至关重要。
静态特性指标¶
1. 测量范围(Range)
传感器能够测量的最小值到最大值的范围。
2. 精度(Accuracy)
测量值与真实值之间的最大偏差。
精度表示方法:
- 绝对精度:±0.5°C
- 相对精度:±1% FS(满量程的1%)
- 分级精度:0.5级、1.0级
示例:
温度传感器精度±0.5°C,测量25°C时:
真实温度范围:24.5°C ~ 25.5°C
3. 分辨率(Resolution)
传感器能够检测到的最小变化量。
分辨率与ADC位数的关系:
12位ADC,参考电压3.3V:
分辨率 = 3.3V / 2^12 = 3.3V / 4096 = 0.8mV
温度传感器输出10mV/°C:
温度分辨率 = 0.8mV / 10mV/°C = 0.08°C
注意:分辨率 ≠ 精度
高分辨率不代表高精度!
4. 灵敏度(Sensitivity)
输出量变化与输入量变化的比值。
灵敏度示例:
温度传感器:10mV/°C
表示温度每变化1°C,输出电压变化10mV
压力传感器:2mV/kPa
表示压力每变化1kPa,输出电压变化2mV
加速度计:0.66mV/g @ 3.3V
表示加速度每变化1g,输出电压变化0.66mV
5. 线性度(Linearity)
传感器实际输出曲线与理想直线的偏离程度。
6. 重复性(Repeatability)
在相同条件下,多次测量同一量值时输出的一致性。
动态特性指标¶
1. 响应时间(Response Time)
传感器从接收输入变化到输出达到稳定值所需的时间。
响应时间定义:
通常定义为达到最终值的63.2%(1个时间常数τ)
或达到最终值的90%、95%的时间
示例:
- 热敏电阻:1-5秒
- 热电偶:0.1-1秒
- 半导体温度传感器:<1秒
- 数字温湿度传感器:2-8秒
2. 频率响应(Frequency Response)
传感器能够准确响应的输入信号频率范围。
频率响应示例:
加速度计:
- 低频:DC ~ 100Hz(适合倾角测量)
- 中频:DC ~ 1kHz(适合振动监测)
- 高频:DC ~ 10kHz(适合冲击测量)
选择原则:
传感器带宽 ≥ 信号最高频率 × 2(奈奎斯特定理)
1.4 传感器选型原则¶
选择合适的传感器需要综合考虑多个因素:
传感器选型决策树:
1. 确定测量需求
├── 测量什么物理量?
├── 测量范围是多少?
├── 需要什么精度?
└── 响应速度要求?
2. 选择传感器类型
├── 模拟 or 数字?
│ ├── 简单应用 → 模拟传感器
│ └── 复杂系统 → 数字传感器
└── 接口类型?
├── I2C(多设备共享总线)
├── SPI(高速传输)
└── 模拟输出(需要ADC)
3. 评估性能指标
├── 精度是否满足要求?
├── 分辨率是否足够?
├── 响应时间是否合适?
└── 工作温度范围?
4. 考虑实际约束
├── 成本预算
├── 功耗限制
├── 尺寸要求
├── 供货稳定性
└── 技术支持
5. 验证兼容性
├── 电源电压兼容?
├── 接口电平兼容?
├── 软件库支持?
└── 开发难度?
选型实例:温度测量
应用场景:室内温度监测
需求分析:
- 测量范围:0°C ~ 50°C
- 精度要求:±1°C
- 响应时间:<10秒
- 成本:低成本
- 数量:单个传感器
方案对比:
┌──────────┬─────────┬────────┬────────┬────────┐
│ 传感器 │ 精度 │ 成本 │ 接口 │ 推荐度 │
├──────────┼─────────┼────────┼────────┼────────┤
│ NTC热敏 │ ±1°C │ 极低 │ 模拟 │ ★★★ │
│ LM35 │ ±0.5°C │ 低 │ 模拟 │ ★★★★ │
│ DS18B20 │ ±0.5°C │ 低 │ 1-Wire │ ★★★★★ │
│ DHT22 │ ±0.5°C │ 中 │ 单总线 │ ★★★★ │
│ SHT30 │ ±0.3°C │ 高 │ I2C │ ★★★ │
└──────────┴─────────┴────────┴────────┴────────┘
推荐方案:
1. 首选:DS18B20(数字输出,精度高,成本低)
2. 备选:LM35(模拟输出,简单易用)
3. 高端:SHT30(同时测温湿度,精度最高)
第二部分:ADC基础与应用¶
2.1 ADC工作原理¶
ADC(Analog-to-Digital Converter,模数转换器) 是将连续变化的模拟信号转换为离散的数字信号的电路。
ADC转换过程¶
ADC转换的四个步骤:
1. 采样(Sampling)
┌─────────────────────────────┐
│ 在特定时刻获取模拟信号的值 │
└─────────────────────────────┘
2. 保持(Holding)
┌─────────────────────────────┐
│ 保持采样值稳定,便于转换 │
└─────────────────────────────┘
3. 量化(Quantization)
┌─────────────────────────────┐
│ 将连续值映射到离散的数字级别│
└─────────────────────────────┘
4. 编码(Encoding)
┌─────────────────────────────┐
│ 将量化值转换为二进制数字 │
└─────────────────────────────┘
示例:3位ADC转换过程
模拟输入:2.1V(参考电压3.3V)
量化级别:2^3 = 8级
每级电压:3.3V / 8 = 0.4125V
量化过程:
2.1V / 0.4125V = 5.09 ≈ 5(量化)
编码结果:5 = 0b101(二进制)
输出数字值:101(3位二进制)
ADC转换类型¶
常见ADC架构对比:
1. 逐次逼近型(SAR ADC)
┌────────────────────────────┐
│ 特点:速度快,功耗低 │
│ 速度:1-5 Msps │
│ 精度:8-18位 │
│ 应用:通用数据采集 │
└────────────────────────────┘
2. Σ-Δ型(Sigma-Delta ADC)
┌────────────────────────────┐
│ 特点:高精度,低速度 │
│ 速度:<1 Msps │
│ 精度:16-24位 │
│ 应用:精密测量、音频 │
└────────────────────────────┘
3. 流水线型(Pipeline ADC)
┌────────────────────────────┐
│ 特点:高速,高功耗 │
│ 速度:>10 Msps │
│ 精度:8-16位 │
│ 应用:视频、通信 │
└────────────────────────────┘
4. 闪速型(Flash ADC)
┌────────────────────────────┐
│ 特点:极高速,高成本 │
│ 速度:>100 Msps │
│ 精度:6-10位 │
│ 应用:高速采样、示波器 │
└────────────────────────────┘
嵌入式MCU常用:SAR ADC
2.2 ADC关键参数¶
分辨率(Resolution)¶
ADC能够区分的最小电压变化,由位数决定。
ADC分辨率计算:
分辨率 = 参考电压 / 2^位数
示例:12位ADC,参考电压3.3V
分辨率 = 3.3V / 2^12 = 3.3V / 4096 = 0.000805V ≈ 0.8mV
常见ADC位数:
┌────────┬──────────┬────────────┬──────────┐
│ 位数 │ 量化级别 │ 分辨率@3.3V│ 应用场景 │
├────────┼──────────┼────────────┼──────────┤
│ 8位 │ 256 │ 12.9mV │ 简单测量 │
│ 10位 │ 1024 │ 3.2mV │ 一般应用 │
│ 12位 │ 4096 │ 0.8mV │ 常用精度 │
│ 16位 │ 65536 │ 50μV │ 高精度 │
│ 24位 │ 16777216 │ 0.2μV │ 超高精度 │
└────────┴──────────┴────────────┴──────────┘
采样率(Sample Rate)¶
ADC每秒能够完成的转换次数,单位:SPS(Samples Per Second)或Hz。
采样率与信号频率的关系:
奈奎斯特采样定理:
采样率 ≥ 2 × 信号最高频率
实际应用建议:
采样率 ≥ 5-10 × 信号最高频率
示例:
测量50Hz工频信号:
- 最低采样率:100 Hz
- 推荐采样率:250-500 Hz
测量心率信号(0.5-5Hz):
- 最低采样率:10 Hz
- 推荐采样率:50-100 Hz
常见MCU ADC采样率:
- STM32F1:1 Msps(12位)
- STM32F4:2.4 Msps(12位)
- STM32H7:3.6 Msps(16位)
转换时间(Conversion Time)¶
完成一次ADC转换所需的时间。
转换时间计算:
转换时间 = 采样时间 + 转换周期
STM32 ADC示例:
采样时间:1.5 ~ 239.5个ADC时钟周期(可配置)
转换周期:12.5个ADC时钟周期(12位ADC)
假设ADC时钟12MHz,采样时间设置为3周期:
转换时间 = (3 + 12.5) / 12MHz = 1.29μs
最大采样率 = 1 / 1.29μs ≈ 775 ksps
优化建议:
- 高阻抗信号:增加采样时间
- 低阻抗信号:减少采样时间
- 高速采集:减少采样时间
- 高精度采集:增加采样时间
精度与误差¶
ADC误差来源:
1. 量化误差(Quantization Error)
┌────────────────────────────┐
│ 固有误差:±0.5 LSB │
│ 无法消除,由位数决定 │
└────────────────────────────┘
2. 偏移误差(Offset Error)
┌────────────────────────────┐
│ 零点偏移 │
│ 可通过校准消除 │
└────────────────────────────┘
3. 增益误差(Gain Error)
┌────────────────────────────┐
│ 斜率偏差 │
│ 可通过校准修正 │
└────────────────────────────┘
4. 非线性误差(INL/DNL)
┌────────────────────────────┐
│ 积分非线性/微分非线性 │
│ 难以校准,选择优质ADC │
└────────────────────────────┘
5. 噪声(Noise)
┌────────────────────────────┐
│ 电源噪声、热噪声 │
│ 通过滤波和屏蔽减小 │
└────────────────────────────┘
总误差计算:
总误差 = √(量化误差² + 偏移误差² + 增益误差² + 非线性误差² + 噪声²)
2.3 STM32 ADC配置¶
以STM32F103为例,讲解ADC的配置方法。
STM32 ADC硬件特性¶
STM32F103 ADC特性:
┌─────────────────────────────────┐
│ • 12位逐次逼近型ADC │
│ • 最多3个ADC控制器 │
│ • 每个ADC最多16个外部通道 │
│ • 2个内部通道(温度、参考电压) │
│ • 转换时间:1μs(1MHz ADC时钟) │
│ • 单次、连续、扫描、间断模式 │
│ • 支持DMA传输 │
│ • 可配置采样时间 │
│ • 模拟看门狗功能 │
│ • 注入通道(4个) │
└─────────────────────────────────┘
ADC通道映射(部分):
ADC1_IN0 → PA0
ADC1_IN1 → PA1
ADC1_IN2 → PA2
ADC1_IN3 → PA3
ADC1_IN4 → PA4
ADC1_IN5 → PA5
ADC1_IN6 → PA6
ADC1_IN7 → PA7
ADC1_IN8 → PB0
ADC1_IN9 → PB1
ADC1_IN10 → PC0
ADC1_IN11 → PC1
...
ADC初始化配置¶
步骤1:使能时钟
// ADC时钟配置
void ADC_Clock_Config(void)
{
// 使能ADC1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
// 使能GPIO时钟(假设使用PA0)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置ADC时钟分频
// PCLK2 = 72MHz, ADC时钟 = 72MHz/6 = 12MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
}
步骤2:配置GPIO
// GPIO配置为模拟输入
void ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 配置PA0为模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
步骤3:配置ADC参数
// ADC基本配置
void ADC_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
// ADC配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; // 1个通道
ADC_Init(ADC1, &ADC_InitStructure);
// 配置ADC通道0,采样时间55.5周期
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 使能ADC
ADC_Cmd(ADC1, ENABLE);
// ADC校准
ADC_ResetCalibration(ADC1); // 复位校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); // 等待复位完成
ADC_StartCalibration(ADC1); // 开始校准
while(ADC_GetCalibrationStatus(ADC1)); // 等待校准完成
}
完整初始化函数
// ADC完整初始化
void ADC_Init_Config(void)
{
ADC_Clock_Config(); // 时钟配置
ADC_GPIO_Config(); // GPIO配置
ADC_Mode_Config(); // ADC模式配置
}
2.4 ADC数据读取¶
单次转换模式¶
/**
* @brief 读取ADC单次转换结果
* @param ADCx: ADC外设(ADC1/ADC2/ADC3)
* @param channel: ADC通道
* @retval ADC转换值(0-4095)
*/
uint16_t ADC_Read_Single(ADC_TypeDef* ADCx, uint8_t channel)
{
// 配置通道
ADC_RegularChannelConfig(ADCx, channel, 1, ADC_SampleTime_55Cycles5);
// 启动转换
ADC_SoftwareStartConvCmd(ADCx, ENABLE);
// 等待转换完成
while(!ADC_GetFlagStatus(ADCx, ADC_FLAG_EOC));
// 读取转换结果
return ADC_GetConversionValue(ADCx);
}
// 使用示例
void ADC_Single_Example(void)
{
uint16_t adc_value;
float voltage;
// 读取ADC值
adc_value = ADC_Read_Single(ADC1, ADC_Channel_0);
// 转换为电压值(参考电压3.3V)
voltage = (float)adc_value * 3.3f / 4096.0f;
printf("ADC Value: %d, Voltage: %.3fV\n", adc_value, voltage);
}
多次采样平均¶
/**
* @brief 读取ADC多次采样平均值
* @param ADCx: ADC外设
* @param channel: ADC通道
* @param times: 采样次数
* @retval ADC平均值
*/
uint16_t ADC_Read_Average(ADC_TypeDef* ADCx, uint8_t channel, uint8_t times)
{
uint32_t sum = 0;
uint8_t i;
for(i = 0; i < times; i++)
{
sum += ADC_Read_Single(ADCx, channel);
// 延时,避免采样过快
delay_us(10);
}
return (uint16_t)(sum / times);
}
// 使用示例
void ADC_Average_Example(void)
{
uint16_t adc_value;
float voltage;
// 读取10次平均值
adc_value = ADC_Read_Average(ADC1, ADC_Channel_0, 10);
// 转换为电压值
voltage = (float)adc_value * 3.3f / 4096.0f;
printf("ADC Average: %d, Voltage: %.3fV\n", adc_value, voltage);
}
连续转换模式¶
// 配置ADC连续转换模式
void ADC_Continuous_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
// 时钟和GPIO配置(同前)
ADC_Clock_Config();
ADC_GPIO_Config();
// ADC配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// 配置通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 使能ADC
ADC_Cmd(ADC1, ENABLE);
// 校准
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
// 启动连续转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
// 读取连续转换结果
uint16_t ADC_Read_Continuous(void)
{
// 等待转换完成
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
// 读取结果(读取后自动清除EOC标志)
return ADC_GetConversionValue(ADC1);
}
第三部分:传感器接口设计¶
3.1 模拟传感器接口电路¶
模拟传感器输出的信号通常需要经过信号调理才能被ADC正确采集。
电阻式传感器接口¶
热敏电阻(NTC)温度测量
NTC热敏电阻分压电路:
VCC (3.3V)
│
┌┴┐
│ │ R1 (10kΩ 固定电阻)
└┬┘
├────→ Vout (接ADC)
┌┴┐
│ │ RT (NTC热敏电阻)
└┬┘
│
GND
电压计算:
Vout = VCC × RT / (R1 + RT)
温度计算(B值方程):
T = 1 / (1/T0 + (1/B) × ln(RT/R0))
其中:
T0 = 298.15K (25°C)
R0 = 10kΩ (25°C时的电阻)
B = 3950 (B值,查数据手册)
NTC温度测量代码实现
/**
* @brief NTC温度传感器配置
*/
#define NTC_ADC_CHANNEL ADC_Channel_0
#define NTC_R1 10000.0f // 上拉电阻10kΩ
#define NTC_R0 10000.0f // 25°C时NTC电阻
#define NTC_B 3950.0f // B值
#define NTC_T0 298.15f // 25°C = 298.15K
/**
* @brief 读取NTC温度
* @retval 温度值(摄氏度)
*/
float NTC_Read_Temperature(void)
{
uint16_t adc_value;
float voltage, rt, temperature;
// 读取ADC值(多次平均)
adc_value = ADC_Read_Average(ADC1, NTC_ADC_CHANNEL, 10);
// 转换为电压
voltage = (float)adc_value * 3.3f / 4096.0f;
// 计算NTC电阻值
// Vout = VCC × RT / (R1 + RT)
// RT = R1 × Vout / (VCC - Vout)
rt = NTC_R1 * voltage / (3.3f - voltage);
// 使用B值方程计算温度
// T = 1 / (1/T0 + (1/B) × ln(RT/R0))
temperature = 1.0f / (1.0f/NTC_T0 + (1.0f/NTC_B) * logf(rt/NTC_R0));
// 转换为摄氏度
temperature = temperature - 273.15f;
return temperature;
}
// 使用示例
void NTC_Example(void)
{
float temperature;
while(1)
{
temperature = NTC_Read_Temperature();
printf("Temperature: %.2f°C\n", temperature);
delay_ms(1000);
}
}
光敏电阻(LDR)光照测量
LDR光敏电阻电路:
VCC (3.3V)
│
┌┴┐
│ │ R1 (10kΩ)
└┬┘
├────→ Vout (接ADC)
┌┴┐
│ │ LDR (光敏电阻)
└┬┘
│
GND
特性:
- 亮光下:LDR电阻小(几百欧姆),Vout低
- 暗光下:LDR电阻大(几兆欧姆),Vout高
光照强度计算:
Lux = k × (Vout / (VCC - Vout))^n
k, n为校准系数,需要实际测量确定
LDR光照测量代码
/**
* @brief 读取光照强度
* @retval 光照强度(相对值0-100)
*/
uint8_t LDR_Read_Light(void)
{
uint16_t adc_value;
uint8_t light_level;
// 读取ADC值
adc_value = ADC_Read_Average(ADC1, ADC_Channel_1, 10);
// 转换为0-100的相对值
// ADC值越大,光照越强
light_level = (uint8_t)(adc_value * 100 / 4095);
return light_level;
}
电压输出传感器接口¶
LM35温度传感器
LM35温度传感器接口:
LM35引脚:
1. VCC (4V-30V,推荐5V)
2. Vout (输出电压)
3. GND
输出特性:
Vout = 10mV/°C
例如:
25°C → 250mV
100°C → 1000mV
电路连接:
VCC (5V) ────┬──── LM35 Pin1
│
┌┴┐ 0.1μF
└┬┘ (去耦电容)
│
GND ─────────┴──── LM35 Pin3
LM35 Pin2 ────→ ADC输入
注意:
1. 如果MCU是3.3V系统,需要分压或使用3.3V参考电压
2. 测量负温度需要特殊电路
LM35温度读取代码
/**
* @brief 读取LM35温度
* @retval 温度值(摄氏度)
*/
float LM35_Read_Temperature(void)
{
uint16_t adc_value;
float voltage, temperature;
// 读取ADC值
adc_value = ADC_Read_Average(ADC1, ADC_Channel_2, 10);
// 转换为电压(假设参考电压3.3V)
voltage = (float)adc_value * 3.3f / 4096.0f;
// 计算温度(10mV/°C)
temperature = voltage / 0.01f;
return temperature;
}
电流输出传感器接口¶
4-20mA电流环传感器
4-20mA电流环接口电路:
传感器输出 ────┬──→ 电流环路
│
┌┴┐
│ │ R (250Ω 采样电阻)
└┬┘
├────→ Vout (接ADC)
│
GND
电压转换:
Vout = I × R
4mA → 1.0V
12mA → 3.0V
20mA → 5.0V
注意:
1. 采样电阻功率:P = I² × R = 0.02² × 250 = 0.1W
2. 如果Vout超过ADC输入范围,需要分压
3. 使用精密电阻(0.1%精度)
4-20mA传感器读取代码
/**
* @brief 读取4-20mA传感器值
* @param min_value: 4mA对应的物理量最小值
* @param max_value: 20mA对应的物理量最大值
* @retval 物理量值
*/
float Read_4_20mA_Sensor(float min_value, float max_value)
{
uint16_t adc_value;
float voltage, current, value;
// 读取ADC值
adc_value = ADC_Read_Average(ADC1, ADC_Channel_3, 10);
// 转换为电压
voltage = (float)adc_value * 3.3f / 4096.0f;
// 计算电流(采样电阻250Ω)
current = voltage / 250.0f;
// 转换为物理量
// 4mA对应min_value,20mA对应max_value
value = min_value + (current - 0.004f) * (max_value - min_value) / 0.016f;
return value;
}
// 使用示例:压力传感器(0-100kPa)
void Pressure_Sensor_Example(void)
{
float pressure;
// 4mA对应0kPa,20mA对应100kPa
pressure = Read_4_20mA_Sensor(0.0f, 100.0f);
printf("Pressure: %.2f kPa\n", pressure);
}
3.2 数字传感器接口¶
I2C接口传感器¶
I2C总线特点
I2C总线优势:
✓ 只需2根线(SCL、SDA)
✓ 支持多主机、多从机
✓ 地址寻址,最多128个设备
✓ 标准速度100kHz,快速400kHz,高速3.4MHz
✓ 广泛应用于传感器通信
I2C总线连接:
MCU 传感器1 传感器2
(地址0x48) (地址0x76)
4.7kΩ
VCC ──────┬────────────┬──────────────┬────
│ │ │
┌┴┐ VCC VCC
│ │
└┬┘
│
SCL ──────┴────────────┼──────────────┼────
SCL SCL
4.7kΩ
VCC ──────┬────────────┬──────────────┬────
│ │ │
┌┴┐ SDA SDA
│ │
└┬┘
│
SDA ──────┴────────────┼──────────────┼────
SDA SDA
GND ───────────────────┴──────────────┴────
GND GND
注意:
1. 需要上拉电阻(2.2kΩ-10kΩ)
2. 总线长度<1米(标准速度)
3. 每个设备需要唯一地址
I2C传感器读取示例(BMP280气压传感器)
/**
* @brief BMP280寄存器地址
*/
#define BMP280_ADDR 0x76 // I2C地址
#define BMP280_REG_TEMP_MSB 0xFA // 温度数据MSB
#define BMP280_REG_TEMP_LSB 0xFB // 温度数据LSB
#define BMP280_REG_TEMP_XLSB 0xFC // 温度数据XLSB
#define BMP280_REG_PRESS_MSB 0xF7 // 压力数据MSB
#define BMP280_REG_PRESS_LSB 0xF8 // 压力数据LSB
#define BMP280_REG_PRESS_XLSB 0xF9 // 压力数据XLSB
#define BMP280_REG_CTRL_MEAS 0xF4 // 控制寄存器
/**
* @brief BMP280初始化
*/
void BMP280_Init(void)
{
uint8_t config;
// 配置测量模式
// osrs_t=001(温度过采样x1), osrs_p=001(压力过采样x1), mode=11(正常模式)
config = 0x27;
I2C_Write_Byte(BMP280_ADDR, BMP280_REG_CTRL_MEAS, config);
delay_ms(10);
}
/**
* @brief 读取BMP280温度
* @retval 温度值(摄氏度)
*/
float BMP280_Read_Temperature(void)
{
uint8_t data[3];
int32_t adc_T;
float temperature;
// 读取温度原始数据
I2C_Read_Bytes(BMP280_ADDR, BMP280_REG_TEMP_MSB, data, 3);
// 组合20位ADC值
adc_T = ((int32_t)data[0] << 12) | ((int32_t)data[1] << 4) | ((int32_t)data[2] >> 4);
// 温度补偿计算(简化版,实际需要使用校准参数)
temperature = (float)adc_T / 5120.0f;
return temperature;
}
/**
* @brief 读取BMP280气压
* @retval 气压值(Pa)
*/
float BMP280_Read_Pressure(void)
{
uint8_t data[3];
int32_t adc_P;
float pressure;
// 读取压力原始数据
I2C_Read_Bytes(BMP280_ADDR, BMP280_REG_PRESS_MSB, data, 3);
// 组合20位ADC值
adc_P = ((int32_t)data[0] << 12) | ((int32_t)data[1] << 4) | ((int32_t)data[2] >> 4);
// 压力补偿计算(简化版)
pressure = (float)adc_P / 256.0f;
return pressure;
}
SPI接口传感器¶
SPI总线特点
SPI总线优势:
✓ 全双工通信
✓ 速度快(可达几十MHz)
✓ 硬件简单
✓ 适合高速数据传输
SPI总线连接:
MCU 传感器
SCK ─────────────────→ SCK (时钟)
MOSI ─────────────────→ MOSI (主机输出)
MISO ←───────────────── MISO (主机输入)
CS ─────────────────→ CS (片选)
VCC ─────────────────→ VCC
GND ─────────────────→ GND
多从机连接:
每个从机需要独立的CS线
MCU 传感器1 传感器2
SCK ───────┬──────────┬────
│ │
MOSI ───────┼──────────┼────
│ │
MISO ───────┼──────────┼────
│ │
CS1 ───────┘ │
CS2 ──────────────────┘
SPI传感器读取示例(ADXL345加速度计)
/**
* @brief ADXL345寄存器地址
*/
#define ADXL345_REG_DEVID 0x00 // 设备ID
#define ADXL345_REG_POWER_CTL 0x2D // 电源控制
#define ADXL345_REG_DATA_FORMAT 0x31 // 数据格式
#define ADXL345_REG_DATAX0 0x32 // X轴数据LSB
#define ADXL345_REG_DATAX1 0x33 // X轴数据MSB
#define ADXL345_REG_DATAY0 0x34 // Y轴数据LSB
#define ADXL345_REG_DATAY1 0x35 // Y轴数据MSB
#define ADXL345_REG_DATAZ0 0x36 // Z轴数据LSB
#define ADXL345_REG_DATAZ1 0x37 // Z轴数据MSB
/**
* @brief ADXL345写寄存器
*/
void ADXL345_Write_Reg(uint8_t reg, uint8_t value)
{
CS_LOW(); // 片选拉低
SPI_ReadWrite_Byte(reg); // 发送寄存器地址
SPI_ReadWrite_Byte(value); // 发送数据
CS_HIGH(); // 片选拉高
}
/**
* @brief ADXL345读寄存器
*/
uint8_t ADXL345_Read_Reg(uint8_t reg)
{
uint8_t value;
CS_LOW();
SPI_ReadWrite_Byte(reg | 0x80); // 读操作,最高位置1
value = SPI_ReadWrite_Byte(0xFF); // 读取数据
CS_HIGH();
return value;
}
/**
* @brief ADXL345初始化
*/
void ADXL345_Init(void)
{
uint8_t device_id;
// 读取设备ID验证通信
device_id = ADXL345_Read_Reg(ADXL345_REG_DEVID);
if(device_id != 0xE5)
{
printf("ADXL345 not found!\n");
return;
}
// 配置数据格式:±2g,全分辨率
ADXL345_Write_Reg(ADXL345_REG_DATA_FORMAT, 0x08);
// 启动测量
ADXL345_Write_Reg(ADXL345_REG_POWER_CTL, 0x08);
delay_ms(10);
}
/**
* @brief 读取ADXL345加速度数据
*/
void ADXL345_Read_Accel(int16_t *x, int16_t *y, int16_t *z)
{
uint8_t data[6];
// 连续读取6个字节
CS_LOW();
SPI_ReadWrite_Byte(ADXL345_REG_DATAX0 | 0x80 | 0x40); // 读+多字节
data[0] = SPI_ReadWrite_Byte(0xFF);
data[1] = SPI_ReadWrite_Byte(0xFF);
data[2] = SPI_ReadWrite_Byte(0xFF);
data[3] = SPI_ReadWrite_Byte(0xFF);
data[4] = SPI_ReadWrite_Byte(0xFF);
data[5] = SPI_ReadWrite_Byte(0xFF);
CS_HIGH();
// 组合16位数据
*x = (int16_t)((data[1] << 8) | data[0]);
*y = (int16_t)((data[3] << 8) | data[2]);
*z = (int16_t)((data[5] << 8) | data[4]);
}
// 使用示例
void ADXL345_Example(void)
{
int16_t x, y, z;
float ax, ay, az;
ADXL345_Init();
while(1)
{
ADXL345_Read_Accel(&x, &y, &z);
// 转换为g值(±2g范围,灵敏度3.9mg/LSB)
ax = (float)x * 0.0039f;
ay = (float)y * 0.0039f;
az = (float)z * 0.0039f;
printf("Accel: X=%.2fg, Y=%.2fg, Z=%.2fg\n", ax, ay, az);
delay_ms(100);
}
}
3.3 信号调理电路¶
运算放大器(Op-Amp)应用¶
信号放大电路
同相放大器:
R2
┌────┐┌┐────┐
│ └┘ │
Vin ─┤+ │
│ Op-Amp ├─→ Vout
─┤- │
│└─────────┘
│ R1
┌┴┐
│ │
└┬┘
│
GND
增益:Av = 1 + R2/R1
示例:将10mV信号放大到1V
需要增益:Av = 1V / 10mV = 100
选择:R1 = 1kΩ, R2 = 99kΩ
实际增益:Av = 1 + 99/1 = 100
差分放大器
差分放大器(用于消除共模噪声):
R2
┌────┐┌┐────┐
│ └┘ │
V1 ─┤+ │
│ Op-Amp ├─→ Vout
V2 ─┤- │
│└─────────┘
│ R1
┌┴┐
│ │
└┬┘
│
GND
输出:Vout = (V1 - V2) × (1 + R2/R1)
应用:
- 桥式传感器信号放大
- 消除共模干扰
- 提高信噪比
滤波电路¶
RC低通滤波器
RC低通滤波器(硬件滤波):
Vin ───┬───┐
│ │
┌┴┐ │
R │ │ │
└┬┘ │
├───┼──→ Vout
┌┴┐ │
C ─ ─ │
└┬┘ │
│ │
GND ─┘
截止频率:fc = 1 / (2π × R × C)
设计示例:
需要截止频率100Hz,选择R=10kΩ
C = 1 / (2π × 10000 × 100) = 0.159μF
选择标准值:C = 0.15μF
作用:
- 滤除高频噪声
- 防止混叠(ADC采样前)
- 平滑信号
3.4 抗干扰设计¶
传感器抗干扰措施:
1. 硬件措施
├── 电源滤波
│ ├── 去耦电容(0.1μF + 10μF)
│ ├── 磁珠隔离
│ └── LDO稳压
│
├── 信号线屏蔽
│ ├── 屏蔽线缆
│ ├── 双绞线
│ └── 接地良好
│
├── PCB布局
│ ├── 模拟地与数字地分离
│ ├── 信号线远离干扰源
│ ├── 地平面完整
│ └── 走线尽量短
│
└── 保护电路
├── TVS管(瞬态抑制)
├── ESD保护
└── 过压保护
2. 软件措施
├── 数字滤波
├── 多次采样平均
├── 异常值剔除
└── 看门狗保护
第四部分:数据采集编程¶
4.1 ADC单次采集¶
单次采集模式适用于偶尔需要读取传感器数据的场景。
/**
* @brief ADC单次采集完整示例
*/
// ADC配置结构
typedef struct {
ADC_TypeDef* ADCx;
uint8_t channel;
float vref;
uint8_t resolution;
} ADC_Config_t;
// 全局配置
ADC_Config_t adc_config = {
.ADCx = ADC1,
.channel = ADC_Channel_0,
.vref = 3.3f,
.resolution = 12
};
/**
* @brief ADC单次采集初始化
*/
void ADC_Single_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 72MHz/6 = 12MHz
// 2. 配置GPIO为模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置ADC
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// 4. 配置通道采样时间
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 5. 使能ADC
ADC_Cmd(ADC1, ENABLE);
// 6. ADC校准
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
}
/**
* @brief 读取ADC原始值
*/
uint16_t ADC_Read_Raw(void)
{
// 启动转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// 等待转换完成
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
// 读取并返回结果
return ADC_GetConversionValue(ADC1);
}
/**
* @brief 读取ADC电压值
*/
float ADC_Read_Voltage(void)
{
uint16_t raw_value;
float voltage;
raw_value = ADC_Read_Raw();
// 转换为电压
voltage = (float)raw_value * adc_config.vref / 4096.0f;
return voltage;
}
/**
* @brief 传感器数据结构
*/
typedef struct {
uint16_t raw;
float voltage;
float physical_value;
uint32_t timestamp;
} Sensor_Data_t;
/**
* @brief 读取传感器数据(带时间戳)
*/
void Sensor_Read_Data(Sensor_Data_t *data)
{
data->raw = ADC_Read_Raw();
data->voltage = (float)data->raw * adc_config.vref / 4096.0f;
data->timestamp = HAL_GetTick(); // 获取时间戳
// 根据传感器类型转换为物理量
// 这里以温度传感器为例(10mV/°C)
data->physical_value = data->voltage / 0.01f;
}
// 使用示例
void ADC_Single_Example(void)
{
Sensor_Data_t sensor_data;
ADC_Single_Init();
while(1)
{
Sensor_Read_Data(&sensor_data);
printf("[%lu ms] Raw: %d, Voltage: %.3fV, Temp: %.2f°C\n",
sensor_data.timestamp,
sensor_data.raw,
sensor_data.voltage,
sensor_data.physical_value);
delay_ms(1000);
}
}
4.2 ADC连续采集¶
连续采集模式适用于需要持续监测传感器数据的场景。
/**
* @brief ADC连续采集配置
*/
void ADC_Continuous_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 2. 配置GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置ADC
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// 4. 配置通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 5. 使能ADC中断
ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
// 6. 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 7. 使能ADC
ADC_Cmd(ADC1, ENABLE);
// 8. 校准
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
// 9. 启动连续转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
// 全局变量存储ADC值
volatile uint16_t adc_value = 0;
volatile uint8_t adc_ready = 0;
/**
* @brief ADC中断服务函数
*/
void ADC1_2_IRQHandler(void)
{
if(ADC_GetITStatus(ADC1, ADC_IT_EOC) != RESET)
{
// 读取ADC值
adc_value = ADC_GetConversionValue(ADC1);
adc_ready = 1;
// 清除中断标志
ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
}
}
// 使用示例
void ADC_Continuous_Example(void)
{
float voltage;
ADC_Continuous_Init();
while(1)
{
if(adc_ready)
{
adc_ready = 0;
voltage = (float)adc_value * 3.3f / 4096.0f;
printf("Voltage: %.3fV\n", voltage);
}
// 可以执行其他任务
// ...
}
}
4.3 DMA传输¶
使用DMA可以实现ADC数据的自动传输,减轻CPU负担。
/**
* @brief ADC + DMA配置
*/
#define ADC_DMA_BUFFER_SIZE 100 // DMA缓冲区大小
uint16_t adc_dma_buffer[ADC_DMA_BUFFER_SIZE]; // DMA缓冲区
/**
* @brief DMA初始化
*/
void ADC_DMA_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
// 使能DMA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 配置DMA
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; // 外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_dma_buffer; // 内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 外设到内存
DMA_InitStructure.DMA_BufferSize = ADC_DMA_BUFFER_SIZE; // 缓冲区大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 非内存到内存
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
// 使能DMA传输完成中断
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
// 使能DMA通道
DMA_Cmd(DMA1_Channel1, ENABLE);
}
/**
* @brief ADC + DMA完整配置
*/
void ADC_DMA_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 2. 配置GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置DMA
ADC_DMA_Init();
// 4. 配置DMA中断
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 5. 配置ADC
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// 6. 配置通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 7. 使能ADC DMA
ADC_DMACmd(ADC1, ENABLE);
// 8. 使能ADC
ADC_Cmd(ADC1, ENABLE);
// 9. 校准
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
// 10. 启动转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
volatile uint8_t dma_transfer_complete = 0;
/**
* @brief DMA中断服务函数
*/
void DMA1_Channel1_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC1) != RESET)
{
dma_transfer_complete = 1;
DMA_ClearITPendingBit(DMA1_IT_TC1);
}
}
/**
* @brief 计算缓冲区平均值
*/
uint16_t Calculate_Average(uint16_t *buffer, uint16_t size)
{
uint32_t sum = 0;
uint16_t i;
for(i = 0; i < size; i++)
{
sum += buffer[i];
}
return (uint16_t)(sum / size);
}
// 使用示例
void ADC_DMA_Example(void)
{
uint16_t average_value;
float voltage;
ADC_DMA_Config();
while(1)
{
if(dma_transfer_complete)
{
dma_transfer_complete = 0;
// 计算平均值
average_value = Calculate_Average(adc_dma_buffer, ADC_DMA_BUFFER_SIZE);
// 转换为电压
voltage = (float)average_value * 3.3f / 4096.0f;
printf("Average Voltage: %.3fV\n", voltage);
}
delay_ms(100);
}
}
4.4 多通道采集¶
多通道采集用于同时监测多个传感器。
/**
* @brief 多通道ADC配置
*/
#define ADC_CHANNEL_NUM 4 // 通道数量
uint16_t adc_multi_buffer[ADC_CHANNEL_NUM]; // 多通道缓冲区
/**
* @brief 多通道ADC + DMA配置
*/
void ADC_MultiChannel_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 2. 配置GPIO(PA0-PA3)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置DMA
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_multi_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = ADC_CHANNEL_NUM;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
// 4. 配置ADC
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = ADC_CHANNEL_NUM;
ADC_Init(ADC1, &ADC_InitStructure);
// 5. 配置各个通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
// 6. 使能ADC DMA
ADC_DMACmd(ADC1, ENABLE);
// 7. 使能ADC
ADC_Cmd(ADC1, ENABLE);
// 8. 校准
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
// 9. 启动转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
/**
* @brief 多通道传感器数据结构
*/
typedef struct {
float channel[ADC_CHANNEL_NUM];
uint32_t timestamp;
} MultiChannel_Data_t;
/**
* @brief 读取多通道数据
*/
void Read_MultiChannel_Data(MultiChannel_Data_t *data)
{
uint8_t i;
for(i = 0; i < ADC_CHANNEL_NUM; i++)
{
data->channel[i] = (float)adc_multi_buffer[i] * 3.3f / 4096.0f;
}
data->timestamp = HAL_GetTick();
}
// 使用示例
void ADC_MultiChannel_Example(void)
{
MultiChannel_Data_t sensor_data;
ADC_MultiChannel_Init();
while(1)
{
Read_MultiChannel_Data(&sensor_data);
printf("[%lu ms] CH0: %.3fV, CH1: %.3fV, CH2: %.3fV, CH3: %.3fV\n",
sensor_data.timestamp,
sensor_data.channel[0],
sensor_data.channel[1],
sensor_data.channel[2],
sensor_data.channel[3]);
delay_ms(500);
}
}
第五部分:传感器校准¶
5.1 校准原理与方法¶
传感器校准是提高测量精度的重要手段,通过校准可以消除系统误差。
校准的目的:
1. 消除偏移误差(零点漂移)
2. 修正增益误差(斜率偏差)
3. 补偿非线性误差
4. 提高测量精度
校准方法分类:
┌────────────────────────────────┐
│ 1. 单点校准 │
│ - 消除偏移误差 │
│ - 适用于线性传感器 │
│ - 简单快速 │
└────────────────────────────────┘
┌────────────────────────────────┐
│ 2. 两点校准 │
│ - 消除偏移和增益误差 │
│ - 最常用的校准方法 │
│ - 精度较高 │
└────────────────────────────────┘
┌────────────────────────────────┐
│ 3. 多点校准 │
│ - 补偿非线性误差 │
│ - 使用查表或拟合曲线 │
│ - 精度最高 │
└────────────────────────────────┘
5.2 单点校准¶
单点校准用于消除零点偏移。
/**
* @brief 单点校准数据结构
*/
typedef struct {
float offset; // 偏移量
uint8_t calibrated; // 校准标志
} OnePoint_Calib_t;
OnePoint_Calib_t calib_data = {0};
/**
* @brief 单点校准
* @param reference_value: 参考值(已知的真实值)
*/
void OnePoint_Calibration(float reference_value)
{
uint16_t adc_value;
float measured_value;
// 读取当前测量值
adc_value = ADC_Read_Average(ADC1, ADC_Channel_0, 100);
measured_value = (float)adc_value * 3.3f / 4096.0f;
// 计算偏移量
calib_data.offset = reference_value - measured_value;
calib_data.calibrated = 1;
printf("Calibration completed. Offset: %.3f\n", calib_data.offset);
}
/**
* @brief 应用单点校准
* @param raw_value: 原始测量值
* @retval 校准后的值
*/
float Apply_OnePoint_Calibration(float raw_value)
{
if(calib_data.calibrated)
{
return raw_value + calib_data.offset;
}
else
{
return raw_value;
}
}
// 使用示例:温度传感器零点校准
void OnePoint_Calib_Example(void)
{
float temperature;
printf("Place sensor in 0°C environment and press key...\n");
WaitForKey();
// 在0°C环境下进行校准
OnePoint_Calibration(0.0f);
// 测量
while(1)
{
temperature = LM35_Read_Temperature();
temperature = Apply_OnePoint_Calibration(temperature);
printf("Calibrated Temperature: %.2f°C\n", temperature);
delay_ms(1000);
}
}
5.3 两点校准¶
两点校准可以同时消除偏移误差和增益误差,是最常用的校准方法。
/**
* @brief 两点校准数据结构
*/
typedef struct {
float k; // 增益系数(斜率)
float b; // 偏移量(截距)
uint8_t calibrated; // 校准标志
} TwoPoint_Calib_t;
TwoPoint_Calib_t two_point_calib = {0};
/**
* @brief 两点校准
* @param ref_value1: 第一个参考值
* @param ref_value2: 第二个参考值
*/
void TwoPoint_Calibration(float ref_value1, float ref_value2)
{
uint16_t adc1, adc2;
float measured1, measured2;
printf("Place sensor at first reference point and press key...\n");
WaitForKey();
// 第一个校准点
adc1 = ADC_Read_Average(ADC1, ADC_Channel_0, 100);
measured1 = (float)adc1 * 3.3f / 4096.0f;
printf("Point 1: ADC=%d, Voltage=%.3fV, Reference=%.2f\n",
adc1, measured1, ref_value1);
printf("Place sensor at second reference point and press key...\n");
WaitForKey();
// 第二个校准点
adc2 = ADC_Read_Average(ADC1, ADC_Channel_0, 100);
measured2 = (float)adc2 * 3.3f / 4096.0f;
printf("Point 2: ADC=%d, Voltage=%.3fV, Reference=%.2f\n",
adc2, measured2, ref_value2);
// 计算线性方程参数:y = kx + b
// k = (y2 - y1) / (x2 - x1)
// b = y1 - k * x1
two_point_calib.k = (ref_value2 - ref_value1) / (measured2 - measured1);
two_point_calib.b = ref_value1 - two_point_calib.k * measured1;
two_point_calib.calibrated = 1;
printf("Calibration completed. k=%.4f, b=%.4f\n",
two_point_calib.k, two_point_calib.b);
}
/**
* @brief 应用两点校准
* @param raw_value: 原始测量值
* @retval 校准后的值
*/
float Apply_TwoPoint_Calibration(float raw_value)
{
if(two_point_calib.calibrated)
{
// y = kx + b
return two_point_calib.k * raw_value + two_point_calib.b;
}
else
{
return raw_value;
}
}
// 使用示例:温度传感器两点校准
void TwoPoint_Calib_Example(void)
{
float temperature;
// 在0°C和100°C环境下进行两点校准
TwoPoint_Calibration(0.0f, 100.0f);
// 测量
while(1)
{
temperature = LM35_Read_Temperature();
temperature = Apply_TwoPoint_Calibration(temperature);
printf("Calibrated Temperature: %.2f°C\n", temperature);
delay_ms(1000);
}
}
5.4 多点校准与曲线拟合¶
对于非线性传感器,需要使用多点校准。
/**
* @brief 多点校准数据结构
*/
#define MAX_CALIB_POINTS 10
typedef struct {
float x[MAX_CALIB_POINTS]; // 测量值
float y[MAX_CALIB_POINTS]; // 参考值
uint8_t num_points; // 校准点数量
uint8_t calibrated; // 校准标志
} MultiPoint_Calib_t;
MultiPoint_Calib_t multi_calib = {0};
/**
* @brief 添加校准点
* @param measured: 测量值
* @param reference: 参考值
*/
void Add_Calibration_Point(float measured, float reference)
{
if(multi_calib.num_points < MAX_CALIB_POINTS)
{
multi_calib.x[multi_calib.num_points] = measured;
multi_calib.y[multi_calib.num_points] = reference;
multi_calib.num_points++;
printf("Added point %d: x=%.3f, y=%.3f\n",
multi_calib.num_points, measured, reference);
}
}
/**
* @brief 完成多点校准
*/
void Complete_MultiPoint_Calibration(void)
{
if(multi_calib.num_points >= 2)
{
multi_calib.calibrated = 1;
printf("Multi-point calibration completed with %d points\n",
multi_calib.num_points);
}
else
{
printf("Error: Need at least 2 calibration points\n");
}
}
/**
* @brief 线性插值
* @param x: 输入值
* @retval 插值结果
*/
float Linear_Interpolation(float x)
{
uint8_t i;
float x1, x2, y1, y2;
// 查找x所在的区间
for(i = 0; i < multi_calib.num_points - 1; i++)
{
if(x >= multi_calib.x[i] && x <= multi_calib.x[i+1])
{
x1 = multi_calib.x[i];
x2 = multi_calib.x[i+1];
y1 = multi_calib.y[i];
y2 = multi_calib.y[i+1];
// 线性插值:y = y1 + (x - x1) * (y2 - y1) / (x2 - x1)
return y1 + (x - x1) * (y2 - y1) / (x2 - x1);
}
}
// 超出范围,使用最近的点
if(x < multi_calib.x[0])
{
return multi_calib.y[0];
}
else
{
return multi_calib.y[multi_calib.num_points - 1];
}
}
/**
* @brief 应用多点校准
* @param raw_value: 原始测量值
* @retval 校准后的值
*/
float Apply_MultiPoint_Calibration(float raw_value)
{
if(multi_calib.calibrated)
{
return Linear_Interpolation(raw_value);
}
else
{
return raw_value;
}
}
// 使用示例:NTC温度传感器多点校准
void MultiPoint_Calib_Example(void)
{
float temperature, voltage;
uint8_t i;
// 校准过程:在多个已知温度点进行测量
float ref_temps[] = {0, 25, 50, 75, 100}; // 参考温度
printf("Starting multi-point calibration...\n");
for(i = 0; i < 5; i++)
{
printf("Place sensor at %.0f°C and press key...\n", ref_temps[i]);
WaitForKey();
voltage = ADC_Read_Voltage();
Add_Calibration_Point(voltage, ref_temps[i]);
}
Complete_MultiPoint_Calibration();
// 测量
while(1)
{
voltage = ADC_Read_Voltage();
temperature = Apply_MultiPoint_Calibration(voltage);
printf("Voltage: %.3fV, Calibrated Temperature: %.2f°C\n",
voltage, temperature);
delay_ms(1000);
}
}
校准数据保存与加载
/**
* @brief 保存校准数据到Flash
*/
void Save_Calibration_Data(void)
{
// 解锁Flash
FLASH_Unlock();
// 擦除页
FLASH_ErasePage(CALIB_DATA_ADDR);
// 写入校准数据
FLASH_ProgramWord(CALIB_DATA_ADDR, *(uint32_t*)&two_point_calib.k);
FLASH_ProgramWord(CALIB_DATA_ADDR + 4, *(uint32_t*)&two_point_calib.b);
FLASH_ProgramWord(CALIB_DATA_ADDR + 8, two_point_calib.calibrated);
// 锁定Flash
FLASH_Lock();
printf("Calibration data saved to Flash\n");
}
/**
* @brief 从Flash加载校准数据
*/
void Load_Calibration_Data(void)
{
uint32_t *addr = (uint32_t*)CALIB_DATA_ADDR;
two_point_calib.k = *(float*)addr;
two_point_calib.b = *(float*)(addr + 1);
two_point_calib.calibrated = *(uint8_t*)(addr + 2);
if(two_point_calib.calibrated)
{
printf("Calibration data loaded: k=%.4f, b=%.4f\n",
two_point_calib.k, two_point_calib.b);
}
else
{
printf("No calibration data found\n");
}
}
第六部分:数据滤波算法¶
6.1 移动平均滤波¶
移动平均滤波是最简单常用的滤波算法,适用于周期性干扰。
简单移动平均¶
/**
* @brief 简单移动平均滤波
*/
#define FILTER_SIZE 10
typedef struct {
float buffer[FILTER_SIZE];
uint8_t index;
uint8_t filled;
} MovingAverage_t;
MovingAverage_t ma_filter = {0};
/**
* @brief 移动平均滤波初始化
*/
void MovingAverage_Init(MovingAverage_t *filter)
{
uint8_t i;
for(i = 0; i < FILTER_SIZE; i++)
{
filter->buffer[i] = 0;
}
filter->index = 0;
filter->filled = 0;
}
/**
* @brief 移动平均滤波更新
* @param filter: 滤波器结构
* @param new_value: 新采样值
* @retval 滤波后的值
*/
float MovingAverage_Update(MovingAverage_t *filter, float new_value)
{
float sum = 0;
uint8_t i, count;
// 添加新值到缓冲区
filter->buffer[filter->index] = new_value;
filter->index++;
if(filter->index >= FILTER_SIZE)
{
filter->index = 0;
filter->filled = 1;
}
// 计算平均值
count = filter->filled ? FILTER_SIZE : filter->index;
for(i = 0; i < count; i++)
{
sum += filter->buffer[i];
}
return sum / count;
}
// 使用示例
void MovingAverage_Example(void)
{
float raw_value, filtered_value;
MovingAverage_Init(&ma_filter);
while(1)
{
raw_value = ADC_Read_Voltage();
filtered_value = MovingAverage_Update(&ma_filter, raw_value);
printf("Raw: %.3fV, Filtered: %.3fV\n", raw_value, filtered_value);
delay_ms(100);
}
}
加权移动平均¶
/**
* @brief 加权移动平均滤波
* 新数据权重更大,旧数据权重更小
*/
#define WMA_SIZE 5
typedef struct {
float buffer[WMA_SIZE];
uint8_t index;
uint8_t filled;
} WeightedMA_t;
// 权重系数(新到旧)
const float weights[WMA_SIZE] = {0.4, 0.3, 0.2, 0.07, 0.03};
/**
* @brief 加权移动平均滤波
*/
float WeightedMovingAverage_Update(WeightedMA_t *filter, float new_value)
{
float sum = 0;
uint8_t i, j;
// 添加新值
filter->buffer[filter->index] = new_value;
filter->index++;
if(filter->index >= WMA_SIZE)
{
filter->index = 0;
filter->filled = 1;
}
// 计算加权平均
j = filter->index;
for(i = 0; i < WMA_SIZE; i++)
{
if(j == 0) j = WMA_SIZE;
j--;
sum += filter->buffer[j] * weights[i];
}
return sum;
}
6.2 中值滤波¶
中值滤波对脉冲干扰有很好的抑制效果。
/**
* @brief 中值滤波
*/
#define MEDIAN_SIZE 5
typedef struct {
float buffer[MEDIAN_SIZE];
uint8_t index;
uint8_t filled;
} MedianFilter_t;
MedianFilter_t median_filter = {0};
/**
* @brief 冒泡排序
*/
void BubbleSort(float *array, uint8_t size)
{
uint8_t i, j;
float temp;
for(i = 0; i < size - 1; i++)
{
for(j = 0; j < size - 1 - i; j++)
{
if(array[j] > array[j + 1])
{
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
/**
* @brief 中值滤波更新
*/
float MedianFilter_Update(MedianFilter_t *filter, float new_value)
{
float sorted[MEDIAN_SIZE];
uint8_t i, count;
// 添加新值
filter->buffer[filter->index] = new_value;
filter->index++;
if(filter->index >= MEDIAN_SIZE)
{
filter->index = 0;
filter->filled = 1;
}
// 复制数据到临时数组
count = filter->filled ? MEDIAN_SIZE : filter->index;
for(i = 0; i < count; i++)
{
sorted[i] = filter->buffer[i];
}
// 排序
BubbleSort(sorted, count);
// 返回中值
return sorted[count / 2];
}
// 使用示例
void MedianFilter_Example(void)
{
float raw_value, filtered_value;
while(1)
{
raw_value = ADC_Read_Voltage();
filtered_value = MedianFilter_Update(&median_filter, raw_value);
printf("Raw: %.3fV, Median: %.3fV\n", raw_value, filtered_value);
delay_ms(100);
}
}
6.3 一阶低通滤波(IIR滤波)¶
一阶低通滤波器是一种简单高效的递归滤波器。
/**
* @brief 一阶低通滤波器
*/
typedef struct {
float alpha; // 滤波系数(0-1)
float output; // 输出值
uint8_t initialized; // 初始化标志
} LowPassFilter_t;
LowPassFilter_t lpf = {0};
/**
* @brief 一阶低通滤波器初始化
* @param filter: 滤波器结构
* @param alpha: 滤波系数(0-1),越小滤波效果越强
*/
void LowPassFilter_Init(LowPassFilter_t *filter, float alpha)
{
filter->alpha = alpha;
filter->output = 0;
filter->initialized = 0;
}
/**
* @brief 一阶低通滤波器更新
* @param filter: 滤波器结构
* @param input: 输入值
* @retval 滤波后的值
*/
float LowPassFilter_Update(LowPassFilter_t *filter, float input)
{
if(!filter->initialized)
{
filter->output = input;
filter->initialized = 1;
}
else
{
// y[n] = α * x[n] + (1 - α) * y[n-1]
filter->output = filter->alpha * input + (1.0f - filter->alpha) * filter->output;
}
return filter->output;
}
/**
* @brief 根据截止频率计算alpha
* @param sample_rate: 采样频率(Hz)
* @param cutoff_freq: 截止频率(Hz)
* @retval alpha值
*/
float Calculate_Alpha(float sample_rate, float cutoff_freq)
{
float rc = 1.0f / (2.0f * 3.14159f * cutoff_freq);
float dt = 1.0f / sample_rate;
return dt / (rc + dt);
}
// 使用示例
void LowPassFilter_Example(void)
{
float raw_value, filtered_value;
float alpha;
// 采样率10Hz,截止频率1Hz
alpha = Calculate_Alpha(10.0f, 1.0f);
LowPassFilter_Init(&lpf, alpha);
printf("Alpha: %.4f\n", alpha);
while(1)
{
raw_value = ADC_Read_Voltage();
filtered_value = LowPassFilter_Update(&lpf, raw_value);
printf("Raw: %.3fV, LPF: %.3fV\n", raw_value, filtered_value);
delay_ms(100); // 10Hz采样
}
}
6.4 卡尔曼滤波¶
卡尔曼滤波是一种最优估计算法,适用于有噪声的动态系统。
/**
* @brief 一维卡尔曼滤波器
*/
typedef struct {
float q; // 过程噪声协方差
float r; // 测量噪声协方差
float x; // 状态估计
float p; // 估计协方差
float k; // 卡尔曼增益
} KalmanFilter_t;
KalmanFilter_t kf = {0};
/**
* @brief 卡尔曼滤波器初始化
* @param filter: 滤波器结构
* @param q: 过程噪声协方差(通常0.001-0.1)
* @param r: 测量噪声协方差(通常0.1-10)
* @param initial_value: 初始值
*/
void KalmanFilter_Init(KalmanFilter_t *filter, float q, float r, float initial_value)
{
filter->q = q;
filter->r = r;
filter->x = initial_value;
filter->p = 1.0f;
}
/**
* @brief 卡尔曼滤波器更新
* @param filter: 滤波器结构
* @param measurement: 测量值
* @retval 滤波后的值
*/
float KalmanFilter_Update(KalmanFilter_t *filter, float measurement)
{
// 预测
// x = x (状态预测,假设状态不变)
filter->p = filter->p + filter->q; // 协方差预测
// 更新
filter->k = filter->p / (filter->p + filter->r); // 计算卡尔曼增益
filter->x = filter->x + filter->k * (measurement - filter->x); // 状态更新
filter->p = (1.0f - filter->k) * filter->p; // 协方差更新
return filter->x;
}
// 使用示例
void KalmanFilter_Example(void)
{
float raw_value, filtered_value;
// 初始化卡尔曼滤波器
// q=0.01(过程噪声小),r=1.0(测量噪声大)
KalmanFilter_Init(&kf, 0.01f, 1.0f, 0.0f);
while(1)
{
raw_value = ADC_Read_Voltage();
filtered_value = KalmanFilter_Update(&kf, raw_value);
printf("Raw: %.3fV, Kalman: %.3fV, Gain: %.4f\n",
raw_value, filtered_value, kf.k);
delay_ms(100);
}
}
6.5 滤波器性能对比¶
/**
* @brief 滤波器性能测试
*/
void Filter_Performance_Test(void)
{
float raw, ma, median, lpf_out, kalman;
uint32_t start_time, end_time;
// 初始化各滤波器
MovingAverage_Init(&ma_filter);
LowPassFilter_Init(&lpf, 0.2f);
KalmanFilter_Init(&kf, 0.01f, 1.0f, 0.0f);
printf("Filter Performance Comparison:\n");
printf("----------------------------------------\n");
for(int i = 0; i < 100; i++)
{
raw = ADC_Read_Voltage();
// 测试移动平均
start_time = micros();
ma = MovingAverage_Update(&ma_filter, raw);
end_time = micros();
printf("MA: %.3fV, Time: %luus\n", ma, end_time - start_time);
// 测试中值滤波
start_time = micros();
median = MedianFilter_Update(&median_filter, raw);
end_time = micros();
printf("Median: %.3fV, Time: %luus\n", median, end_time - start_time);
// 测试低通滤波
start_time = micros();
lpf_out = LowPassFilter_Update(&lpf, raw);
end_time = micros();
printf("LPF: %.3fV, Time: %luus\n", lpf_out, end_time - start_time);
// 测试卡尔曼滤波
start_time = micros();
kalman = KalmanFilter_Update(&kf, raw);
end_time = micros();
printf("Kalman: %.3fV, Time: %luus\n", kalman, end_time - start_time);
printf("----------------------------------------\n");
delay_ms(100);
}
}
滤波器选择指南
滤波器特性对比:
┌──────────┬────────┬────────┬────────┬────────┐
│ 滤波器 │ 计算量 │ 延迟 │ 效果 │ 适用场景│
├──────────┼────────┼────────┼────────┼────────┤
│ 移动平均 │ 中 │ 大 │ 好 │ 周期干扰│
│ 中值滤波 │ 大 │ 中 │ 很好 │ 脉冲干扰│
│ 低通滤波 │ 小 │ 小 │ 好 │ 高频噪声│
│ 卡尔曼 │ 中 │ 小 │ 最好 │ 动态系统│
└──────────┴────────┴────────┴────────┴────────┘
选择建议:
1. 简单应用 → 移动平均
2. 有脉冲干扰 → 中值滤波
3. 实时性要求高 → 低通滤波
4. 高精度要求 → 卡尔曼滤波
5. 可以组合使用(如中值+低通)
6.6 组合滤波¶
/**
* @brief 组合滤波器(中值+低通)
*/
typedef struct {
MedianFilter_t median;
LowPassFilter_t lpf;
} CombinedFilter_t;
CombinedFilter_t combined_filter;
/**
* @brief 组合滤波器初始化
*/
void CombinedFilter_Init(CombinedFilter_t *filter, float alpha)
{
// 初始化中值滤波器
filter->median.index = 0;
filter->median.filled = 0;
// 初始化低通滤波器
LowPassFilter_Init(&filter->lpf, alpha);
}
/**
* @brief 组合滤波器更新
*/
float CombinedFilter_Update(CombinedFilter_t *filter, float input)
{
float median_out, lpf_out;
// 先中值滤波(去除脉冲干扰)
median_out = MedianFilter_Update(&filter->median, input);
// 再低通滤波(平滑信号)
lpf_out = LowPassFilter_Update(&filter->lpf, median_out);
return lpf_out;
}
// 使用示例
void CombinedFilter_Example(void)
{
float raw, filtered;
CombinedFilter_Init(&combined_filter, 0.3f);
while(1)
{
raw = ADC_Read_Voltage();
filtered = CombinedFilter_Update(&combined_filter, raw);
printf("Raw: %.3fV, Combined: %.3fV\n", raw, filtered);
delay_ms(100);
}
}
第七部分:实战项目¶
7.1 多通道数据采集系统¶
实现一个完整的多通道数据采集系统,支持多种传感器。
/**
* @brief 数据采集系统配置
*/
#define MAX_CHANNELS 8
typedef enum {
SENSOR_TYPE_VOLTAGE, // 电压传感器
SENSOR_TYPE_TEMPERATURE, // 温度传感器
SENSOR_TYPE_PRESSURE, // 压力传感器
SENSOR_TYPE_CURRENT // 电流传感器
} SensorType_t;
typedef struct {
uint8_t channel; // ADC通道
SensorType_t type; // 传感器类型
char name[20]; // 传感器名称
float scale; // 缩放系数
float offset; // 偏移量
float min_value; // 最小值
float max_value; // 最大值
uint8_t enabled; // 使能标志
} ChannelConfig_t;
typedef struct {
uint16_t raw; // 原始ADC值
float voltage; // 电压值
float physical; // 物理量值
uint32_t timestamp; // 时间戳
uint8_t valid; // 有效标志
} ChannelData_t;
typedef struct {
ChannelConfig_t config[MAX_CHANNELS];
ChannelData_t data[MAX_CHANNELS];
uint8_t num_channels;
uint8_t running;
} DataAcquisition_t;
DataAcquisition_t daq_system = {0};
/**
* @brief 添加采集通道
*/
void DAQ_Add_Channel(uint8_t channel, SensorType_t type, const char *name,
float scale, float offset, float min, float max)
{
if(daq_system.num_channels < MAX_CHANNELS)
{
uint8_t idx = daq_system.num_channels;
daq_system.config[idx].channel = channel;
daq_system.config[idx].type = type;
strncpy(daq_system.config[idx].name, name, 19);
daq_system.config[idx].scale = scale;
daq_system.config[idx].offset = offset;
daq_system.config[idx].min_value = min;
daq_system.config[idx].max_value = max;
daq_system.config[idx].enabled = 1;
daq_system.num_channels++;
printf("Added channel %d: %s\n", idx, name);
}
}
/**
* @brief 数据采集系统初始化
*/
void DAQ_Init(void)
{
// 配置ADC多通道
ADC_MultiChannel_Init();
// 配置通道
// 通道0:温度传感器(LM35,10mV/°C)
DAQ_Add_Channel(ADC_Channel_0, SENSOR_TYPE_TEMPERATURE,
"Temperature", 100.0f, 0.0f, -40.0f, 125.0f);
// 通道1:压力传感器(0-5V对应0-100kPa)
DAQ_Add_Channel(ADC_Channel_1, SENSOR_TYPE_PRESSURE,
"Pressure", 20.0f, 0.0f, 0.0f, 100.0f);
// 通道2:电流传感器(4-20mA,250Ω采样电阻)
DAQ_Add_Channel(ADC_Channel_2, SENSOR_TYPE_CURRENT,
"Current", 4.0f, -16.0f, 0.0f, 20.0f);
// 通道3:电压传感器(分压1:2)
DAQ_Add_Channel(ADC_Channel_3, SENSOR_TYPE_VOLTAGE,
"Voltage", 2.0f, 0.0f, 0.0f, 6.6f);
daq_system.running = 1;
printf("DAQ System initialized with %d channels\n", daq_system.num_channels);
}
/**
* @brief 采集单个通道数据
*/
void DAQ_Read_Channel(uint8_t idx)
{
if(idx >= daq_system.num_channels || !daq_system.config[idx].enabled)
return;
ChannelConfig_t *cfg = &daq_system.config[idx];
ChannelData_t *data = &daq_system.data[idx];
// 读取ADC值
data->raw = ADC_Read_Average(ADC1, cfg->channel, 10);
// 转换为电压
data->voltage = (float)data->raw * 3.3f / 4096.0f;
// 转换为物理量
data->physical = data->voltage * cfg->scale + cfg->offset;
// 范围检查
if(data->physical < cfg->min_value || data->physical > cfg->max_value)
{
data->valid = 0;
}
else
{
data->valid = 1;
}
// 时间戳
data->timestamp = HAL_GetTick();
}
/**
* @brief 采集所有通道数据
*/
void DAQ_Read_All_Channels(void)
{
uint8_t i;
for(i = 0; i < daq_system.num_channels; i++)
{
DAQ_Read_Channel(i);
}
}
/**
* @brief 打印通道数据
*/
void DAQ_Print_Data(void)
{
uint8_t i;
ChannelConfig_t *cfg;
ChannelData_t *data;
printf("\n========== Data Acquisition Report ==========\n");
printf("Timestamp: %lu ms\n", HAL_GetTick());
printf("---------------------------------------------\n");
for(i = 0; i < daq_system.num_channels; i++)
{
cfg = &daq_system.config[i];
data = &daq_system.data[i];
if(!cfg->enabled)
continue;
printf("[CH%d] %s:\n", i, cfg->name);
printf(" Raw: %d\n", data->raw);
printf(" Voltage: %.3fV\n", data->voltage);
switch(cfg->type)
{
case SENSOR_TYPE_TEMPERATURE:
printf(" Temperature: %.2f°C %s\n",
data->physical, data->valid ? "✓" : "✗");
break;
case SENSOR_TYPE_PRESSURE:
printf(" Pressure: %.2f kPa %s\n",
data->physical, data->valid ? "✓" : "✗");
break;
case SENSOR_TYPE_CURRENT:
printf(" Current: %.2f mA %s\n",
data->physical, data->valid ? "✓" : "✗");
break;
case SENSOR_TYPE_VOLTAGE:
printf(" Voltage: %.2f V %s\n",
data->physical, data->valid ? "✓" : "✗");
break;
}
printf("\n");
}
printf("=============================================\n");
}
/**
* @brief 数据采集主循环
*/
void DAQ_Main_Loop(void)
{
DAQ_Init();
while(daq_system.running)
{
// 采集所有通道
DAQ_Read_All_Channels();
// 打印数据
DAQ_Print_Data();
// 延时
delay_ms(1000);
}
}
7.2 温度监测与报警系统¶
实现一个带阈值报警的温度监测系统。
/**
* @brief 温度报警系统
*/
typedef enum {
ALARM_NONE,
ALARM_LOW,
ALARM_HIGH,
ALARM_CRITICAL
} AlarmLevel_t;
typedef struct {
float low_threshold; // 低温阈值
float high_threshold; // 高温阈值
float critical_threshold; // 临界温度
AlarmLevel_t level; // 当前报警级别
uint32_t alarm_count; // 报警次数
uint8_t alarm_enabled; // 报警使能
} TempAlarm_t;
TempAlarm_t temp_alarm = {
.low_threshold = 10.0f,
.high_threshold = 30.0f,
.critical_threshold = 40.0f,
.level = ALARM_NONE,
.alarm_count = 0,
.alarm_enabled = 1
};
/**
* @brief 温度报警检查
*/
AlarmLevel_t Check_Temperature_Alarm(float temperature)
{
if(!temp_alarm.alarm_enabled)
return ALARM_NONE;
if(temperature >= temp_alarm.critical_threshold)
{
return ALARM_CRITICAL;
}
else if(temperature >= temp_alarm.high_threshold)
{
return ALARM_HIGH;
}
else if(temperature <= temp_alarm.low_threshold)
{
return ALARM_LOW;
}
else
{
return ALARM_NONE;
}
}
/**
* @brief 报警处理
*/
void Handle_Temperature_Alarm(AlarmLevel_t level, float temperature)
{
if(level == ALARM_NONE)
{
// 正常,关闭报警
LED_Off();
Buzzer_Off();
return;
}
temp_alarm.alarm_count++;
switch(level)
{
case ALARM_LOW:
printf("⚠ LOW TEMP ALARM: %.2f°C (< %.2f°C)\n",
temperature, temp_alarm.low_threshold);
LED_Blink_Slow();
break;
case ALARM_HIGH:
printf("⚠ HIGH TEMP ALARM: %.2f°C (> %.2f°C)\n",
temperature, temp_alarm.high_threshold);
LED_Blink_Fast();
Buzzer_Beep(1);
break;
case ALARM_CRITICAL:
printf("🚨 CRITICAL TEMP ALARM: %.2f°C (> %.2f°C)\n",
temperature, temp_alarm.critical_threshold);
LED_On();
Buzzer_Beep(3);
// 可以触发紧急关机等保护措施
break;
default:
break;
}
}
/**
* @brief 温度监测主程序
*/
void Temperature_Monitor_Main(void)
{
float temperature;
AlarmLevel_t alarm_level;
LowPassFilter_t lpf;
// 初始化
ADC_Single_Init();
LowPassFilter_Init(&lpf, 0.2f);
printf("Temperature Monitor Started\n");
printf("Low: %.1f°C, High: %.1f°C, Critical: %.1f°C\n",
temp_alarm.low_threshold,
temp_alarm.high_threshold,
temp_alarm.critical_threshold);
while(1)
{
// 读取温度
temperature = LM35_Read_Temperature();
// 滤波
temperature = LowPassFilter_Update(&lpf, temperature);
// 检查报警
alarm_level = Check_Temperature_Alarm(temperature);
temp_alarm.level = alarm_level;
// 处理报警
Handle_Temperature_Alarm(alarm_level, temperature);
// 显示温度
printf("[%lu] Temp: %.2f°C, Alarm: %d, Count: %lu\n",
HAL_GetTick(), temperature, alarm_level, temp_alarm.alarm_count);
delay_ms(500);
}
}
7.3 传感器数据记录器¶
实现数据记录功能,将传感器数据保存到SD卡或Flash。
/**
* @brief 数据记录器
*/
#define LOG_BUFFER_SIZE 100
typedef struct {
uint32_t timestamp;
float value;
} LogEntry_t;
typedef struct {
LogEntry_t buffer[LOG_BUFFER_SIZE];
uint16_t write_index;
uint16_t read_index;
uint16_t count;
uint8_t logging_enabled;
uint32_t log_interval; // 记录间隔(ms)
uint32_t last_log_time;
} DataLogger_t;
DataLogger_t data_logger = {
.write_index = 0,
.read_index = 0,
.count = 0,
.logging_enabled = 0,
.log_interval = 1000,
.last_log_time = 0
};
/**
* @brief 添加日志条目
*/
void Logger_Add_Entry(float value)
{
uint32_t current_time = HAL_GetTick();
if(!data_logger.logging_enabled)
return;
// 检查记录间隔
if(current_time - data_logger.last_log_time < data_logger.log_interval)
return;
// 添加条目
data_logger.buffer[data_logger.write_index].timestamp = current_time;
data_logger.buffer[data_logger.write_index].value = value;
data_logger.write_index++;
if(data_logger.write_index >= LOG_BUFFER_SIZE)
data_logger.write_index = 0;
if(data_logger.count < LOG_BUFFER_SIZE)
data_logger.count++;
else
data_logger.read_index = data_logger.write_index; // 覆盖最旧的数据
data_logger.last_log_time = current_time;
}
/**
* @brief 保存日志到文件
*/
void Logger_Save_To_File(const char *filename)
{
FILE *fp;
uint16_t i, idx;
fp = fopen(filename, "w");
if(fp == NULL)
{
printf("Error: Cannot open file %s\n", filename);
return;
}
// 写入CSV头
fprintf(fp, "Timestamp(ms),Value\n");
// 写入数据
idx = data_logger.read_index;
for(i = 0; i < data_logger.count; i++)
{
fprintf(fp, "%lu,%.3f\n",
data_logger.buffer[idx].timestamp,
data_logger.buffer[idx].value);
idx++;
if(idx >= LOG_BUFFER_SIZE)
idx = 0;
}
fclose(fp);
printf("Saved %d entries to %s\n", data_logger.count, filename);
}
/**
* @brief 打印日志统计
*/
void Logger_Print_Statistics(void)
{
uint16_t i, idx;
float sum = 0, min = 999999, max = -999999;
float avg;
if(data_logger.count == 0)
{
printf("No data logged\n");
return;
}
// 计算统计数据
idx = data_logger.read_index;
for(i = 0; i < data_logger.count; i++)
{
float value = data_logger.buffer[idx].value;
sum += value;
if(value < min) min = value;
if(value > max) max = value;
idx++;
if(idx >= LOG_BUFFER_SIZE)
idx = 0;
}
avg = sum / data_logger.count;
printf("\n========== Logger Statistics ==========\n");
printf("Entries: %d\n", data_logger.count);
printf("Average: %.3f\n", avg);
printf("Minimum: %.3f\n", min);
printf("Maximum: %.3f\n", max);
printf("Range: %.3f\n", max - min);
printf("=======================================\n");
}
/**
* @brief 数据记录器主程序
*/
void Data_Logger_Main(void)
{
float temperature;
uint32_t start_time;
// 初始化
ADC_Single_Init();
// 启动记录
data_logger.logging_enabled = 1;
data_logger.log_interval = 1000; // 每秒记录一次
start_time = HAL_GetTick();
printf("Data Logger Started (Interval: %lums)\n", data_logger.log_interval);
while(1)
{
// 读取温度
temperature = LM35_Read_Temperature();
// 记录数据
Logger_Add_Entry(temperature);
// 显示当前值
printf("[%lu] Temp: %.2f°C, Logged: %d/%d\n",
HAL_GetTick(), temperature,
data_logger.count, LOG_BUFFER_SIZE);
// 每60秒打印统计
if((HAL_GetTick() - start_time) % 60000 < 100)
{
Logger_Print_Statistics();
}
// 缓冲区满时保存到文件
if(data_logger.count >= LOG_BUFFER_SIZE)
{
Logger_Save_To_File("sensor_log.csv");
data_logger.count = 0;
data_logger.write_index = 0;
data_logger.read_index = 0;
}
delay_ms(100);
}
}
7.4 实时数据可视化¶
通过串口发送数据到上位机进行实时显示。
/**
* @brief 数据可视化协议
* 格式:$DATA,timestamp,ch0,ch1,ch2,ch3*checksum\r\n
*/
/**
* @brief 计算校验和
*/
uint8_t Calculate_Checksum(const char *data, uint16_t len)
{
uint8_t checksum = 0;
uint16_t i;
for(i = 0; i < len; i++)
{
checksum ^= data[i];
}
return checksum;
}
/**
* @brief 发送数据包
*/
void Send_Data_Packet(uint32_t timestamp, float *values, uint8_t num_channels)
{
char buffer[128];
uint16_t len;
uint8_t checksum;
uint8_t i;
// 构建数据包
len = sprintf(buffer, "$DATA,%lu", timestamp);
for(i = 0; i < num_channels; i++)
{
len += sprintf(buffer + len, ",%.3f", values[i]);
}
// 计算校验和
checksum = Calculate_Checksum(buffer + 1, len - 1);
// 添加校验和和结束符
sprintf(buffer + len, "*%02X\r\n", checksum);
// 发送
UART_Send_String(buffer);
}
/**
* @brief 实时数据可视化主程序
*/
void Real_Time_Visualization_Main(void)
{
float values[4];
uint32_t timestamp;
// 初始化
ADC_MultiChannel_Init();
UART_Init(115200);
printf("Real-Time Visualization Started\n");
printf("Baud Rate: 115200\n");
while(1)
{
// 读取多通道数据
Read_MultiChannel_Data(&sensor_data);
timestamp = sensor_data.timestamp;
values[0] = sensor_data.channel[0];
values[1] = sensor_data.channel[1];
values[2] = sensor_data.channel[2];
values[3] = sensor_data.channel[3];
// 发送数据包
Send_Data_Packet(timestamp, values, 4);
delay_ms(50); // 20Hz更新率
}
}
Python上位机接收程序
import serial
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
class SensorVisualizer:
def __init__(self, port, baudrate=115200):
self.ser = serial.Serial(port, baudrate)
self.data = [[] for _ in range(4)]
self.timestamps = []
self.max_points = 200
# 创建图表
self.fig, self.axes = plt.subplots(4, 1, figsize=(10, 8))
self.lines = []
for i, ax in enumerate(self.axes):
line, = ax.plot([], [], 'b-')
self.lines.append(line)
ax.set_ylabel(f'Channel {i}')
ax.grid(True)
self.axes[-1].set_xlabel('Time (s)')
def parse_packet(self, line):
"""解析数据包"""
if not line.startswith('$DATA'):
return None
# 分离数据和校验和
parts = line.split('*')
if len(parts) != 2:
return None
data_str = parts[0]
checksum_str = parts[1].strip()
# 验证校验和
calculated = 0
for c in data_str[1:]:
calculated ^= ord(c)
if f'{calculated:02X}' != checksum_str:
print("Checksum error")
return None
# 解析数据
fields = data_str.split(',')
timestamp = int(fields[1])
values = [float(v) for v in fields[2:]]
return timestamp, values
def update(self, frame):
"""更新图表"""
if self.ser.in_waiting:
line = self.ser.readline().decode('utf-8').strip()
result = self.parse_packet(line)
if result:
timestamp, values = result
self.timestamps.append(timestamp / 1000.0) # 转换为秒
for i, value in enumerate(values):
self.data[i].append(value)
# 限制数据点数量
if len(self.timestamps) > self.max_points:
self.timestamps = self.timestamps[-self.max_points:]
for i in range(4):
self.data[i] = self.data[i][-self.max_points:]
# 更新图表
for i, line in enumerate(self.lines):
line.set_data(self.timestamps, self.data[i])
self.axes[i].relim()
self.axes[i].autoscale_view()
return self.lines
def run(self):
"""运行可视化"""
ani = FuncAnimation(self.fig, self.update, interval=50, blit=True)
plt.tight_layout()
plt.show()
# 使用示例
if __name__ == '__main__':
visualizer = SensorVisualizer('COM3', 115200)
visualizer.run()
第八部分:故障排除与优化¶
8.1 常见问题诊断¶
问题1:ADC读数不稳定¶
症状:
- ADC值跳动剧烈
- 读数随机变化
- 无法获得稳定值
可能原因及解决方案:
1. 电源噪声
┌────────────────────────────────┐
│ 原因:电源纹波大,干扰ADC │
│ 解决: │
│ • 添加去耦电容(0.1μF + 10μF) │
│ • 使用LDO稳压 │
│ • 分离模拟地和数字地 │
└────────────────────────────────┘
2. 参考电压不稳定
┌────────────────────────────────┐
│ 原因:VREF波动 │
│ 解决: │
│ • 使用专用参考电压芯片 │
│ • 添加滤波电容 │
│ • 检查VREF连接 │
└────────────────────────────────┘
3. 采样时间不足
┌────────────────────────────────┐
│ 原因:高阻抗信号源 │
│ 解决: │
│ • 增加采样时间 │
│ • 添加缓冲放大器 │
│ • 降低信号源阻抗 │
└────────────────────────────────┘
4. 外部干扰
┌────────────────────────────────┐
│ 原因:信号线受干扰 │
│ 解决: │
│ • 使用屏蔽线 │
│ • 缩短信号线长度 │
│ • 远离干扰源 │
│ • 添加RC滤波 │
└────────────────────────────────┘
诊断代码
/**
* @brief ADC稳定性测试
*/
void ADC_Stability_Test(void)
{
uint16_t values[100];
uint16_t i;
float mean, std_dev, sum = 0, variance = 0;
printf("ADC Stability Test\n");
printf("Collecting 100 samples...\n");
// 采集100个样本
for(i = 0; i < 100; i++)
{
values[i] = ADC_Read_Single(ADC1, ADC_Channel_0);
sum += values[i];
delay_ms(10);
}
// 计算平均值
mean = sum / 100.0f;
// 计算标准差
for(i = 0; i < 100; i++)
{
variance += (values[i] - mean) * (values[i] - mean);
}
std_dev = sqrtf(variance / 100.0f);
// 评估稳定性
printf("\nResults:\n");
printf("Mean: %.2f\n", mean);
printf("Std Dev: %.2f\n", std_dev);
printf("Coefficient of Variation: %.2f%%\n", (std_dev / mean) * 100.0f);
if(std_dev < 5.0f)
{
printf("✓ ADC is STABLE\n");
}
else if(std_dev < 20.0f)
{
printf("⚠ ADC is MODERATELY STABLE\n");
printf(" Consider adding filtering\n");
}
else
{
printf("✗ ADC is UNSTABLE\n");
printf(" Check power supply and grounding\n");
}
}
问题2:传感器读数偏差大¶
症状:
- 测量值与实际值偏差大
- 不同传感器读数不一致
- 温度漂移严重
解决方案:
1. 执行校准
• 单点校准(消除偏移)
• 两点校准(消除偏移和增益误差)
• 多点校准(补偿非线性)
2. 温度补偿
• 测量环境温度
• 应用温度补偿系数
• 使用温度稳定的元件
3. 检查硬件
• 验证参考电阻精度
• 检查电源电压
• 测量实际参考电压
温度补偿示例
/**
* @brief 温度补偿
*/
typedef struct {
float temp_coeff; // 温度系数(ppm/°C)
float ref_temp; // 参考温度(°C)
float ref_value; // 参考温度下的值
} TempCompensation_t;
TempCompensation_t temp_comp = {
.temp_coeff = 100.0f, // 100ppm/°C
.ref_temp = 25.0f,
.ref_value = 0.0f
};
/**
* @brief 应用温度补偿
*/
float Apply_Temperature_Compensation(float value, float current_temp)
{
float temp_diff, compensation;
// 计算温度差
temp_diff = current_temp - temp_comp.ref_temp;
// 计算补偿量(ppm转换为百分比)
compensation = value * (temp_comp.temp_coeff / 1000000.0f) * temp_diff;
// 应用补偿
return value - compensation;
}
问题3:I2C/SPI通信失败¶
I2C通信问题诊断:
1. 检查硬件连接
┌────────────────────────────────┐
│ • 上拉电阻是否存在(2.2k-10k)│
│ • SCL/SDA是否接对 │
│ • 电源和地是否连接 │
│ • 设备地址是否正确 │
└────────────────────────────────┘
2. 检查时序
┌────────────────────────────────┐
│ • 时钟频率是否过高 │
│ • 是否有ACK响应 │
│ • 起始和停止条件是否正确 │
└────────────────────────────────┘
3. 检查电平
┌────────────────────────────────┐
│ • 高电平是否达到VCC │
│ • 低电平是否接近0V │
│ • 电平是否兼容(3.3V/5V) │
└────────────────────────────────┘
I2C诊断工具
/**
* @brief I2C总线扫描
*/
void I2C_Bus_Scan(void)
{
uint8_t addr;
uint8_t found = 0;
printf("Scanning I2C bus...\n");
printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F\n");
for(addr = 0; addr < 128; addr++)
{
if(addr % 16 == 0)
{
printf("%02X: ", addr);
}
// 尝试通信
if(I2C_Check_Device(addr) == SUCCESS)
{
printf("%02X ", addr);
found++;
}
else
{
printf("-- ");
}
if((addr + 1) % 16 == 0)
{
printf("\n");
}
}
printf("\nFound %d device(s)\n", found);
}
/**
* @brief 检查I2C设备是否存在
*/
uint8_t I2C_Check_Device(uint8_t addr)
{
// 发送起始条件
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 发送地址
I2C_Send7bitAddress(I2C1, addr << 1, I2C_Direction_Transmitter);
// 等待ACK
uint32_t timeout = 1000;
while(timeout--)
{
if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
I2C_GenerateSTOP(I2C1, ENABLE);
return SUCCESS;
}
}
I2C_GenerateSTOP(I2C1, ENABLE);
return ERROR;
}
8.2 性能优化技巧¶
优化1:提高采样速度¶
/**
* @brief 高速ADC采样优化
*/
// 方法1:减少采样时间
void ADC_Fast_Sampling_Config(void)
{
// 使用最短采样时间(1.5周期)
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_1Cycles5);
// 注意:仅适用于低阻抗信号源(<10kΩ)
}
// 方法2:使用DMA + 双缓冲
#define BUFFER_SIZE 1000
uint16_t adc_buffer1[BUFFER_SIZE];
uint16_t adc_buffer2[BUFFER_SIZE];
uint8_t current_buffer = 0;
void ADC_DMA_DoubleBuffer_Config(void)
{
// 配置DMA双缓冲模式
DMA_DoubleBufferModeConfig(DMA1_Channel1, (uint32_t)adc_buffer2, DMA_Memory_0);
DMA_DoubleBufferModeCmd(DMA1_Channel1, ENABLE);
// 使能DMA传输完成中断
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
}
void DMA1_Channel1_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC1))
{
// 切换缓冲区
current_buffer = !current_buffer;
// 处理数据(在另一个缓冲区)
Process_ADC_Data(current_buffer ? adc_buffer1 : adc_buffer2, BUFFER_SIZE);
DMA_ClearITPendingBit(DMA1_IT_TC1);
}
}
// 方法3:使用定时器触发ADC
void ADC_Timer_Trigger_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_InitStructure;
// 配置定时器(例如:10kHz采样率)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InitStructure.TIM_Period = 7200 - 1; // 72MHz / 7200 = 10kHz
TIM_InitStructure.TIM_Prescaler = 0;
TIM_InitStructure.TIM_ClockDivision = 0;
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
// 配置定时器触发输出
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
// 配置ADC使用定时器触发
ADC_ExternalTrigConvCmd(ADC1, ENABLE);
ADC_ExternalTrigConv(ADC1, ADC_ExternalTrigConv_T2_TRGO);
// 启动定时器
TIM_Cmd(TIM2, ENABLE);
}
优化2:降低功耗¶
/**
* @brief 低功耗ADC配置
*/
// 方法1:按需采样
void ADC_OnDemand_Sampling(void)
{
// 平时关闭ADC
ADC_Cmd(ADC1, DISABLE);
// 需要采样时
ADC_Cmd(ADC1, ENABLE);
delay_us(10); // 等待ADC稳定
// 采样
uint16_t value = ADC_Read_Single(ADC1, ADC_Channel_0);
// 关闭ADC
ADC_Cmd(ADC1, DISABLE);
}
// 方法2:降低采样率
void ADC_LowPower_Config(void)
{
// 使用较长的采样时间
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5);
// 降低ADC时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div8); // 72MHz/8 = 9MHz
// 使用间断模式
ADC_DiscModeChannelCountConfig(ADC1, 1);
ADC_DiscModeCmd(ADC1, ENABLE);
}
// 方法3:使用睡眠模式
void ADC_Sleep_Mode_Sampling(void)
{
// 配置ADC中断唤醒
ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
// 启动转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// 进入睡眠模式
__WFI(); // 等待中断
// ADC中断唤醒后继续执行
}
优化3:提高精度¶
/**
* @brief 高精度ADC配置
*/
// 方法1:过采样
#define OVERSAMPLE_RATE 16
uint16_t ADC_Oversample(ADC_TypeDef* ADCx, uint8_t channel)
{
uint32_t sum = 0;
uint8_t i;
for(i = 0; i < OVERSAMPLE_RATE; i++)
{
sum += ADC_Read_Single(ADCx, channel);
}
// 右移提高分辨率
// 16次过采样可提高2位分辨率(12位→14位)
return (uint16_t)(sum >> 2); // 除以4
}
// 方法2:使用外部参考电压
void ADC_External_VREF_Config(void)
{
// 使用外部精密参考电压(如REF3030)
// 配置VREF+引脚
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置VREF+引脚为模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // VREF+
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
// 方法3:温度补偿
void ADC_Temperature_Compensation(void)
{
float vref_25c = 1.20f; // 25°C时的参考电压
float temp_coeff = 0.0003f; // 温度系数(V/°C)
float current_temp, vref_actual;
// 读取内部温度传感器
current_temp = Read_Internal_Temperature();
// 计算实际参考电压
vref_actual = vref_25c + temp_coeff * (current_temp - 25.0f);
// 使用实际参考电压进行转换
// ...
}
8.3 精度提升方法¶
/**
* @brief 综合精度提升方案
*/
typedef struct {
// 硬件校准
TwoPoint_Calib_t hardware_calib;
// 温度补偿
TempCompensation_t temp_comp;
// 滤波器
KalmanFilter_t kalman;
MedianFilter_t median;
// 过采样
uint8_t oversample_rate;
// 统计信息
float last_value;
float max_error;
uint32_t sample_count;
} HighPrecision_ADC_t;
HighPrecision_ADC_t hp_adc = {0};
/**
* @brief 高精度ADC初始化
*/
void HighPrecision_ADC_Init(void)
{
// 硬件配置
ADC_External_VREF_Config();
ADC_Single_Init();
// 加载校准数据
Load_Calibration_Data();
// 初始化滤波器
KalmanFilter_Init(&hp_adc.kalman, 0.001f, 0.1f, 0.0f);
// 设置过采样率
hp_adc.oversample_rate = 16;
printf("High Precision ADC Initialized\n");
}
/**
* @brief 高精度ADC读取
*/
float HighPrecision_ADC_Read(void)
{
float raw_value, filtered_value, compensated_value, final_value;
float current_temp;
// 1. 过采样
raw_value = (float)ADC_Oversample(ADC1, ADC_Channel_0);
// 2. 转换为电压
raw_value = raw_value * 3.0f / 16384.0f; // 14位分辨率
// 3. 中值滤波(去除脉冲干扰)
filtered_value = MedianFilter_Update(&hp_adc.median, raw_value);
// 4. 卡尔曼滤波(平滑信号)
filtered_value = KalmanFilter_Update(&hp_adc.kalman, filtered_value);
// 5. 硬件校准
compensated_value = Apply_TwoPoint_Calibration(filtered_value);
// 6. 温度补偿
current_temp = Read_Internal_Temperature();
final_value = Apply_Temperature_Compensation(compensated_value, current_temp);
// 7. 更新统计
hp_adc.sample_count++;
hp_adc.last_value = final_value;
return final_value;
}
8.4 最佳实践总结¶
传感器数据采集最佳实践:
1. 硬件设计
✓ 使用去耦电容(每个IC旁边)
✓ 模拟地和数字地分离
✓ 使用屏蔽线或双绞线
✓ 信号线远离干扰源
✓ 添加RC滤波电路
✓ 使用精密参考电压
2. 软件设计
✓ 多次采样平均
✓ 使用适当的滤波算法
✓ 实施校准程序
✓ 添加异常值检测
✓ 记录校准数据
✓ 实现温度补偿
3. 调试技巧
✓ 使用示波器查看信号
✓ 测量实际参考电压
✓ 验证ADC时钟配置
✓ 检查采样时间设置
✓ 监控电源质量
✓ 测试极限条件
4. 性能优化
✓ 根据需求选择采样率
✓ 使用DMA减轻CPU负担
✓ 合理配置滤波参数
✓ 平衡精度和速度
✓ 考虑功耗限制
✓ 优化数据处理算法
5. 可靠性设计
✓ 添加看门狗保护
✓ 实现错误检测
✓ 提供故障恢复机制
✓ 记录异常事件
✓ 定期自检
✓ 冗余设计(关键应用)
总结¶
关键要点回顾¶
通过本教程的学习,你应该掌握了以下核心知识:
1. 传感器基础 - 理解传感器的分类(模拟/数字) - 掌握传感器性能指标(精度、分辨率、灵敏度等) - 学会根据应用需求选择合适的传感器
2. ADC原理与应用 - 理解ADC的工作原理和转换过程 - 掌握ADC关键参数(分辨率、采样率、转换时间) - 熟练配置STM32 ADC进行数据采集
3. 传感器接口设计 - 掌握模拟传感器接口电路设计 - 理解数字传感器通信协议(I2C、SPI) - 学会设计信号调理和抗干扰电路
4. 数据采集编程 - 实现单次、连续、多通道ADC采集 - 使用DMA提高数据传输效率 - 编写健壮的传感器驱动程序
5. 传感器校准 - 理解校准的原理和重要性 - 掌握单点、两点、多点校准方法 - 实现校准数据的保存和加载
6. 数据滤波算法 - 掌握移动平均、中值、低通、卡尔曼等滤波算法 - 理解各种滤波器的特点和适用场景 - 学会根据需求选择和组合滤波器
7. 实战项目经验 - 构建多通道数据采集系统 - 实现温度监测与报警功能 - 开发数据记录和可视化工具
8. 故障排除能力 - 诊断常见的ADC和传感器问题 - 掌握性能优化技巧 - 应用最佳实践提高系统可靠性
学习路径建议¶
进阶学习路径:
当前水平:传感器数据采集基础 ✓
下一步学习:
├── 1. 深入学习特定传感器
│ ├── 温湿度传感器(DHT22、SHT3x)
│ ├── 加速度计和陀螺仪(MPU6050)
│ ├── 气压传感器(BMP280)
│ └── 光照传感器(BH1750)
│
├── 2. 高级信号处理
│ ├── FFT频谱分析
│ ├── 数字滤波器设计
│ ├── 传感器融合算法
│ └── 机器学习应用
│
├── 3. 通信与网络
│ ├── 无线传感器网络
│ ├── 物联网协议(MQTT、CoAP)
│ ├── 云平台集成
│ └── 边缘计算
│
└── 4. 系统集成
├── 多传感器系统设计
├── 实时操作系统应用
├── 低功耗设计
└── 产品化开发
实践建议¶
1. 动手实验
推荐实验项目:
入门级:
□ 使用LM35测量温度
□ 使用光敏电阻检测光照
□ 实现简单的移动平均滤波
□ 制作温度显示器
进阶级:
□ 多通道数据采集系统
□ 温度监测与报警系统
□ 数据记录器(SD卡存储)
□ 实时数据可视化
挑战级:
□ 无线传感器网络节点
□ 传感器融合系统
□ 智能环境监测站
□ 工业数据采集系统
2. 调试技能 - 学会使用示波器观察信号 - 掌握逻辑分析仪使用方法 - 熟练使用串口调试工具 - 学习使用专业测试设备
3. 代码规范 - 编写清晰的注释 - 使用有意义的变量名 - 模块化设计 - 错误处理完善 - 代码可移植性
4. 持续学习 - 阅读传感器数据手册 - 学习信号处理理论 - 关注最新传感器技术 - 参与开源项目 - 分享学习经验
常用资源¶
1. 开发工具
硬件工具:
• 示波器(查看模拟信号)
• 逻辑分析仪(调试数字通信)
• 万用表(测量电压电流)
• 信号发生器(测试用)
软件工具:
• Keil MDK / STM32CubeIDE(开发环境)
• Serial Port Monitor(串口调试)
• MATLAB / Python(数据分析)
• Excel(数据处理)
2. 学习资源
官方文档:
• STM32参考手册
• 传感器数据手册
• 应用笔记(Application Notes)
在线资源:
• ST官方网站
• GitHub开源项目
• 技术论坛(CSDN、博客园)
• 视频教程(B站、YouTube)
推荐书籍:
• 《嵌入式系统设计》
• 《数字信号处理》
• 《传感器技术手册》
• 《STM32权威指南》
3. 开发板推荐
入门级:
• STM32F103C8T6最小系统板
• Arduino Uno(快速原型)
• ESP32开发板(带WiFi)
进阶级:
• STM32F4 Discovery
• STM32 Nucleo系列
• 树莓派(上位机)
专业级:
• STM32H7系列
• NI myRIO(教学)
• 工业级数据采集卡
下一步学习¶
完成本教程后,建议按以下顺序继续学习:
1. 深入学习相关主题 - 温湿度传感器应用开发 - 学习DHT和SHT系列传感器 - 加速度计与陀螺仪 - 学习运动传感器 - 光照与压力传感器 - 学习环境传感器
2. 进阶主题 - 传感器融合技术 - 多传感器数据融合 - 传感器校准技术 - 高级校准方法 - 生物传感器应用 - 医疗健康应用
3. 系统集成 - RTOS任务调度 - 无线通信 - 数据存储
练习题¶
基础练习
- ADC配置练习
- 配置STM32 ADC1的通道0,采样时间55.5周期
- 实现单次转换模式
-
将ADC值转换为电压并通过串口输出
-
滤波算法实现
- 实现5点移动平均滤波
- 实现5点中值滤波
-
对比两种滤波器的效果
-
传感器校准
- 使用LM35温度传感器
- 在0°C和100°C进行两点校准
- 测量室温并验证校准效果
进阶练习
- 多通道采集
- 配置4个ADC通道
- 使用DMA进行数据传输
-
实现循环采集并计算每个通道的平均值
-
数据记录系统
- 实现100个数据点的循环缓冲区
- 每秒记录一次温度数据
-
计算并显示最大值、最小值、平均值
-
报警系统
- 设置温度上下限阈值
- 实现三级报警(正常、警告、严重)
- 使用LED和蜂鸣器指示报警状态
挑战练习
- 高精度测量
- 实现16倍过采样
- 应用卡尔曼滤波
- 实现温度补偿
-
达到0.1°C的测量精度
-
数据可视化
- 通过串口发送传感器数据
- 编写Python上位机程序
- 实时绘制数据曲线
-
保存数据到CSV文件
-
完整系统
- 设计一个环境监测站
- 采集温度、湿度、光照、压力
- 实现数据记录和报警功能
- 通过WiFi上传数据到云平台
参考资料¶
官方文档¶
- STM32参考手册
- STM32F1xx Reference Manual (RM0008)
- STM32F4xx Reference Manual (RM0090)
-
STM32 ADC Application Note (AN2834)
-
传感器数据手册
- LM35 Precision Centigrade Temperature Sensors (Texas Instruments)
- DHT22 Digital Temperature and Humidity Sensor (Aosong)
- BMP280 Digital Pressure Sensor (Bosch)
-
ADXL345 3-Axis Digital Accelerometer (Analog Devices)
-
通信协议规范
- I2C-bus Specification (NXP)
- SPI Block Guide (Motorola)
- UART Technical Documents
技术文章¶
- ADC相关
- "Understanding ADC Specifications" - Analog Devices
- "ADC Noise Analysis and Filtering" - Texas Instruments
-
"Improving ADC Resolution by Oversampling" - Atmel
-
滤波算法
- "Digital Filter Design" - MIT OpenCourseWare
- "Kalman Filter Tutorial" - Greg Welch & Gary Bishop
-
"Moving Average Filters" - DSP Guide
-
传感器应用
- "Sensor Calibration Techniques" - National Instruments
- "Temperature Sensor Design Guide" - Maxim Integrated
- "Sensor Signal Conditioning" - Analog Devices
在线资源¶
- 官方网站
- ST官网:https://www.st.com
- ARM官网:https://www.arm.com
-
传感器厂商网站
-
开源项目
- GitHub: STM32 Examples
- GitHub: Sensor Libraries
-
Arduino Libraries
-
技术社区
- Stack Overflow
- ST Community
- 电子工程世界论坛
- CSDN博客
推荐书籍¶
- 嵌入式系统
- 《STM32库开发实战指南》- 野火
- 《ARM Cortex-M3权威指南》- Joseph Yiu
-
《嵌入式实时操作系统μC/OS-III》- Jean J. Labrosse
-
信号处理
- 《数字信号处理》- 高西全、丁玉美
- 《The Scientist and Engineer's Guide to Digital Signal Processing》- Steven W. Smith
-
《Understanding Digital Signal Processing》- Richard G. Lyons
-
传感器技术
- 《传感器技术手册》- Jon S. Wilson
- 《传感器原理及应用》- 黄贤武
- 《Sensors and Actuators》- Clarence W. de Silva
视频教程¶
- 中文教程
- B站:正点原子STM32教程
- B站:野火STM32视频教程
-
慕课网:嵌入式系统课程
-
英文教程
- YouTube: STM32 Tutorial Series
- Coursera: Embedded Systems Courses
- edX: IoT and Embedded Systems
工具软件¶
- 开发工具
- Keil MDK-ARM
- STM32CubeIDE
-
IAR Embedded Workbench
-
调试工具
- Serial Port Monitor
- Logic Analyzer Software
-
MATLAB / Simulink
-
数据分析
- Python (NumPy, SciPy, Matplotlib)
- MATLAB
- LabVIEW
附录¶
A. 常用传感器参数对照表¶
┌──────────────┬────────────┬──────────┬──────────┬──────────┐
│ 传感器类型 │ 测量范围 │ 精度 │ 接口 │ 价格 │
├──────────────┼────────────┼──────────┼──────────┼──────────┤
│ LM35 │ -55~150°C │ ±0.5°C │ 模拟 │ ¥3-5 │
│ DHT22 │ -40~80°C │ ±0.5°C │ 单总线 │ ¥10-15 │
│ SHT30 │ -40~125°C │ ±0.3°C │ I2C │ ¥15-25 │
│ BMP280 │ 300-1100hPa│ ±1hPa │ I2C/SPI │ ¥8-12 │
│ ADXL345 │ ±2/4/8/16g │ ±4mg │ I2C/SPI │ ¥15-20 │
│ MPU6050 │ ±2/4/8/16g │ ±2% │ I2C │ ¥10-15 │
│ BH1750 │ 1-65535lux │ ±20% │ I2C │ ¥5-8 │
│ MQ-2 │ 气体检测 │ - │ 模拟 │ ¥5-10 │
└──────────────┴────────────┴──────────┴──────────┴──────────┘
B. ADC配置速查表¶
STM32F103 ADC配置参数:
采样时间选项:
• ADC_SampleTime_1Cycles5 - 1.5周期
• ADC_SampleTime_7Cycles5 - 7.5周期
• ADC_SampleTime_13Cycles5 - 13.5周期
• ADC_SampleTime_28Cycles5 - 28.5周期
• ADC_SampleTime_41Cycles5 - 41.5周期
• ADC_SampleTime_55Cycles5 - 55.5周期
• ADC_SampleTime_71Cycles5 - 71.5周期
• ADC_SampleTime_239Cycles5 - 239.5周期
转换时间计算:
T_conv = 采样时间 + 12.5周期
示例(ADC时钟12MHz,采样时间55.5周期):
T_conv = (55.5 + 12.5) / 12MHz = 5.67μs
C. 滤波器参数选择指南¶
滤波器参数推荐:
移动平均滤波:
• 窗口大小:5-20(根据采样率)
• 适用:周期性干扰
中值滤波:
• 窗口大小:3-7(奇数)
• 适用:脉冲干扰
一阶低通滤波:
• Alpha值:0.1-0.5
• 截止频率:信号频率的1/5-1/10
• 适用:高频噪声
卡尔曼滤波:
• 过程噪声Q:0.001-0.1
• 测量噪声R:0.1-10
• Q/R比值决定滤波强度
• 适用:动态系统
D. 故障排除检查清单¶
□ 硬件检查
□ 电源电压正常(3.3V或5V)
□ 去耦电容已安装
□ 信号线连接正确
□ 上拉电阻已安装(I2C)
□ 地线连接良好
□ 软件配置
□ 时钟配置正确
□ GPIO模式正确
□ ADC参数合理
□ 中断优先级设置
□ DMA配置正确
□ 信号质量
□ 电源纹波<50mV
□ 信号无明显噪声
□ 通信波形正常
□ 时序符合规范
□ 功能验证
□ ADC读数稳定
□ 通信正常
□ 数据合理
□ 校准有效
文档版本: 1.0.0
最后更新: 2026-03-16
作者: 嵌入式知识平台内容团队
许可: CC BY-NC-SA 4.0
本文档是嵌入式系统学习路径的一部分,更多内容请访问知识库主页。