3. 万用表数据采集基础:ADC采样原理、万用表前端电路与STM32内部ADC配置

好,咱们进入第三章节。说实话,这一章是整个课程里最「硬核」的部分之一。你想想看,万用表测电压、测电流、测电阻,最终都要变成数字信号给蓝牙发出去。那这个「变」的过程,靠的就是ADC——模数转换器。

我当年第一次做手持仪表项目时,就栽在ADC的采样精度上。明明电路算得好好的,出来的数据就是跳来跳去。后来才发现,是前端电路和ADC配置没配合好。嗯,今天咱们就把这块彻底讲透。

3.1 ADC采样原理:从模拟到数字的桥梁

ADC说白了,就是把连续的模拟电压,变成离散的数字量。比如你测一节1.5V的电池,ADC会输出一个数字,比如3072(假设是12位ADC,参考电压3.3V)。

这里有个核心公式:

数字量 = (模拟电压 / 参考电压) × (2^N - 1)

其中N是ADC的位数。12位就是4096级,16位就是65536级。

重要概念:分辨率与精度

分辨率 = 参考电压 / 2^N。比如3.3V参考、12位ADC,分辨率就是3.3/4096 ≈ 0.8mV。但注意,分辨率不等于精度!我见过有人把12位ADC当16位用,结果数据全是噪声。

常见的ADC类型有几种:

  • 逐次逼近型(SAR):STM32内部用的就是这种。速度快,精度适中,功耗低。
  • 积分型(双斜率):万用表里常用。精度高,抗干扰强,但慢。
  • Σ-Δ型:高精度测量仪器用。分辨率能做到24位,但速度慢。

我个人习惯,做便携设备首选SAR型。为什么?因为它在速度和功耗之间平衡得最好。你想想看,万用表要测电池,ADC本身不能太耗电,不然还没测完,电池先被ADC吃光了。

3.2 万用表前端电路:分压电阻与电流检测

ADC不能直接测高压,也不能直接测电流。所以前端电路就是干这个的——把被测信号「翻译」成ADC能接受的0~3.3V范围。

3.2.1 电压测量:分压电阻网络

万用表测电压,核心就是电阻分压。比如你要测0~100V,ADC只能接受0~3.3V,那分压比就是100:3.3 ≈ 30.3:1。

实际电路里,我一般用多档位切换:

档位1: 0~200mV   → 直接进ADC(不衰减)
档位2: 0~2V      → 1:1 分压
档位3: 0~20V     → 10:1 分压
档位4: 0~200V    → 100:1 分压
档位5: 0~1000V   → 500:1 分压

注意:输入阻抗问题

分压电阻的取值很关键。电阻太大,噪声大;电阻太小,会拉低被测电压。我一般取1MΩ级别,这样对被测电路影响小。曾经有个项目,用了10kΩ分压,结果测高阻信号时,读数直接偏了20%。

3.2.2 电流测量:采样电阻+差分放大

测电流的原理更简单——让电流流过一个小电阻,测它两端的电压。比如1A电流流过0.1Ω电阻,产生100mV压降。

但这里有个坑:采样电阻的功率。1A×0.1Ω=0.1W,看起来不大。但如果是10A呢?10A×0.1Ω=10W,电阻会烫得能煎鸡蛋。

所以我建议:

  • 小电流(mA级):用10Ω~100Ω采样电阻
  • 中电流(A级):用0.1Ω~1Ω采样电阻
  • 大电流(10A级):用mΩ级采样电阻,配合高精度运放放大

差分放大器的作用,就是把采样电阻两端的微小电压差,放大到ADC能识别的范围。我常用的运放是LMV321或MCP6001,便宜又好用。

3.2.3 电阻测量:恒流源法

测电阻,本质上是测电压。给被测电阻通一个已知电流,测它两端的电压,然后R=V/I。

实际电路里,我一般用恒流源芯片(比如LM334)或者运放搭的恒流源。注意档位切换:

档位 恒流电流 测量范围
200Ω 1mA 0~200mV对应0~200Ω
2kΩ 100μA 0~200mV对应0~2kΩ
20kΩ 10μA 0~200mV对应0~20kΩ
200kΩ 1μA 0~200mV对应0~200kΩ
2MΩ 0.1μA 0~200mV对应0~2MΩ

小技巧:测高阻时,要注意漏电流。PCB板上的焊剂残留、潮湿环境,都会导致漏电流。我一般会在PCB上画一个「保护环」(Guard Ring),把高阻引脚围起来,效果立竿见影。

3.3 STM32内部ADC配置:从理论到代码

好,前端电路搞定了,信号已经调理到0~3.3V了。接下来就是STM32的ADC登场。

