万用表模块共有四个功能:测电压、测电流、测电阻(2K/30K档),以及二极管档功能。其中二极管档与电阻档30K档复用硬件,下面我们来依次讲解:
一、挡位切换

由于板子空间极为受限,只够塞得下一组香蕉头,为了协调“数控电源”、“电压表”、“电阻表”、“电流表”,我们必须进行选通控制,因此就有了上图的设计~
对于红表笔,数控电源部分的防倒灌设计(mx5050t + Mos管)刚好构成一个开关,万用表的每个功能都分别使用一个大电流光耦隔开。需要用哪一个功能,就打开哪一个开关,联通红表笔即可~
对于黑表笔,对于数控电源部分,由于需要流过大电流,因此使用栅极驱动芯片 + Mos管 的组合;对于万用表,仅有电流表和电阻表需要接到模拟地,不需要过大电流,因此一个普通的Nmos即可达成要求~
这里铺垫一个前提:mx5050t + Mos管构成的开关并不理想,根据mx5050t的数据手册,在关断状态下,mx5050t为 红表笔端 -> 电源端提供了一个2mA的路径,电源端还接了一个500欧的输出泄放电阻(可开关),在万用表的所有挡位中,除了电流表外均开启。
二、电压表

电压表的硬件还是很简单的:前级先经过47K+2.2K的电阻网络分压,将压差从设计的±36V降低到±1.61V;后级的U56.1、U56.2、U56.4构成一个放大倍率为1的仪表放大器,同时将输出抬升1.65V(DMM_VREF)。这样,最终的输出就是1.65±1.61 = 0.04~3.26V,经过ADC读取后就可以换算出真实的电压差~
需要注意的是,在表笔什么都没有接时,红端和黑端都是悬空的!这时候就需要有东西来泄放由运放Ib引起的浮空电压。对于红表笔来说,前面铺垫提到的,则构成了一条 红端-> 500Ω -> PGND -> AGND的泄放路径;黑端则需要补充一个1K的电阻。
但是不得不说,这样做的代价就是万用表的前端阻值太低了!在测量阻抗偏高的电压时(带载能力弱),测量值会偏低。
三、电流表

电流表的设计没什么好讲的,由于光耦的带载能力限制,我们的电流测量上限只能给到2A。电路原理很简单:电流流过15mΩ电阻产生压降,TP181将压降放大50倍后,叠加1.65V的直流偏置(用来实现测负电压)输出到DAC测量。我们简单算一下:
四、电阻表

