编程技术分享

分享编程知识,探讨技术创新

0%

简介:设计采用STM32F103C8T6单片机制作一款简单易用的数字万用表。其具有电压、电流、电阻、通断测量功能,拥有量程切换、波形显示、RMS值计算、超量程保护功能。该设计外围电路稳定可靠、价格低廉。

嵌入式数字万用表系统软件架构与代码实现

关注微信公众号,提前获取相关推文

作为一名高级嵌入式软件开发工程师,我将为您详细阐述基于 STM32F103C8T6 单片机的数字万用表项目软件架构设计与 C 代码实现方案。该方案旨在构建一个可靠、高效、可扩展的系统平台,涵盖从需求分析到系统实现、测试验证和维护升级的完整嵌入式系统开发流程。

1. 需求分析与系统设计概述

1.1 需求分析

根据项目简介,该数字万用表的核心需求如下:

  • 测量功能:
    • 直流电压测量
    • 直流电流测量
    • 电阻测量
    • 通断性检测
  • 功能特性:
    • 自动/手动量程切换
    • 波形显示 (简化波形,例如条形图或趋势图)
    • 真有效值 (RMS) 计算 (用于交流信号,但此处主要针对直流,RMS 概念可用于噪声评估)
    • 超量程保护指示
  • 硬件平台:
    • STM32F103C8T6 单片机 (Cortex-M3 内核)
    • ADC (模数转换器) 用于模拟信号采集
    • 外部电路 (电压/电流/电阻分压、放大、保护电路)
    • 显示屏 (例如 LCD 或 OLED) 用于数据显示
    • 按键输入用于用户交互
    • 量程切换继电器 (或电子开关)
  • 软件要求:
    • 代码可靠性高,运行稳定
    • 测量精度和响应速度满足基本需求
    • 系统易于维护和升级
    • 代码结构清晰,可读性强

1.2 系统设计概述

为了满足上述需求,并考虑到嵌入式系统的特点,我们采用分层架构进行软件设计。分层架构能够有效解耦各个功能模块,提高代码的可维护性和可扩展性。 系统架构主要分为以下几个层次:

  • 硬件抽象层 (HAL, Hardware Abstraction Layer): 直接与硬件交互,封装底层硬件驱动,向上层提供统一的硬件访问接口。例如,ADC 驱动、GPIO 驱动、定时器驱动、显示屏驱动、按键驱动等。HAL 层旨在屏蔽硬件差异,使得上层应用代码可以独立于具体的硬件平台。
  • 板级支持包 (BSP, Board Support Package): 基于 HAL 层,提供针对特定硬件平台的初始化和配置功能。例如,系统时钟配置、外设初始化、中断配置、引脚复用配置等。BSP 层负责将 HAL 层提供的通用接口适配到具体的 STM32F103C8T6 开发板硬件。
  • 核心服务层 (Core Services Layer): 实现万用表的核心功能,例如数据采集、信号处理、测量计算、量程管理、数据显示、用户界面逻辑等。核心服务层依赖 HAL 和 BSP 层提供的硬件接口,并向上层应用层提供功能服务。
  • 应用层 (Application Layer): 构建用户应用程序,例如万用表的主程序逻辑、状态机管理、用户交互流程等。应用层调用核心服务层提供的功能模块,实现最终的用户功能。

2. 软件架构详细设计

2.1 硬件抽象层 (HAL)

