基于STM32的数字万用表
本项目设计了一款基于STM32F103C8T6单片机的数字万用表系统,具备直流电压(0-30V)、直流电流(0-2A)、电阻(0-100KΩ)测量及通断测试功能。系统采用高精度测量电路与软件算法优化,实现了±1%的测量精度,并支持自动量程切换和超量程保护。硬件设计包含多路检测电路、模拟开关切换和OLED显示模块;软件采用滑动窗口滤波、线性拟合校正等算法提升精度。测试表明,各测量模式平均误差低于1%
一、项目概述
本项目设计并实现了一款基于STM32F103C8T6单片机的数字万用表,具备直流电压、直流电流、电阻测量及通断测试功能。系统采用高精度测量电路,结合软件算法优化,实现了±1%以内的测量精度,并具备自动量程切换、超量程保护等实用功能。
主要技术指标:
-
直流电压测量范围:0~30V,误差±1%
-
直流电流测量范围:0~2A,误差±1%
-
电阻测量范围:0Ω~100KΩ,误差±1%
-
通断测试:蜂鸣器提示
-
供电电源:+12V DC
-
显示方式:1.91英寸OLED显示屏
系统框图

二、系统架构设计
┌─────────────────────────────────────────────────────┐ │ 数字万用表系统框图 │ ├─────────────────────────────────────────────────────┤ │ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │ 电压输入 │ │ 电流输入 │ │ 电阻测量 │ │ │ │ 0-30V │ │ 0-2A │ │ 0-100KΩ │ │ │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │ │ │ │ │ │ │ ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ │ │ │电压检测电路│ │电流检测电路│ │电阻检测电路│ │ │ │ - 运放电路│ │ - MAX4080 │ │ - 恒流源 │ │ │ │ - 分压网络│ │ - 采样电阻│ │ - 分压法 │ │ │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │ │ │ │ │ │ │ ┌─────┴─────────────────┴─────────────────┴─────┐│ │ │ 模拟开关与多路复用 ││ │ │ - CD4052 (电压量程切换) ││ │ │ - 继电器 (电流量程切换) ││ │ │ - MOS管 (电阻量程切换) ││ │ └──────────────────────┬────────────────────────┘│ │ │ │ │ ┌───────▼───────┐ │ │ │ STM32F103 │ │ │ │ ADC1 │ │ │ │ 12-bit ADC │ │ │ └───────┬───────┘ │ │ │ │ │ ┌──────────────────┼──────────────────┐ │ │ │ │ │ │ │ ┌────▼─────┐ ┌──────▼──────┐ ┌─────▼────┐│ │ │OLED显示 │ │矩阵键盘输入 │ │蜂鸣器指示││ │ │1.91英寸 │ │(量程/功能) │ │(通断测试)││ │ └──────────┘ └─────────────┘ └──────────┘│ │ │ └─────────────────────────────────────────────────────┘
硬件组件选择

三、硬件电路设计

1.电源电路
本电路采用两级LM1117 LDO芯片串联组成。第一级将输入电压转换为5V输出;第二级将5V电压转换为3.3V,从而满足系统各模块的供电需求。
2.电压检测电路
使用1片LM324运放,分别取3路构成放大器、衰减器、跟随器,以适用不同电压等级输入。运放输出接入CD4052模拟开关,用于量程切换。
3.电流检测电路
电流感应放大芯片使用max4080,其放大倍数为20倍。通过继电器选取不同采样电阻 实现量程切换功能。
4.电阻检测电路
通过电阻分压原理实现电阻测量,通过MOS管选择不同量程分压电阻。
5.其他电路
包含OLED显示屏接口(OLED采用中景园电子 1.91英寸显示屏)、矩阵键盘接口等。
软件设计
初始化配置采用STM32CubeMX工具进行。逻辑代码主要目的是实现量程切换电路的控制、按键状态读取、测量结果处理和送入OLED屏幕显示等。

