4. 传感器数据采集:模拟信号读取原理、ADC配置、使用ACS712采集电流、使用分压电路采集电压、数据校准方法

好,咱们进入第四章。这一章讲的是传感器数据采集,说白了就是怎么让单片机“看懂”外面的世界。数字信号它认识,但模拟信号——比如电流、电压这种连续变化的值——它得靠ADC来翻译。

我个人习惯把ADC比作一个“翻译官”。它把模拟世界的连续电压,转成单片机认识的二进制数字。你想想看,一个0-5V的电压,经过ADC之后,就变成了0-4095之间的一个数(如果是12位ADC的话)。

模拟信号读取原理

模拟信号,就是连续变化的信号。比如你用手拧电位器,电压从0V平滑变到5V,中间没有跳变。单片机不认识这种连续信号,它只认0和1。所以我们需要ADC(模数转换器)来做这个翻译工作。

ADC的核心原理其实不复杂:它内部有一个比较器,不断拿输入电压跟一个参考电压去比。比来比去,最后锁定一个最接近的数字值。这个过程叫“逐次逼近”。

关键点:ADC的精度取决于它的位数。8位ADC只能分出256个台阶,12位能分出4096个台阶。我做过一个项目,用8位ADC测电池电压,结果电压波动根本看不出来,换成12位之后,数据曲线就平滑多了。

ADC配置实战

配置ADC,不同芯片的寄存器不一样,但思路是通用的。我以STM32为例,给你看看核心配置步骤。

// ADC初始化配置
void ADC_Init(void)
{
    // 1. 开启ADC时钟
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    
    // 2. 配置ADC为独立模式
    ADC1->CR1 &= ~ADC_CR1_SCAN;
    
    // 3. 设置数据对齐方式:右对齐
    ADC1->CR2 &= ~ADC_CR2_ALIGN;
    
    // 4. 设置采样时间:55.5个周期
    ADC1->SMPR2 |= ADC_SMPR2_SMP0_2 | ADC_SMPR2_SMP0_1;
    
    // 5. 开启ADC
    ADC1->CR2 |= ADC_CR2_ADON;
    
    // 6. 校准ADC
    ADC1->CR2 |= ADC_CR2_CAL;
    while(ADC1->CR2 & ADC_CR2_CAL);
}

嗯,这里要注意:校准这一步很多人会忘。我曾经有一次,ADC读出来的数据总是偏大,折腾了半天才发现是没做校准。校准其实就是让ADC自己测量内部参考电压,消除芯片本身的偏差。

使用ACS712采集电流

ACS712是个霍尔效应电流传感器。它用起来很简单:把电流线穿过芯片,芯片输出一个跟电流成正比的电压。比如ACS712-05B这个型号,灵敏度是185mV/A,也就是说1A电流对应185mV的电压变化。

但有个坑:ACS712在0A时输出是2.5V(VCC/2)。所以你要先测出这个“零点电压”,然后才能算出实际电流。

// 读取ACS712电流值
float Read_Current(void)
{
    uint16_t adc_value;
    float voltage, current;
    
    // 读取ADC原始值
    adc_value = ADC_GetValue();
    
    // 转换成电压值(假设参考电压3.3V,12位ADC)
    voltage = (float)adc_value * 3.3f / 4096.0f;
    
    // 减去零点偏移(2.5V),再除以灵敏度
    current = (voltage - 2.5f) / 0.185f;
    
    return current;
}

我的经验:ACS712对电源噪声很敏感。我建议在它的VCC和GND之间加一个100nF的陶瓷电容,最好再串一个10Ω的电阻。这样做之后,读数稳定性会好很多。

使用分压电路采集电压

单片机的ADC输入范围通常是0-3.3V或0-5V。如果你要测12V电池,或者220V交流电,直接接上去就烧了。这时候就需要分压电路。

分压电路就是两个电阻串联。比如你要测0-24V,可以用一个10kΩ和一个1kΩ的电阻分压。这样24V输入时,ADC脚上只有2.18V,安全得很。

// 分压电路电压读取
float Read_Voltage(float R1, float R2)
{
    uint16_t adc_value;
    float voltage_at_adc, actual_voltage;
    
    // 读取ADC值
    adc_value = ADC_GetValue();
    
    // ADC引脚上的电压
    voltage_at_adc = (float)adc_value * 3.3f / 4096.0f;
    
    // 反推实际电压:V_actual = V_adc * (R1 + R2) / R2
    actual_voltage = voltage_at_adc * (R1 + R2) / R2;
    
    return actual_voltage;
}

警告:分压电阻的精度直接影响测量结果。我建议用1%精度的金属膜电阻。另外,分压后的电压不要超过ADC的参考电压,否则会烧坏ADC引脚。

数据校准方法

校准,是数据采集里最容易被忽视的一环。你想想看,电阻有误差,ADC有偏移,电源有波动,这些因素叠加起来,测出来的数据可能偏差5%甚至更多。

我常用的校准方法有两种:

  1. 两点校准法:用两个已知的基准值(比如0V和5V),测出ADC对应的读数,然后算出斜率和偏移。
  2. 线性拟合:用多个基准点,做最小二乘法拟合。这个方法更准,但计算量稍大。
// 两点校准示例
typedef struct {
    float slope;      // 斜率
    float offset;     // 偏移
} Calibration_t;

Calibration_t Calibrate(float ref1, float adc1, float ref2, float adc2)
{
    Calibration_t cal;
    
    // 计算斜率和偏移
    cal.slope = (ref2 - ref1) / (adc2 - adc1);
    cal.offset = ref1 - cal.slope * adc1;
    
    return cal;
}

// 应用校准
float ApplyCalibration(float raw_value, Calibration_t cal)
{
    return raw_value * cal.slope + cal.offset;
}

避坑指南:我曾经在一个项目中,校准参数直接写死在代码里。结果换了批传感器,数据全乱了。后来我改成把校准参数存在EEPROM里,每台设备单独校准。这个习惯我一直保留到现在。

本章知识体系

下面这张图,是我画的知识结构图。你可以看到,从模拟信号到数字信号,再到校准输出,整个流程是环环相扣的。

传感器数据采集知识体系 模拟信号输入 电流/电压/电阻 信号调理 分压/滤波/放大 ADC转换 逐次逼近/采样 数字 数据处理与校准 两点校准/线性拟合 准确测量值 图:从模拟信号到准确测量值的完整流程

好了,这一章的内容就到这里。记住,数据采集的核心就三步:信号调理、ADC转换、数据校准。每一步都马虎不得。下一章我们会聊更具体的传感器接口协议,到时候见。


专注资料整理