一、项目概述

   本项目设计并实现了一款基于STM32F103C8T6单片机的数字万用表,具备直流电压、直流电流、电阻测量及通断测试功能。系统采用高精度测量电路,结合软件算法优化,实现了±1%以内的测量精度,并具备自动量程切换、超量程保护等实用功能。

主要技术指标

  • 直流电压测量范围:0~30V,误差±1%

  • 直流电流测量范围:0~2A,误差±1%

  • 电阻测量范围:0Ω~100KΩ,误差±1%

  • 通断测试:蜂鸣器提示

  • 供电电源:+12V DC

  • 显示方式:1.91英寸OLED显示屏

系统框图

InzbXg7McKvohQnkXeNYdK1koNu6gvAv7gY9dEEf.png

二、系统架构设计

┌─────────────────────────────────────────────────────┐
│               数字万用表系统框图                     │
├─────────────────────────────────────────────────────┤
│                                                     │
│  ┌───────────┐    ┌───────────┐    ┌───────────┐  │
│  │ 电压输入  │    │ 电流输入  │    │ 电阻测量  │  │
│  │  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屏幕显示等。

gW7xQaYTBEwha5esem2egdIYxCQUH49FqIAPaK9c.png

准确度优化和测试

准确度优化方案:

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显示清晰,操作简单,完善保护功能:超量程保护、反接保护、过流保护,可扩展性强:模块化设计,便于功能扩展

技术创新点

  1. 混合量程切换:结合模拟开关、继电器、MOS管,实现最优切换方案

  2. 软件补偿算法:采用线性拟合和温度补偿,显著提高测量精度

  3. 多重滤波:结合滑动窗口、中值、卡尔曼滤波,有效抑制噪声

  4. 自适应校准:支持用户现场校准,适应不同使用环境

实际应用价值

本设计不仅是一个教学项目,还具有实际应用价值:

  • 教育领域:电子工程教学实验平台

  • 工业现场:便携式测量工具

  • 维修应用:电子设备维修辅助工具

  • DIY项目:开源硬件平台

通过本项目的学习和实践,开发者可以掌握,项目所有代码和设计文档均开源,可作为学习和二次开发的基础平台

  1. STM32单片机应用开发

  2. 模拟电路设计与优化

  3. 高精度测量技术

  4. 传感器信号处理

  5. 嵌入式系统调试

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