电阻表就更简单了,电阻分压算电阻嘛,知道总电压、知道基准电阻阻值、知道待测电阻电压,阻值自然是能计算的。
不过需要注意的是,这里依旧借用了前面“铺垫”处提到的泄放电路来稳定电压,但是代价就是测量范围受限。
五、软件讲解
万用表的ADC数据处理是实现高精度、稳定测量的核心环节,采用定时器触发+DMA多通道同步采集的硬件架构,搭配解交错、去极值滤波、过采样升精度、硬件校准、IIR平滑滤波的五级软件处理流程,将STM32内置12位ADC的原始采样数据,转化为精准、无抖动的电压/电流/电阻物理测量值。
5.1 采集初始化配置
在启动数据处理前,先完成ADC DMA采集与触发定时器的基础配置,为自动化、同步化采样提供硬件支撑:
// 全局变量 - ADC原始数据缓存
// 三个通道,每个通道200个采样点,总计600个数据
volatile uint16_t dmm_adc_raw_buf[600] = {0};
// 启动ADC DMA采集(600个数据)
HAL_ADC_Start_DMA(&hadc1, (uint32_t *) dmm_adc_raw_buf, 600);
// 配置定时器8:20Khz触发(预分频0,自动重装7199)
__HAL_TIM_SET_PRESCALER(&htim8, 0);
__HAL_TIM_SET_AUTORELOAD(&htim8, 7200 - 1);
AdcTim_ON(); // 开启ADC触发
- 缓存定义:
dmm_adc_raw_buf为600字节的易失性数组,存储3个测量通道×200个采样点的交错原始数据,volatile防止编译器优化,保证DMA数据实时同步。 - DMA采集:启用ADC DMA模式,自动将采样结果搬运至缓存,无需CPU逐次读取,解放主控资源。
- 定时器触发:TIM8配置为20kHz触发频率,固定采样周期,确保电压、电流、电阻三通道采样时序严格同步,避免时序误差。
5.2 ADC转换完成回调(核心数据处理)
当DMA完成600个数据搬运后,触发CB_DMM_ADC_ConvCpltCallback回调函数,进入完整的数据处理流程,处理完成后重启采集,实现循环测量。
5.2.1 暂停采集与数据解交错
ADC多通道扫描模式会按通道0→通道1→通道2的顺序交错存储数据,需先拆分数据并暂停采集,防止缓存被覆盖:
AdcTim_OFF(); // 关闭ADC触发定时器
// 1. 解交错:将600个交错数据拆分为3个通道各200个点
uint16_t ch1_data[200]; // 电压通道
uint16_t ch2_data[200]; // 电流通道
uint16_t ch3_data[200]; // 电阻通道
int idx = 0;
for (int i = 0; i < 600; i += 3) {
ch1_data[idx] = dmm_adc_raw_buf[i]; // 通道2(电压)
ch2_data[idx] = dmm_adc_raw_buf[i+1]; // 通道1(电流)
ch3_data[idx] = dmm_adc_raw_buf[i+2]; // 通道0(电阻)
idx++;
}
- 暂停触发:数据处理期间关闭定时器触发,避免新采样数据覆盖缓存,保证数据完整性。
- 解交错:将连续的交错采样数据,按通道拆分到三个独立数组,每个通道得到200个纯净采样点。
5.2.2 排序去极值滤波(去毛刺)
针对环境电磁干扰、电源噪声产生的尖峰数据,采用排序+剔除极值的方式消除毛刺:
// 2. 排序(用于后续去毛刺)
qsort(ch1_data, 200, sizeof(uint16_t), compare_uint16_ternary);
qsort(ch2_data, 200, sizeof(uint16_t), compare_uint16_ternary);
qsort(ch3_data, 200, sizeof(uint16_t), compare_uint16_ternary);
// 3. 求和(去毛刺:去掉前4和后4个极值)
uint32_t ch1_sum = 0, ch2_sum = 0, ch3_sum = 0;
for (uint8_t i = 4; i < 196; i++) {
ch1_sum += ch1_data[i];
ch2_sum += ch2_data[i];
ch3_sum += ch3_data[i];
}
- 快速排序:将200个采样点从小到大排序,极值会集中在数组首尾位置。
- 剔除极值:去掉前4个最小值和后4个最大值,仅保留中间192个有效数据,彻底滤除尖峰噪声干扰。
5.2.3 过采样升精度(12位→14位)
STM32内置ADC为12位精度,通过过采样+右移算法,软件提升至14位高精度:
// 4. 过采样到14bit + 均值滤波(192个数据求和后/12)
uint16_t ch1_14bit = 0, ch2_14bit = 0, ch3_14bit = 0;
ch1_14bit = (ch1_sum >> 2) / 12;
ch2_14bit = (ch2_sum >> 2) / 12;
ch3_14bit = (ch3_sum >> 2) / 12;
- 过采样原理:12位ADC(0~4095)通过4倍过采样右移2位,精度升级为14位(0~16383)。
- 均值滤波:192个有效数据求和后除以12,进一步平滑数据,大幅提升测量分辨率。
5.2.4 硬件校准与物理量转换
将14位ADC数字量,结合硬件校准参数,线性转换为实际电压、电流、电阻物理值:
// 5. 校准计算(转换为实际物理值)
// 电压校准
int32_t temp1 = (int32_t)ch1_14bit - (8192 - UserParam.DMM_Voltage_Original);
float raw_voltage = (float) temp1 * (3.3f / 16383.0f) * (temp1 >= 0
? UserParam.DMM_Voltage_Factor_R
: UserParam.DMM_Voltage_Factor_B);
// 电流校准
int32_t temp2 = (int32_t)ch2_14bit - (8192 + UserParam.DMM_Current_Original);
float raw_current = (float) temp2 * (3.3f / 16383.0f) * UserParam.DMM_Current_Factor;
// 电阻校准
int16_t Res_Original;
dmm_res_mode == DMM_RES_MODE_200 ? Res_Original = UserParam.DMM_Res_R200_Original : (Res_Original = UserParam.DMM_Res_R2K_Original);
float raw_resistance_voltage = (float)(ch3_14bit + Res_Original) * (3.3f / 16383.0f);
- 零点校准:扣除硬件偏移参数(
Original),补偿运放、分压电阻的硬件误差。 - 量程适配:电压分正负量程、电阻分200Ω/2kΩ量程,匹配万用表多档位测量需求。
- 线性转换:基于3.3V ADC参考电压,将数字量精准转换为真实物理量。
5.2.5 IIR一阶平滑滤波
通过无限脉冲响应(IIR)滤波,抑制高频噪声,让测量值无抖动、平滑输出:
// 6. IIR滤波
DMM_Voltage = iir_filter(raw_voltage, iir_prev_voltage, IIR_ALPHA_VOLTAGE);
DMM_Current = iir_filter(raw_current, iir_prev_current, IIR_ALPHA_CURRENT);
DMM_Resistance_Voltage = iir_filter(raw_resistance_voltage, iir_prev_resistance, IIR_ALPHA_RESISTANCE);
// 7. 更新滤波历史值
iir_prev_voltage = DMM_Voltage;
iir_prev_current = DMM_Current;
iir_prev_resistance = DMM_Resistance_Voltage;
- 滤波原理:结合当前测量值与历史滤波值加权计算,兼顾响应速度与稳定性。
- 独立系数:电压、电流、电阻使用专属滤波系数,适配不同物理量的测量特性。
5.2.6 重启采集,循环工作
数据处理完成后,重新开启定时器触发,进入下一轮采样→处理循环:
AdcTim_ON(); // 重新开启ADC触发定时器
5.3 模块总结
本ADC数据处理模块是万用表实现高精度测量的核心:
效果层面:将STM32 12位ADC软升级为14位高精度测量,输出稳定、无抖动的电压/电流/电阻值,完全满足手持万用表的实用测量需求。
硬件层面:定时器触发+DMA搬运,实现三通道同步自动化采样,不占用CPU资源。
算法层面:通过解交错、去极值、过采样、校准、IIR滤波五级处理,消除噪声、提升精度、补偿硬件误差。
偷懒用ai~嘿嘿





从立创开源广场过来的,现在的大学生都这么硬了么。我们当年也就点个灯….
这是大二学生能做出来的?太强了