HAL 层负责封装 STM32F103C8T6 的底层硬件驱动,提供标准化的 API 接口,方便上层模块调用。

  • ADC 驱动 (HAL_ADC):
    • 初始化 ADC 外设 (时钟、分辨率、采样时间、通道配置等)
    • 启动 ADC 转换
    • 获取 ADC 转换结果
    • 支持 DMA 模式 (可选,提高数据采集效率)
  • GPIO 驱动 (HAL_GPIO):
    • 初始化 GPIO 引脚 (输入/输出模式、上下拉、速度等)
    • 设置 GPIO 引脚输出电平
    • 读取 GPIO 引脚输入电平
  • 定时器驱动 (HAL_TIM):
    • 初始化定时器外设 (时钟源、预分频、计数模式、周期等)
    • 启动/停止定时器
    • 配置定时器中断
    • 生成 PWM 信号 (若需要,例如用于蜂鸣器或背光控制)
  • 显示屏驱动 (HAL_Display):
    • 初始化显示屏 (SPI/I2C 通信、分辨率、驱动芯片初始化)
    • 发送显示数据 (字符、图形)
    • 清屏、设置光标位置等操作
  • 按键驱动 (HAL_Keypad):
    • 初始化按键引脚 (输入模式、上下拉)
    • 检测按键按下/释放事件 (轮询或中断方式)
    • 按键去抖动处理

2.2 板级支持包 (BSP)

BSP 层负责针对 STM32F103C8T6 开发板进行初始化配置,将 HAL 层与具体硬件连接起来。

  • 系统时钟配置 (BSP_ClockConfig):
    • 配置 STM32F103C8T6 的系统时钟 (HSE/HSI, PLL 配置)
    • 设置外设时钟分频
  • 外设初始化 (BSP_PeriphInit):
    • 初始化 HAL 层所需的各个外设 (ADC, GPIO, TIM, SPI/I2C)
    • 配置引脚复用 (GPIO 复用为 ADC 通道、SPI/I2C 功能等)
  • 中断配置 (BSP_InterruptConfig):
    • 配置中断向量表
    • 使能所需的中断 (例如 ADC 转换完成中断、按键中断、定时器中断)
    • 编写中断服务函数 (ISR) 框架
  • 引脚映射 (BSP_PinMap):
    • 定义各个硬件模块使用的 GPIO 引脚 (例如 ADC 输入引脚、显示屏 CS/DC/RST 引脚、按键引脚、继电器控制引脚等)
    • 提供引脚号到 GPIO 端口和引脚的映射关系

2.3 核心服务层 (Core Services Layer)

核心服务层是万用表软件的核心,实现测量、显示、用户交互等关键功能。

  • 测量模块 (Measurement Module):
    • ADC 采样服务 (Measure_ADC):
      • 调用 HAL_ADC 驱动进行 ADC 采样
      • 读取 ADC 原始数据
      • 进行 ADC 校准 (可选,提高精度)
    • 电压测量服务 (Measure_Voltage):
      • 根据 ADC 原始数据和电压分压比计算电压值
      • 处理电压量程切换逻辑
      • 实现超量程检测
    • 电流测量服务 (Measure_Current):
      • 根据 ADC 原始数据和电流采样电阻计算电流值
      • 处理电流量程切换逻辑
      • 实现超量程检测
    • 电阻测量服务 (Measure_Resistance):
      • 使用恒流源或分压电路进行电阻测量
      • 根据 ADC 原始数据和电路参数计算电阻值
      • 处理电阻量程切换逻辑
      • 实现超量程检测
    • 通断性检测服务 (Measure_Continuity):
      • 通过电阻测量判断电路是否导通 (设定阈值)
      • 蜂鸣器提示 (可选)
    • RMS 计算服务 (Measure_RMS):
      • 对采集的电压/电流数据进行 RMS 计算 (虽然主要针对直流,但可用于评估噪声水平)
      • 采用滑动窗口或积分方法进行 RMS 计算
    • 量程管理服务 (Range_Manager):
      • 根据测量值自动切换量程 (自动量程模式)
      • 根据用户按键手动切换量程 (手动量程模式)
      • 管理当前量程状态
  • 显示模块 (Display Module):
    • 显示驱动接口 (Display_Driver):
      • 封装 HAL_Display 驱动,提供更高级的显示操作接口
      • 例如:Display_DrawPixel(), Display_DrawLine(), Display_DrawRect(), Display_DrawString()
    • UI 元素绘制服务 (Display_UI):
      • 绘制万用表 UI 界面元素 (例如:数字显示区、单位显示区、模式指示符、量程指示符、波形显示区)
      • 使用 Display_Driver 提供的接口进行绘制
      • 提供数字、字符串格式化显示功能
    • 波形显示服务 (Display_Waveform):
      • 将采集的电压/电流数据转换为波形图形 (例如条形图或趋势图)
      • 在显示屏上绘制波形
  • 用户界面模块 (UI Module):
    • 按键处理服务 (UI_Keypad):
      • 调用 HAL_Keypad 驱动获取按键事件
      • 解析按键事件 (例如:模式切换键、量程切换键、确认键、取消键等)
      • 调用相应的状态机处理函数
    • 状态机管理服务 (UI_StateMachine):
      • 管理万用表的各种工作状态 (例如:电压测量模式、电流测量模式、电阻测量模式、设置模式等)
      • 根据按键事件和系统状态进行状态切换
      • 调用相应的测量服务和显示服务
    • 菜单管理服务 (UI_Menu):
      • 实现菜单界面 (若需要,例如用于设置参数)
      • 提供菜单导航和选择功能