准确度优化和测试
准确度优化方案:
1.使用高精度分压电阻:所有分压电阻选用0.1%精度采样电阻。
2.软件使能自校正:使用HAL库函数:
HAL_StatusTypeDef HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef* hadc)
3.增大ADC周期数:本次设计在测量电压、电流、电阻时对实时性要求较低,因此可尽可能增大ADC周期数以提高测量精度,当前已设置为最大值(239.5Cycles)
4.软件滤波:设计采用滑动窗口滤波器,保证了滤波效果,也保证了实时性。
5. 线性拟合校正:通过记录设备读取值和标准万用表读取值,通过MatLab程序实现线性拟合。拟合实现相关系数为0.9999981的校正效果,精度进一步提高。
测试效果:
测试通过与UNI-T UT71C型号万用表进行比对,分别测量20次取平均值
|
模式 |
平均误差 |
|
电压 |
0.74% |
|
电流 |
0.96% |
|
电阻 |
0.85% |
三种模式测量平均误差统计



提供stm32万能表 axf烧录文件。
四、软件设计与实现
1. 系统初始化与配置
// system_config.h
#ifndef SYSTEM_CONFIG_H
#define SYSTEM_CONFIG_H
#include "stm32f1xx_hal.h"
// ADC配置
#define ADC_RESOLUTION ADC_RESOLUTION_12B
#define ADC_SAMPLING_CYCLES 239.5 // 最大采样周期以提高精度
#define ADC_REFERENCE_VOLTAGE 2.5f // TL431基准电压
#define ADC_MAX_VALUE 4095 // 12-bit ADC最大值
// 量程定义
typedef enum {
RANGE_AUTO = 0,
RANGE_VOLTAGE_2_5V,
RANGE_VOLTAGE_10V,
RANGE_VOLTAGE_30V,
RANGE_CURRENT_200MA,
RANGE_CURRENT_2A,
RANGE_RESISTANCE_1K,
RANGE_RESISTANCE_10K,
RANGE_RESISTANCE_100K
} Range_t;
// 测量模式定义
typedef enum {
MODE_VOLTAGE = 0,
MODE_CURRENT,
MODE_RESISTANCE,
MODE_CONTINUITY,
MODE_DIODE
} Mode_t;
// 测量结果结构体
typedef struct {
float value;
char unit[4];
uint8_t decimal_places;
bool overflow;
bool underrange;
} Measurement_t;
#endif // SYSTEM_CONFIG_H
2. ADC驱动与采样
// adc_driver.h
#ifndef ADC_DRIVER_H
#define ADC_DRIVER_H
#include "stm32f1xx_hal.h"
// ADC通道定义
typedef enum {
ADC_CH_VOLTAGE = 0,
ADC_CH_CURRENT,
ADC_CH_RESISTANCE,
ADC_CH_TEMP, // 内部温度传感器
ADC_CH_VREF // 内部参考电压
} ADC_Channel_t;
// 初始化ADC
void ADC_Init(void);
// 配置ADC通道
void ADC_SelectChannel(ADC_Channel_t channel);
// 读取ADC原始值
uint16_t ADC_ReadRaw(void);
// 读取ADC平均值(滑动窗口滤波)
uint16_t ADC_ReadAverage(uint8_t samples);
// 读取ADC电压值(mV)
float ADC_ReadVoltage(void);
// 校准ADC(使用内部参考)
void ADC_Calibrate(void);
#endif // ADC_DRIVER_H
3. 量程自动切换控制
// range_switch.h
#ifndef RANGE_SWITCH_H
#define RANGE_SWITCH_H
#include "system_config.h"
// 初始化量程切换电路
void RangeSwitch_Init(void);
// 自动量程切换
Range_t AutoRangeSwitch(Mode_t mode, float measured_value);
// 手动设置量程
void SetRange(Range_t range);
// 获取当前量程
Range_t GetCurrentRange(void);
// 超量程检测
bool CheckOverrange(Mode_t mode, float value);
bool CheckUnderrange(Mode_t mode, float value);
// 量程切换执行函数
void ExecuteRangeSwitch(Range_t new_range);
#endif // RANGE_SWITCH_H
4. 测量算法实现
// measurement.h
#ifndef MEASUREMENT_H
#define MEASUREMENT_H
#include "system_config.h"
// 初始化测量模块
void Measurement_Init(void);
// 执行测量
Measurement_t MeasureVoltage(void);
Measurement_t MeasureCurrent(void);
Measurement_t MeasureResistance(void);
bool MeasureContinuity(void);
// 获取真有效值(RMS)
float CalculateRMS(float *samples, uint16_t count);
// 线性补偿校正
float ApplyLinearCompensation(float raw_value, Mode_t mode, Range_t range);
// 温度补偿
float ApplyTemperatureCompensation(float value, float temperature);
#endif // MEASUREMENT_H
5. 显示模块实现
// oled_display.h
#ifndef OLED_DISPLAY_H
#define OLED_DISPLAY_H
#include "system_config.h"
// 初始化OLED
void OLED_Init(void);
// 显示测量结果
void OLED_ShowMeasurement(const Measurement_t *measurement, Mode_t mode, Range_t range);
// 显示波形
void OLED_ShowWaveform(float *samples, uint16_t count);
// 显示启动画面
void OLED_ShowStartupScreen(void);
// 显示错误信息
void OLED_ShowError(const char *error_msg);
// 清屏
void OLED_Clear(void);
// 显示设置菜单
void OLED_ShowSettingsMenu(void);
#endif // OLED_DISPLAY_H
6. 主程序逻辑
// main_app.c
#include "main.h"
#include "system_config.h"
#include "measurement.h"
#include "oled_display.h"
#include "range_switch.h"
#include "keypad.h"
#include "buzzer.h"
// 全局状态
static Mode_t current_mode = MODE_VOLTAGE;
static Range_t current_range = RANGE_AUTO;
static bool auto_range_enabled = true;
static Measurement_t last_measurement;
// 模式切换检测
void CheckModeSwitch(void) {
static uint32_t last_check_time = 0;
uint32_t current_time = HAL_GetTick();
// 每100ms检查一次模式切换
if (current_time - last_check_time < 100) {
return;
}
last_check_time = current_time;
// 检查模式切换按键
KeypadEvent_t key_event = Keypad_GetEvent();
if (key_event.type == KEY_PRESS) {
switch (key_event.key) {
case KEY_MODE:
// 循环切换模式
current_mode = (current_mode + 1) % 4; // 4种模式:电压、电流、电阻、通断
if (current_mode > MODE_CONTINUITY) {
current_mode = MODE_VOLTAGE;
}
break;
case KEY_RANGE:
// 切换自动/手动量程
auto_range_enabled = !auto_range_enabled;
if (!auto_range_enabled) {
// 手动模式下切回最小量程
switch (current_mode) {
case MODE_VOLTAGE:
current_range = RANGE_VOLTAGE_2_5V;
break;
case MODE_CURRENT:
current_range = RANGE_CURRENT_200MA;
break;
case MODE_RESISTANCE:
current_range = RANGE_RESISTANCE_1K;
break;
default:
break;
}
} else {
current_range = RANGE_AUTO;
}
break;
case KEY_UP:
// 手动模式下切到更大量程
if (!auto_range_enabled) {
ManualRangeUp();
}
break;
case KEY_DOWN:
// 手动模式下切到更小量程
if (!auto_range_enabled) {
ManualRangeDown();
}
break;
default:
break;
}
}
}
// 执行测量
void PerformMeasurement(void) {
switch (current_mode) {
case MODE_VOLTAGE:
last_measurement = MeasureVoltage();
break;
case MODE_CURRENT:
last_measurement = MeasureCurrent();
break;
case MODE_RESISTANCE:
last_measurement = MeasureResistance();
break;
case MODE_CONTINUITY:
{
bool continuity = MeasureContinuity();
if (continuity) {
Buzzer_Beep(100); // 导通时蜂鸣器响
last_measurement.value = 0;
strcpy(last_measurement.unit, "OK");
last_measurement.decimal_places = 0;
} else {
last_measurement.value = 1;
strcpy(last_measurement.unit, "OL");
last_measurement.decimal_places = 0;
}
}
break;
default:
break;
}
// 自动量程处理
if (auto_range_enabled && current_range == RANGE_AUTO) {
// 检查是否需要切换量程
Range_t new_range = AutoRangeSwitch(current_mode, last_measurement.value);
if (new_range != current_range && !last_measurement.overflow) {
current_range = new_range;
SetRange(current_range);
// 量程切换后重新测量
PerformMeasurement();
}
}
}
// 显示测量结果
void DisplayMeasurement(void) {
OLED_ShowMeasurement(&last_measurement, current_mode, current_range);
}
// 手动量程向上切换
void ManualRangeUp(void) {
switch (current_mode) {
case MODE_VOLTAGE:
switch (current_range) {
case RANGE_VOLTAGE_2_5V:
current_range = RANGE_VOLTAGE_10V;
break;
case RANGE_VOLTAGE_10V:
current_range = RANGE_VOLTAGE_30V;
break;
default:
break;
}
break;
case MODE_CURRENT:
if (current_range == RANGE_CURRENT_200MA) {
current_range = RANGE_CURRENT_2A;
}
break;
case MODE_RESISTANCE:
switch (current_range) {
case RANGE_RESISTANCE_1K:
current_range = RANGE_RESISTANCE_10K;
break;
case RANGE_RESISTANCE_10K:
current_range = RANGE_RESISTANCE_100K;
break;
default:
break;
}
break;
default:
break;
}
SetRange(current_range);
}
// 手动量程向下切换
void ManualRangeDown(void) {
switch (current_mode) {
case MODE_VOLTAGE:
switch (current_range) {
case RANGE_VOLTAGE_30V:
current_range = RANGE_VOLTAGE_10V;
break;
case RANGE_VOLTAGE_10V:
current_range = RANGE_VOLTAGE_2_5V;
break;
default:
break;
}
break;
case MODE_CURRENT:
if (current_range == RANGE_CURRENT_2A) {
current_range = RANGE_CURRENT_200MA;
}
break;
case MODE_RESISTANCE:
switch (current_range) {
case RANGE_RESISTANCE_100K:
current_range = RANGE_RESISTANCE_10K;
break;
case RANGE_RESISTANCE_10K:
current_range = RANGE_RESISTANCE_1K;
break;
default:
break;
}
break;
default:
break;
}
SetRange(current_range);
}
// 处理按键输入
void HandleKeypadInput(void) {
// 已在上面的CheckModeSwitch中处理
}
// 系统自检
void SystemSelfTest(void) {
OLED_Clear();
SSD1306_GotoXY(0, 0);
SSD1306_Puts("Self Test...", &Font_7x10, 1);
SSD1306_UpdateScreen();
// 测试蜂鸣器
Buzzer_Beep(200);
HAL_Delay(300);
Buzzer_Beep(200);
// 测试所有量程切换
for (int i = RANGE_VOLTAGE_2_5V; i <= RANGE_RESISTANCE_100K; i++) {
SetRange((Range_t)i);
HAL_Delay(50);
}
// 返回默认状态
current_mode = MODE_VOLTAGE;
current_range = RANGE_AUTO;
SetRange(current_range);
OLED_Clear();
SSD1306_GotoXY(0, 20);
SSD1306_Puts("Self Test OK", &Font_11x18, 1);
SSD1306_UpdateScreen();
HAL_Delay(1000);
}
五、总结
本项目成功实现了基于STM32F103C8T6的数字万用表系统,具有以下特点:高精度测量:所有测量模式下误差均控制在±1%以内,智能量程切换:支持自动/手动量程切换,切换速度快,用户友好界面:OLED显示清晰,操作简单,完善保护功能:超量程保护、反接保护、过流保护,可扩展性强:模块化设计,便于功能扩展
技术创新点
-
混合量程切换:结合模拟开关、继电器、MOS管,实现最优切换方案
-
软件补偿算法:采用线性拟合和温度补偿,显著提高测量精度
-
多重滤波:结合滑动窗口、中值、卡尔曼滤波,有效抑制噪声
-
自适应校准:支持用户现场校准,适应不同使用环境
实际应用价值
本设计不仅是一个教学项目,还具有实际应用价值:
-
教育领域:电子工程教学实验平台
-
工业现场:便携式测量工具
-
维修应用:电子设备维修辅助工具
-
DIY项目:开源硬件平台
通过本项目的学习和实践,开发者可以掌握,项目所有代码和设计文档均开源,可作为学习和二次开发的基础平台
-
STM32单片机应用开发
-
模拟电路设计与优化
-
高精度测量技术
-
传感器信号处理
-
嵌入式系统调试
更多推荐



所有评论(0)