STM32的ADC模块,我常用的是:

  • 12位分辨率(可配置为6、8、10、12位)
  • 最多18个通道(16个外部+2个内部)
  • 支持单次、连续、扫描、间断模式
  • 内置电压参考(VREFINT,约1.2V)

3.4.1 基本配置步骤

我个人习惯用HAL库,但也会直接操作寄存器做优化。下面是一个典型的ADC初始化代码:

// ADC初始化配置
void MX_ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};

    // 1. 使能ADC时钟
    __HAL_RCC_ADC1_CLK_ENABLE();

    // 2. 配置ADC句柄
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;  // 时钟分频
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;            // 12位分辨率
    hadc1.Init.ScanConvMode = DISABLE;                     // 单通道模式
    hadc1.Init.ContinuousConvMode = DISABLE;               // 单次转换
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;      // 软件触发
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;            // 右对齐
    hadc1.Init.NbrOfConversion = 1;                        // 1个转换通道
    hadc1.Init.DMAContinuousRequests = DISABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;

    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }

    // 3. 配置通道
    sConfig.Channel = ADC_CHANNEL_0;          // PA0引脚
    sConfig.Rank = 1;                         // 转换顺序
    sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;  // 采样时间

    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
}

采样时间的选择:

采样时间 = (采样周期数 + 12.5个周期) × ADC时钟周期。比如ADC时钟12MHz,采样周期3个周期,那么总时间 = (3+12.5)/12M ≈ 1.29μs。采样时间越长,精度越高,但速度越慢。我一般测直流信号用3~7个周期,测音频信号用1.5个周期。

3.4.2 读取ADC值

配置好之后,读取就简单了:

// 单次读取ADC值
uint16_t Read_ADC_Value(void)
{
    HAL_ADC_Start(&hadc1);                    // 启动转换
    HAL_ADC_PollForConversion(&hadc1, 100);   // 等待转换完成
    return HAL_ADC_GetValue(&hadc1);          // 读取结果
}

// 多次采样取平均(我常用的方法)
uint16_t Read_ADC_Average(uint8_t times)
{
    uint32_t sum = 0;
    for(uint8_t i = 0; i < times; i++)
    {
        sum += Read_ADC_Value();
        HAL_Delay(1);  // 间隔1ms
    }
    return (uint16_t)(sum / times);
}

注意:参考电压的稳定性

ADC的精度直接取决于参考电压。如果VREF+有纹波,ADC结果就会有噪声。我一般会在VREF+引脚加一个10μF+0.1μF的滤波电容。如果要求更高,可以用外部基准电压源,比如TL431或REF3033。

3.4.3 校准与误差补偿

STM32的ADC有内置校准功能。我每次初始化后都会调用:

// ADC校准
HAL_ADCEx_Calibration_Start(&hadc1);

但即使校准了,实际测量还是有误差。我一般会在软件里做两点校准:

  • 零点校准:将ADC输入端短接到GND,读取的值作为零点偏移
  • 满量程校准:输入一个已知的精确电压(比如2.5V),计算增益误差

然后实际电压 = (ADC值 - 零点偏移) × (参考电压 / (满量程值 - 零点偏移))。

我曾经在一个项目中,没做校准就直接用,结果测出来的电压偏差了3%。后来加了校准,误差降到了0.1%以内。嗯,这个步骤千万别省。

3.5 实战中的坑与经验

最后,分享几个我踩过的坑:

  1. ADC输入阻抗不匹配:STM32的ADC输入阻抗约50kΩ,如果前端电路输出阻抗太高,会拉低电压。我一般会在ADC前加一个运放做电压跟随器。
  2. 数字噪声耦合:STM32的GPIO翻转时会产生噪声,耦合到ADC输入。我习惯把ADC引脚周围的GPIO设为模拟模式,并且远离高频信号线。
  3. 温度漂移:ADC的零点会随温度变化。如果产品工作温度范围宽,建议加温度传感器做补偿。
  4. 电源纹波:蓝牙发射时电流会突然增大,导致电源电压波动。我一般在ADC采样时关闭蓝牙发射,或者加一个足够大的储能电容。

我的个人习惯:每次画PCB时,ADC相关的模拟电路和数字电路严格分地。模拟地单独走线,在电源入口处单点连接。这个习惯帮我避免了很多噪声问题。

好了,这一章的内容就到这里。ADC采样看似简单,但要做好、做准,需要前端电路、ADC配置、软件校准三者配合。下一章,咱们会把这些数据通过蓝牙发出去,真正实现无线传输。

记住:理论是基础,实践出真知。建议你拿块开发板,接个电位器,实际测一测ADC的值,看看和万用表读数差多少。动手试试,比看十遍文章都管用。