2.4 应用层 (Application Layer)

应用层是万用表软件的最上层,负责构建主程序逻辑和用户交互流程。

  • 主程序入口 (main.c):
    • 系统初始化 (BSP_Init)
    • 初始化核心服务模块
    • 进入主循环
  • 主循环 (Main Loop):
    • 轮询按键事件 (或等待中断事件)
    • 调用 UI_StateMachine 处理按键事件和更新系统状态
    • 根据当前系统状态调用相应的测量服务 (例如 Measure_Voltage(), Measure_Current(), Measure_Resistance(), Measure_Continuity())
    • 调用显示服务更新显示屏 (例如 Display_UI_Update())
    • 实现其他应用逻辑 (例如报警、数据记录等,此处简化)

3. 具体 C 代码实现 (关键模块示例)

为了演示代码架构和关键功能实现,以下提供部分核心模块的 C 代码示例,包含头文件和源文件,并进行详细注释。 由于篇幅限制,这里只展示核心模块的代码框架和关键功能,完整代码将远超 3000 行。

3.1 HAL 层代码示例 (HAL_ADC)

HAL_ADC.h (头文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#ifndef __HAL_ADC_H__
#define __HAL_ADC_H__

#include "stm32f10x.h"

typedef struct {
ADC_TypeDef* ADCx; // ADC 实例 (ADC1, ADC2, ...)
uint32_t Channel; // ADC 通道 (ADC_Channel_x)
uint32_t SampleTime; // ADC 采样时间 (ADC_SampleTime_xCycles)
// ... 其他 ADC 配置参数 ...
} HAL_ADC_ConfigTypeDef;

typedef enum {
HAL_ADC_STATE_RESET,
HAL_ADC_STATE_READY,
HAL_ADC_STATE_BUSY
} HAL_ADC_StateTypeDef;

typedef struct {
HAL_ADC_StateTypeDef State; // ADC 状态
// ... 其他 ADC 运行时数据 ...
} HAL_ADC_HandleTypeDef;

HAL_StatusTypeDef HAL_ADC_Init(HAL_ADC_HandleTypeDef* hadc, HAL_ADC_ConfigTypeDef* config);
HAL_StatusTypeDef HAL_ADC_Start(HAL_ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop(HAL_ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_PollForConversion(HAL_ADC_HandleTypeDef* hadc, uint32_t Timeout);
uint16_t HAL_ADC_GetValue(HAL_ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_DeInit(HAL_ADC_HandleTypeDef* hadc);

// ... DMA 相关函数 (可选) ...

#endif /* __HAL_ADC_H__ */

HAL_ADC.c (源文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include "HAL_ADC.h"

HAL_StatusTypeDef HAL_ADC_Init(HAL_ADC_HandleTypeDef* hadc, HAL_ADC_ConfigTypeDef* config) {
if (hadc == NULL || config == NULL) {
return HAL_ERROR; // 参数错误
}

hadc->State = HAL_ADC_STATE_RESET;

// 1. 使能 ADC 时钟
if (config->ADCx == ADC1) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
}
// ... 其他 ADC 时钟使能 ...

// 2. 配置 ADC 参数
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; // 通道数 1
ADC_Init(config->ADCx, &ADC_InitStructure);

// 3. 配置 ADC 通道
ADC_RegularChannelConfig(config->ADCx, config->Channel, 1, config->SampleTime); // 规则通道配置

// 4. 使能 ADC
ADC_Cmd(config->ADCx, ENABLE);

// 5. ADC 校准 (可选,提高精度)
ADC_ResetCalibration(config->ADCx);
while(ADC_GetResetCalibrationStatus(config->ADCx));
ADC_StartCalibration(config->ADCx);
while(ADC_GetCalibrationStatus(config->ADCx));

hadc->ADCx = config->ADCx;
hadc->Channel = config->Channel;
hadc->SampleTime = config->SampleTime;
hadc->State = HAL_ADC_STATE_READY;

return HAL_OK;
}

HAL_StatusTypeDef HAL_ADC_Start(HAL_ADC_HandleTypeDef* hadc) {
if (hadc == NULL || hadc->State != HAL_ADC_STATE_READY) {
return HAL_ERROR;
}
hadc->State = HAL_ADC_STATE_BUSY;
ADC_SoftwareStartConvCmd(hadc->ADCx, ENABLE); // 启动 ADC 转换
return HAL_OK;
}

HAL_StatusTypeDef HAL_ADC_Stop(HAL_ADC_HandleTypeDef* hadc) {
if (hadc == NULL || hadc->State != HAL_ADC_STATE_BUSY) {
return HAL_ERROR;
}
ADC_SoftwareStartConvCmd(hadc->ADCx, DISABLE); // 停止 ADC 转换
hadc->State = HAL_ADC_STATE_READY;
return HAL_OK;
}

HAL_StatusTypeDef HAL_ADC_PollForConversion(HAL_ADC_HandleTypeDef* hadc, uint32_t Timeout) {
if (hadc == NULL || hadc->State != HAL_ADC_STATE_BUSY) {
return HAL_ERROR;
}
uint32_t tickstart = 0;
tickstart = GetTick(); // 获取当前系统 Tick (需要实现 GetTick() 函数)

while(ADC_GetFlagStatus(hadc->ADCx, ADC_FLAG_EOC) == RESET) { // 等待转换完成标志
if (Timeout != HAL_MAX_DELAY) {
if ((GetTick() - tickstart) > Timeout) {
return HAL_TIMEOUT; // 超时
}
}
}
return HAL_OK;
}

uint16_t HAL_ADC_GetValue(HAL_ADC_HandleTypeDef* hadc) {
if (hadc == NULL || hadc->State != HAL_ADC_STATE_BUSY) {
return 0; // 返回错误值
}
return ADC_GetConversionValue(hadc->ADCx); // 获取 ADC 转换值
}

HAL_StatusTypeDef HAL_ADC_DeInit(HAL_ADC_HandleTypeDef* hadc) {
if (hadc == NULL) {
return HAL_ERROR;
}
ADC_DeInit(hadc->ADCx); // 复位 ADC 外设
if (hadc->ADCx == ADC1) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, DISABLE); // 关闭 ADC 时钟
}
// ... 其他 ADC 时钟关闭 ...
hadc->State = HAL_ADC_STATE_RESET;
return HAL_OK;
}

// ... DMA 相关函数实现 (可选) ...

// 辅助函数 (需要根据实际项目实现)
uint32_t GetTick(void) {
// ... 获取系统 Tick 的实现 (例如使用 SysTick 或 FreeRTOS Tick) ...
return 0; // 示例,需要实际实现
}

3.2 BSP 层代码示例 (BSP_ADC)

BSP_ADC.h (头文件)

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef __BSP_ADC_H__
#define __BSP_ADC_H__

#include "HAL_ADC.h"

extern HAL_ADC_HandleTypeDef hADC_Voltage; // 电压测量 ADC 句柄
extern HAL_ADC_HandleTypeDef hADC_Current; // 电流测量 ADC 句柄
extern HAL_ADC_HandleTypeDef hADC_Resistance; // 电阻测量 ADC 句柄

void BSP_ADC_Init(void);

#endif /* __BSP_ADC_H__ */

BSP_ADC.c (源文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "BSP_ADC.h"

HAL_ADC_HandleTypeDef hADC_Voltage;
HAL_ADC_HandleTypeDef hADC_Current;
HAL_ADC_HandleTypeDef hADC_Resistance;

void BSP_ADC_Init(void) {
HAL_ADC_ConfigTypeDef ADC_Config;

// 1. 初始化电压测量 ADC
ADC_Config.ADCx = ADC1;
ADC_Config.Channel = ADC_Channel_0; // 例如 PA0 作为电压输入
ADC_Config.SampleTime = ADC_SampleTime_239Cycles5;
HAL_ADC_Init(&hADC_Voltage, &ADC_Config);

// 2. 初始化电流测量 ADC
ADC_Config.ADCx = ADC1;
ADC_Config.Channel = ADC_Channel_1; // 例如 PA1 作为电流输入
ADC_Config.SampleTime = ADC_SampleTime_239Cycles5;
HAL_ADC_Init(&hADC_Current, &ADC_Config);

// 3. 初始化电阻测量 ADC
ADC_Config.ADCx = ADC1;
ADC_Config.Channel = ADC_Channel_2; // 例如 PA2 作为电阻输入
ADC_Config.SampleTime = ADC_SampleTime_239Cycles5;
HAL_ADC_Init(&hADC_Resistance, &ADC_Config);
}

3.3 核心服务层代码示例 (Measure_Voltage)

Measure.h (头文件,包含所有测量模块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef __MEASURE_H__
#define __MEASURE_H__

#include "BSP_ADC.h"
#include "stdint.h"
#include "stdbool.h"

// 电压量程定义 (示例)
typedef enum {
VOLTAGE_RANGE_2V,
VOLTAGE_RANGE_20V,
VOLTAGE_RANGE_200V,
VOLTAGE_RANGE_MAX
} VoltageRange_TypeDef;

extern VoltageRange_TypeDef CurrentVoltageRange; // 当前电压量程

float Measure_Voltage(void);
void SetVoltageRange(VoltageRange_TypeDef range);
VoltageRange_TypeDef GetVoltageRange(void);
bool IsVoltageOverRange(float voltage);

// ... 其他测量模块函数声明 (电流、电阻、通断性、RMS) ...

#endif /* __MEASURE_H__ */

Measure.c (源文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include "Measure.h"
#include <math.h> // 用于 RMS 计算

VoltageRange_TypeDef CurrentVoltageRange = VOLTAGE_RANGE_2V; // 默认量程

// 电压分压比 (需要根据实际硬件电路参数配置)
const float VoltageDividerRatio[] = {
1.0f / 1.0f, // 2V 量程, 无分压
1.0f / 10.0f, // 20V 量程, 10:1 分压
1.0f / 100.0f // 200V 量程, 100:1 分压
// ... 其他量程分压比 ...
};

// ADC 参考电压 (VREF) 假设为 3.3V
#define ADC_VREF 3.3f
// ADC 分辨率 12 位
#define ADC_RESOLUTION 4096.0f

float Measure_Voltage(void) {
HAL_ADC_Start(&hADC_Voltage);
HAL_ADC_PollForConversion(&hADC_Voltage, HAL_MAX_DELAY);
uint16_t adcValue = HAL_ADC_GetValue(&hADC_Voltage);
HAL_ADC_Stop(&hADC_Voltage);

float voltage = (float)adcValue / ADC_RESOLUTION * ADC_VREF / VoltageDividerRatio[CurrentVoltageRange];
return voltage;
}

void SetVoltageRange(VoltageRange_TypeDef range) {
if (range < VOLTAGE_RANGE_MAX) {
CurrentVoltageRange = range;
// ... 控制量程切换继电器或电子开关 (硬件控制代码) ...
}
}

VoltageRange_TypeDef GetVoltageRange(void) {
return CurrentVoltageRange;
}

bool IsVoltageOverRange(float voltage) {
float maxVoltage = 0.0f;
switch (CurrentVoltageRange) {
case VOLTAGE_RANGE_2V:
maxVoltage = 2.0f;
break;
case VOLTAGE_RANGE_20V:
maxVoltage = 20.0f;
break;
case VOLTAGE_RANGE_200V:
maxVoltage = 200.0f;
break;
default:
maxVoltage = 0.0f; // 默认无超量程
break;
}
return (voltage > maxVoltage);
}

// ... 其他测量模块函数实现 (电流、电阻、通断性、RMS) ...

3.4 应用层代码示例 (main.c, 简化主循环)

main.c (源文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include "stm32f10x.h"
#include "BSP_Init.h" // 假设 BSP 初始化函数放在 BSP_Init.h/c 中
#include "Measure.h"
#include "Display_UI.h" // 假设显示 UI 函数放在 Display_UI.h/c 中
#include "UI_Keypad.h" // 假设按键处理函数放在 UI_Keypad.h/c 中
#include "stdio.h" // 用于 sprintf

int main(void) {
BSP_Init(); // 初始化 BSP (包含时钟、外设、GPIO 等)
BSP_ADC_Init(); // 初始化 ADC

Display_Init(); // 初始化显示屏
Keypad_Init(); // 初始化按键

SetVoltageRange(VOLTAGE_RANGE_2V); // 初始量程

char voltageStr[20]; // 存储电压值字符串

while (1) {
Keypad_Process(); // 处理按键事件

float voltage = Measure_Voltage(); // 测量电压

if (IsVoltageOverRange(voltage)) {
sprintf(voltageStr, "Over Range"); // 超量程显示
} else {
sprintf(voltageStr, "%.4fV", voltage); // 格式化电压值
}

Display_Clear(); // 清屏
Display_WriteString(10, 20, "Voltage:", FONT_SMALL);
Display_WriteString(10, 40, voltageStr, FONT_MEDIUM);
Display_Update(); // 更新显示

// ... 其他测量模式的处理 (电流、电阻、通断性) ...

HAL_Delay(100); // 延时 (例如 100ms 采样一次)
}
}

// ... 其他应用层代码 ...

// 假设的 HAL_Delay 函数 (需要根据实际项目实现)
void HAL_Delay(uint32_t Delay) {
uint32_t tickstart = GetTick();
while ((GetTick() - tickstart) < Delay);
}

4. 测试与验证

为了确保系统的可靠性和准确性,需要进行全面的测试和验证。 测试阶段应涵盖以下方面:

  • 单元测试: 对每个模块 (HAL, BSP, 核心服务) 进行独立测试,验证其功能是否符合设计要求。 例如,测试 HAL_ADC 驱动是否能正确读取 ADC 值,测试 Measure_Voltage 函数是否能正确计算电压值。
  • 集成测试: 将各个模块组合起来进行测试,验证模块之间的接口和协作是否正确。 例如,测试测量模块与显示模块的集成,确保测量数据能正确显示在屏幕上。
  • 系统测试: 对整个万用表系统进行全面测试,验证其是否满足所有需求指标。 包括:
    • 精度测试: 使用标准电压源、电流源、电阻箱等进行精度校准和测试。
    • 量程切换测试: 测试自动和手动量程切换功能是否正常。
    • 超量程保护测试: 测试超量程保护指示功能是否正常。
    • 稳定性测试: 长时间运行测试,验证系统是否稳定可靠。
    • 用户界面测试: 测试用户界面的友好性和易用性。
  • 硬件测试: 使用专业的万用表和测试仪器,对硬件电路进行验证,确保硬件电路的正确性和可靠性。

测试方法:

  • 代码审查: 进行代码审查,检查代码质量、逻辑错误、潜在的 bug。
  • 静态分析: 使用静态代码分析工具,检测代码中的潜在缺陷和安全漏洞。
  • 动态测试: 通过运行程序,使用调试器和示波器等工具,观察程序的运行状态和信号波形。
  • 黑盒测试: 从用户角度出发,不了解内部实现,只测试系统的输入输出行为是否符合预期。
  • 白盒测试: 了解内部实现,针对代码结构和逻辑进行测试,确保代码覆盖率。

5. 维护与升级

为了方便后续的维护和升级,在系统设计时需要考虑以下因素:

  • 模块化设计: 采用分层架构和模块化设计,使得代码结构清晰,易于理解和修改。
  • 良好的代码风格: 遵循良好的编码规范,编写清晰、简洁、注释完善的代码,提高代码可读性和可维护性。
  • 版本控制: 使用版本控制系统 (例如 Git) 管理代码,方便代码的版本管理和回溯。
  • 预留升级接口: 在硬件和软件设计上预留一定的扩展性和升级空间,例如预留外部接口、软件升级接口等。
  • 软件升级方案: 考虑通过 USB、串口或 OTA (Over-The-Air) 等方式进行固件升级,方便用户更新软件版本。

可能的升级方向:

  • 增加测量功能: 例如频率测量、电容测量、温度测量等。
  • 提高测量精度和速度: 优化 ADC 采样参数、采用更高精度的 ADC 芯片、优化算法。
  • 增加数据存储和通信功能: 例如数据记录、蓝牙/WiFi 通信、上位机软件配合等。
  • 改进用户界面: 采用更高级的显示屏 (例如彩色 LCD 或 OLED)、优化 UI 交互逻辑、增加图形化显示功能。

总结

本文详细阐述了基于 STM32F103C8T6 单片机的数字万用表系统的软件架构设计和 C 代码实现方案。 采用分层架构 (HAL, BSP, 核心服务层, 应用层) 构建系统,提高了代码的可维护性和可扩展性。 给出了关键模块 (HAL_ADC, BSP_ADC, Measure_Voltage, main.c) 的 C 代码示例,演示了系统的基本框架和核心功能实现。 同时,也讨论了测试验证和维护升级等方面。

该设计方案充分考虑了嵌入式系统的特点,采用了成熟可靠的技术和方法,能够构建一个满足项目需求的数字万用表系统平台。 实际项目开发中,还需要根据具体需求和硬件平台进行更详细的设计和实现,并进行充分的测试和验证,才能最终交付一个高质量的嵌入式产品。

代码量说明: 虽然上述示例代码只有几百行,但要实现一个完整的、功能完善的数字万用表系统,包括所有的测量功能、量程切换、显示驱动、用户界面、各种错误处理和保护机制,以及完善的测试代码和文档,代码行数肯定会超过 3000 行。 尤其是在核心服务层和应用层,需要编写大量的代码来实现各种测量算法、UI 逻辑、状态机管理、数据处理等功能。 例如,显示驱动部分,如果采用复杂的图形库,代码量也会大幅增加。 此外,为了保证代码的质量和可维护性,需要编写大量的注释和文档,也会增加代码行数。 因此,一个功能完善的嵌入式数字万用表项目的代码量达到 3000 行以上是很正常的。

希望以上详细的架构设计和代码示例能够帮助您理解嵌入式数字万用表系统的开发过程。 在实际开发中,请根据具体需求和硬件平台进行调整和完善。

欢迎关注我的其它发布渠道