编程技术分享

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

0%

简介:基于CW32F030的电压电流表

我将为您详细阐述基于CW32F030微控制器的电压电流表项目的代码设计架构,并提供具体的C代码实现。这个项目的设计目标是构建一个可靠、高效、可扩展的嵌入式系统平台,用于精确测量电压和电流,并在显示屏上实时显示。
关注微信公众号,提前获取相关推文

项目概述:基于CW32F030的电压电流表

1. 需求分析

  • 功能需求:

    • 电压测量: 测量直流电压,范围例如 0-30V (可根据实际传感器和应用场景调整)。精度要求例如 +/- 0.1V 或更高。
    • 电流测量: 测量直流电流,范围例如 0-5A (可根据实际传感器和应用场景调整)。精度要求例如 +/- 0.01A 或更高。
    • 实时显示: 将测量的电压和电流值实时显示在屏幕上 (例如 LCD, OLED)。
    • 单位显示: 电压单位为 V,电流单位为 A。
    • 用户交互 (可选): 可以通过按键进行功能切换、参数设置、校准等 (本示例中先简化为基本显示功能,交互功能可在后续扩展)。
    • 过载保护 (可选): 在电流或电压超过安全阈值时发出警告或采取保护措施 (本示例中先不包含过载保护,可在后续扩展)。
  • 性能需求:

    • 测量精度: 满足指定的电压和电流精度要求。
    • 采样频率: 保证实时性,例如每秒至少采样 10 次以上。
    • 响应速度: 显示更新速度快,用户能及时看到电压电流变化。
    • 低功耗 (可选): 如果是电池供电,需要考虑功耗优化。
  • 可靠性需求:

    • 系统稳定性: 系统应长时间稳定运行,不易崩溃。
    • 数据准确性: 测量数据准确可靠。
    • 抗干扰能力: 系统应具备一定的抗电磁干扰能力。
  • 可扩展性需求:

    • 功能扩展: 方便后续增加新的功能,例如数据记录、通信接口、更高级的UI等。
    • 硬件扩展: 方便更换传感器、显示屏或其他硬件模块。

2. 系统架构设计

为了满足上述需求,特别是可靠性、高效性和可扩展性,我将采用分层架构模块化设计。这种架构将系统划分为多个独立的模块,每个模块负责特定的功能,模块之间通过清晰的接口进行通信。

系统架构图:

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
+---------------------+
| 应用层 (APP) | <- 用户界面逻辑, 测量算法, 数据处理
+---------------------+
|
| (服务接口)
V
+---------------------+
| 服务层 (Service) | <- 提供电压/电流测量服务, 显示服务, 按键服务等
+---------------------+
|
| (驱动接口)
V
+---------------------+
| 硬件抽象层 (HAL) | <- 抽象硬件细节, 提供统一的硬件访问接口 (ADC, GPIO, Display, Timer)
+---------------------+
|
| (硬件驱动)
V
+---------------------+
| 硬件驱动层 (Driver) | <- CW32F030 外设驱动 (ADC Driver, GPIO Driver, Display Driver, Timer Driver)
+---------------------+
|
| (硬件)
V
+---------------------+
| CW32F030 MCU | <- 核心微控制器
+---------------------+
|
| (外围硬件)
V
+---------------------+
| 传感器, 显示屏, 按键 | <- 电压电流传感器, 显示屏, 用户按键
+---------------------+

各层的功能职责:

  • 硬件驱动层 (Driver):

    • 直接操作 CW32F030 微控制器的硬件外设 (ADC, GPIO, SPI, I2C, Timer 等)。
    • 提供底层的硬件驱动函数,例如 ADC 初始化、ADC 读取、GPIO 输出控制、SPI 数据传输、定时器配置等。
    • 这一层代码与具体的 CW32F030 芯片硬件紧密相关。
  • 硬件抽象层 (HAL):

    • 在硬件驱动层之上构建一层抽象层。
    • 定义统一的硬件访问接口 (API),隐藏底层硬件的差异性。
    • 应用程序和服务层通过 HAL 提供的 API 来访问硬件,而无需关心具体的硬件驱动细节。
    • 这样做的好处是提高了代码的可移植性和可维护性,当更换硬件平台时,只需要修改 HAL 层和驱动层,应用程序和服务层代码可以保持不变。
    • HAL 层可以包含 ADC HAL, GPIO HAL, Display HAL, Timer HAL 等模块。
  • 服务层 (Service):

    • 在 HAL 层之上构建服务层。
    • 提供更高级的服务接口,供应用程序层调用。
    • 例如,电压/电流测量服务 (封装了 ADC 采样、数据处理、单位转换等),显示服务 (封装了显示屏驱动、显示内容格式化等),按键服务 (封装了按键扫描、按键事件处理等)。
    • 服务层将底层的硬件操作和业务逻辑进行封装,使应用程序层可以更专注于业务逻辑的实现。
    • 服务层可以包含 Measurement Service, Display Service, Button Service 等模块。
  • 应用层 (APP):

    • 系统的最高层,直接与用户交互。
    • 实现具体的应用程序逻辑,例如电压电流表的界面显示、数据处理、用户交互等。
    • 应用层调用服务层提供的接口来完成各种功能。
    • 应用程序层可以包含 UI Logic, Measurement Logic, Data Processing Logic 等模块。

模块化设计:

在每一层内部,也采用模块化设计,将功能进一步细分到更小的模块中。例如:

  • HAL 层: ADC HAL 模块、GPIO HAL 模块、Display HAL 模块、Timer HAL 模块 等。
  • 服务层: Measurement Service 模块 (电压测量子模块, 电流测量子模块)、Display Service 模块 (LCD/OLED 驱动子模块, 字符/数字显示子模块)、Button Service 模块 (按键扫描子模块, 按键事件处理子模块) 等。
  • APP 层: UI 模块 (界面布局子模块, 菜单管理子模块)、Measurement App 模块 (电压电流计算子模块, 数据滤波子模块)、Data Display 模块 (数据显示格式化子模块, 显示刷新子模块) 等。

3. 代码实现 (C语言)

下面我将提供一个简化的 C 代码实现,涵盖电压电流表的核心功能。我会尽可能详细地展开,包含注释、头文件、模块划分、函数实现等,并模拟实际项目开发中的代码结构。

3.1. 工程目录结构

为了组织代码,我们创建一个工程目录,例如 VoltageCurrentMeter_CW32F030,并在其中创建以下子目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
VoltageCurrentMeter_CW32F030/
├── Core/ # 核心代码 (HAL, Service, APP)
│ ├── HAL/ # 硬件抽象层
│ │ ├── inc/ # HAL 头文件
│ │ └── src/ # HAL 源文件
│ ├── Service/ # 服务层
│ │ ├── inc/ # Service 头文件
│ │ └── src/ # Service 源文件
│ └── APP/ # 应用层
│ ├── inc/ # APP 头文件
│ └── src/ # APP 源文件
├── Drivers/ # 硬件驱动层
│ ├── CW32F030_Driver/ # CW32F030 官方驱动库 (如果使用)
│ ├── inc/ # 驱动头文件
│ └── src/ # 驱动源文件
├── Lib/ # 第三方库 (例如 FreeRTOS, GUI 库等,本例暂不引入)
├── Inc/ # 项目全局头文件
├── Src/ # 主函数, 启动代码, 中断处理等
├── Startup/ # 启动文件, 链接脚本等
├── Doc/ # 项目文档
└── Tools/ # 开发工具, 脚本等

3.2. 代码实现 (逐步展开)

3.2.1. 全局头文件 (Inc/main.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
34
35
36
37
#ifndef __MAIN_H__
#define __MAIN_H__

#include <stdint.h>
#include <stdbool.h>

// 系统时钟频率 (根据实际配置修改)
#define SYS_CLK_FREQ_HZ 48000000UL

// ADC 分辨率 (例如 12 位)
#define ADC_RESOLUTION 12

// ADC 参考电压 (例如 3.3V)
#define ADC_REF_VOLTAGE_MV 3300

// 电压分压电阻值 (R1, R2 用于电压分压,计算电压比例)
#define VOLTAGE_R1_KOHM 10.0f
#define VOLTAGE_R2_KOHM 1.0f
#define VOLTAGE_DIVIDE_RATIO (VOLTAGE_R2_KOHM / (VOLTAGE_R1_KOHM + VOLTAGE_R2_KOHM))

// 电流采样电阻值 (例如 0.1 欧姆)
#define CURRENT_SENSE_RESISTOR_OHM 0.1f

// 电压电流单位
typedef enum {
UNIT_VOLTAGE_V,
UNIT_CURRENT_A,
UNIT_NONE
} Unit_TypeDef;

// 测量数据结构
typedef struct {
float voltage; // 电压值 (单位: V)
float current; // 电流值 (单位: A)
} MeasurementData_TypeDef;

#endif // __MAIN_H__

3.2.2. 硬件驱动层 (Drivers/CW32F030_Driver/)

这里假设您使用了 CW32F030 的官方驱动库 (如果官方有提供)。如果官方没有,或者您想自己编写驱动,需要参考 CW32F030 的数据手册,配置寄存器来控制外设。

  • ADC 驱动 (Drivers/src/adc_driver.c, Drivers/inc/adc_driver.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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// Drivers/inc/adc_driver.h
#ifndef __ADC_DRIVER_H__
#define __ADC_DRIVER_H__

#include "CW32F030.h"
#include <stdbool.h>

// ADC 初始化函数
bool ADC_Driver_Init(void);

// 读取 ADC 通道的值
uint16_t ADC_Driver_ReadChannel(uint32_t channel);

#endif // __ADC_DRIVER_H__

// Drivers/src/adc_driver.c
#include "adc_driver.h"

bool ADC_Driver_Init(void) {
// 1. 使能 ADC 时钟
RCC_EnableAPB1PeriphClk(RCC_APB1_PERIPH_ADC, true);

// 2. 配置 ADC 工作模式和参数 (参考 CW32F030 数据手册)
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Resolution = ADC_RESOLUTION_12B; // 12 位分辨率
ADC_InitStructure.ADC_PRESCARE = ADC_PRESCARE_DIV_4; // ADC 预分频 (根据系统时钟和 ADC 频率要求配置)
ADC_InitStructure.ADC_Mode = ADC_MODE_SINGLE_CONV; // 单次转换模式
ADC_InitStructure.ADC_DataAlign = ADC_DATA_ALIGN_RIGHT; // 数据右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_EXTERNAL_TRIG_NONE; // 软件触发
ADC_Init(&ADC_InitStructure);

// 3. 使能 ADC
ADC_Enable();

// 4. 等待 ADC 稳定 (可选,根据实际情况添加延时)
// Delay_ms(1); // 假设有 Delay_ms 函数

return true; // 初始化成功
}

uint16_t ADC_Driver_ReadChannel(uint32_t channel) {
// 1. 配置 ADC 通道
ADC_ConfigChannel(channel, ADC_RANK_CHANNEL_1, ADC_SAMPLETIME_240CYCLES); // 选择通道,采样时间

// 2. 启动 ADC 转换
ADC_SoftwareStartConvCmd(true);

// 3. 等待转换完成
while (!ADC_GetFlagStatus(ADC_FLAG_EOC));

// 4. 清除 EOC 标志 (可选,有些芯片会自动清除)
ADC_ClearFlag(ADC_FLAG_EOC);

// 5. 读取 ADC 数据
return ADC_GetConversionValue();
}
  • GPIO 驱动 (Drivers/src/gpio_driver.c, Drivers/inc/gpio_driver.h) (如果显示屏或按键需要 GPIO 控制,则需要 GPIO 驱动,这里先简化,假设显示屏是 SPI 或 I2C 接口)

  • SPI/I2C 驱动 (Drivers/src/spi_driver.c, Drivers/inc/spi_driver.h) 或 (Drivers/src/i2c_driver.c, Drivers/inc/i2c_driver.h) (如果显示屏是 SPI 或 I2C 接口,则需要相应的驱动)

  • Timer 驱动 (Drivers/src/timer_driver.c, Drivers/inc/timer_driver.h) (用于定时采样、定时显示刷新等)

3.2.3. 硬件抽象层 (Core/HAL/)

  • ADC HAL (Core/HAL/src/adc_hal.c, Core/HAL/inc/adc_hal.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
34
35
36
37
38
39
// Core/HAL/inc/adc_hal.h
#ifndef __ADC_HAL_H__
#define __ADC_HAL_H__

#include <stdint.h>
#include <stdbool.h>

// 定义 ADC 通道 (根据硬件连接修改)
typedef enum {
ADC_CHANNEL_VOLTAGE = ADC_CHANNEL_5, // 假设电压传感器连接到 ADC 通道 5
ADC_CHANNEL_CURRENT = ADC_CHANNEL_6 // 假设电流传感器连接到 ADC 通道 6
} ADC_Channel_TypeDef;

// ADC HAL 初始化函数
bool ADC_HAL_Init(void);

// 读取电压 ADC 值
uint16_t ADC_HAL_ReadVoltageChannel(void);

// 读取电流 ADC 值
uint16_t ADC_HAL_ReadCurrentChannel(void);

#endif // __ADC_HAL_H__

// Core/HAL/src/adc_hal.c
#include "adc_hal.h"
#include "adc_driver.h"

bool ADC_HAL_Init(void) {
return ADC_Driver_Init(); // 直接调用底层 ADC 驱动初始化
}

uint16_t ADC_HAL_ReadVoltageChannel(void) {
return ADC_Driver_ReadChannel(ADC_CHANNEL_VOLTAGE);
}

uint16_t ADC_HAL_ReadCurrentChannel(void) {
return ADC_Driver_ReadChannel(ADC_CHANNEL_CURRENT);
}
  • Display HAL (Core/HAL/src/display_hal.c, Core/HAL/inc/display_hal.h) (假设使用 SPI 接口的 OLED 显示屏,需要 SPI HAL 和 Display HAL)
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
// Core/HAL/inc/display_hal.h
#ifndef __DISPLAY_HAL_H__
#define __DISPLAY_HAL_H__

#include <stdint.h>
#include <stdbool.h>

// Display HAL 初始化函数
bool Display_HAL_Init(void);

// 清屏
void Display_HAL_ClearScreen(void);

// 显示字符串
void Display_HAL_WriteString(uint8_t x, uint8_t y, const char *str);

// 显示数字 (整数)
void Display_HAL_WriteInteger(uint8_t x, uint8_t y, int32_t num);

// 显示数字 (浮点数,需要根据实际情况实现浮点数显示)
void Display_HAL_WriteFloat(uint8_t x, uint8_t y, float num, uint8_t decimal_places);


#endif // __DISPLAY_HAL_H__

// Core/HAL/src/display_hal.c
#include "display_hal.h"
#include "spi_driver.h" // 假设使用 SPI 驱动
// #include "i2c_driver.h" // 如果使用 I2C 驱动

// ... (假设 OLED 驱动库已经存在或需要自己实现,这里省略 OLED 驱动的底层细节) ...
// ... (例如 OLED 初始化函数, OLED 写命令函数, OLED 写数据函数, OLED 显示字符函数, OLED 显示数字函数 等) ...

bool Display_HAL_Init(void) {
// 初始化 SPI 或 I2C 驱动 (如果需要)
// SPI_Driver_Init(); // 或 I2C_Driver_Init();

// 初始化 OLED 显示屏 (调用 OLED 驱动库的初始化函数)
// OLED_Init(); // 假设有 OLED_Init 函数

// 清屏 (可选)
Display_HAL_ClearScreen();

return true; // 初始化成功
}

void Display_HAL_ClearScreen(void) {
// 调用 OLED 驱动库的清屏函数
// OLED_Clear(); // 假设有 OLED_Clear 函数
}

void Display_HAL_WriteString(uint8_t x, uint8_t y, const char *str) {
// 调用 OLED 驱动库的字符串显示函数
// OLED_WriteString(x, y, str); // 假设有 OLED_WriteString 函数
}

void Display_HAL_WriteInteger(uint8_t x, uint8_t y, int32_t num) {
// 调用 OLED 驱动库的整数显示函数
// OLED_WriteInteger(x, y, num); // 假设有 OLED_WriteInteger 函数
}

void Display_HAL_WriteFloat(uint8_t x, uint8_t y, float num, uint8_t decimal_places) {
// 实现浮点数到字符串的转换 (可以使用 sprintf 或自定义函数)
char str_buffer[20]; // 足够存放浮点数字符串
sprintf(str_buffer, "%.*f", decimal_places, num); // 格式化浮点数
Display_HAL_WriteString(x, y, str_buffer); // 显示字符串
}
  • Timer HAL (Core/HAL/src/timer_hal.c, Core/HAL/inc/timer_hal.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
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
// Core/HAL/inc/timer_hal.h
#ifndef __TIMER_HAL_H__
#define __TIMER_HAL_H__

#include <stdint.h>
#include <stdbool.h>

// Timer HAL 初始化函数
bool Timer_HAL_Init(void);

// 设置定时器周期 (单位: 毫秒)
bool Timer_HAL_SetPeriodMs(uint32_t period_ms);

// 启动定时器
bool Timer_HAL_Start(void);

// 停止定时器
bool Timer_HAL_Stop(void);

// 获取定时器状态 (是否超时)
bool Timer_HAL_IsTimeout(void);

// 清除定时器超时标志
void Timer_HAL_ClearTimeoutFlag(void);

#endif // __TIMER_HAL_H__

// Core/HAL/src/timer_hal.c
#include "timer_hal.h"
#include "timer_driver.h" // 假设有 Timer 驱动

bool Timer_HAL_Init(void) {
// 初始化 Timer 驱动
// Timer_Driver_Init(); // 假设有 Timer_Driver_Init 函数
return true; // 初始化成功
}

bool Timer_HAL_SetPeriodMs(uint32_t period_ms) {
// 设置 Timer 周期 (调用 Timer 驱动的设置周期函数)
// Timer_Driver_SetPeriodMs(period_ms); // 假设有 Timer_Driver_SetPeriodMs 函数
return true;
}

bool Timer_HAL_Start(void) {
// 启动 Timer (调用 Timer 驱动的启动函数)
// Timer_Driver_Start(); // 假设有 Timer_Driver_Start 函数
return true;
}

bool Timer_HAL_Stop(void) {
// 停止 Timer (调用 Timer 驱动的停止函数)
// Timer_Driver_Stop(); // 假设有 Timer_Driver_Stop 函数
return true;
}

bool Timer_HAL_IsTimeout(void) {
// 检查 Timer 是否超时 (调用 Timer 驱动的检查超时函数)
// return Timer_Driver_IsTimeout(); // 假设有 Timer_Driver_IsTimeout 函数
return false; // 简化示例,始终返回 false,实际需要根据 Timer 驱动实现
}

void Timer_HAL_ClearTimeoutFlag(void) {
// 清除 Timer 超时标志 (调用 Timer 驱动的清除超时函数)
// Timer_Driver_ClearTimeoutFlag(); // 假设有 Timer_Driver_ClearTimeoutFlag 函数
}

3.2.4. 服务层 (Core/Service/)

  • Measurement Service (Core/Service/src/measurement_service.c, Core/Service/inc/measurement_service.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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Core/Service/inc/measurement_service.h
#ifndef __MEASUREMENT_SERVICE_H__
#define __MEASUREMENT_SERVICE_H__

#include "main.h"
#include <stdbool.h>

// Measurement Service 初始化函数
bool Measurement_Service_Init(void);

// 获取电压电流测量数据
MeasurementData_TypeDef Measurement_Service_GetData(void);

#endif // __MEASUREMENT_SERVICE_H__

// Core/Service/src/measurement_service.c
#include "measurement_service.h"
#include "adc_hal.h"

bool Measurement_Service_Init(void) {
if (!ADC_HAL_Init()) {
return false; // ADC HAL 初始化失败
}
return true; // 初始化成功
}

MeasurementData_TypeDef Measurement_Service_GetData(void) {
MeasurementData_TypeDef data;

// 1. 读取 ADC 值
uint16_t voltage_adc_value = ADC_HAL_ReadVoltageChannel();
uint16_t current_adc_value = ADC_HAL_ReadCurrentChannel();

// 2. 将 ADC 值转换为电压值 (mV)
float voltage_mv = (float)voltage_adc_value * ADC_REF_VOLTAGE_MV / ( (1 << ADC_RESOLUTION) - 1 );

// 3. 计算实际电压值 (考虑分压电阻)
data.voltage = voltage_mv / VOLTAGE_DIVIDE_RATIO / 1000.0f; // 转换为 V

// 4. 将 ADC 值转换为电流值 (mA)
float current_mv = (float)current_adc_value * ADC_REF_VOLTAGE_MV / ( (1 << ADC_RESOLUTION) - 1 );

// 5. 计算实际电流值 (考虑采样电阻和运放增益,这里假设增益为 1,实际需要根据电路设计调整)
data.current = current_mv / CURRENT_SENSE_RESISTOR_OHM / 1000.0f; // 转换为 A

return data;
}
  • Display Service (Core/Service/src/display_service.c, Core/Service/inc/display_service.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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Core/Service/inc/display_service.h
#ifndef __DISPLAY_SERVICE_H__
#define __DISPLAY_SERVICE_H__

#include <stdbool.h>

// Display Service 初始化函数
bool Display_Service_Init(void);

// 显示电压值
void Display_Service_ShowVoltage(float voltage);

// 显示电流值
void Display_Service_ShowCurrent(float current);

// 清屏
void Display_Service_Clear(void);

#endif // __DISPLAY_SERVICE_H__

// Core/Service/src/display_service.c
#include "display_service.h"
#include "display_hal.h"

bool Display_Service_Init(void) {
if (!Display_HAL_Init()) {
return false; // Display HAL 初始化失败
}
return true; // 初始化成功
}

void Display_Service_ShowVoltage(float voltage) {
Display_Service_Clear(); // 每次显示前清屏 (可以根据实际需求优化)
Display_HAL_WriteString(0, 0, "Voltage: ");
Display_HAL_WriteFloat(80, 0, voltage, 2); // 显示电压值,保留 2 位小数
Display_HAL_WriteString(120, 0, "V");
}

void Display_Service_ShowCurrent(float current) {
Display_HAL_WriteString(0, 16, "Current: "); // 假设 OLED 行高为 16
Display_HAL_WriteFloat(80, 16, current, 3); // 显示电流值,保留 3 位小数
Display_HAL_WriteString(120, 16, "A");
}

void Display_Service_Clear(void) {
Display_HAL_ClearScreen();
}

3.2.5. 应用层 (Core/APP/)

  • VoltageCurrentMeter App (Core/APP/src/voltage_current_meter_app.c, Core/APP/inc/voltage_current_meter_app.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
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
// Core/APP/inc/voltage_current_meter_app.h
#ifndef __VOLTAGE_CURRENT_METER_APP_H__
#define __VOLTAGE_CURRENT_METER_APP_H__

#include <stdbool.h>

// 电压电流表应用初始化函数
bool VoltageCurrentMeterApp_Init(void);

// 电压电流表应用主循环
void VoltageCurrentMeterApp_Run(void);

#endif // __VOLTAGE_CURRENT_METER_APP_H__

// Core/APP/src/voltage_current_meter_app.c
#include "voltage_current_meter_app.h"
#include "measurement_service.h"
#include "display_service.h"
#include "timer_hal.h"

#define MEASUREMENT_PERIOD_MS 100 // 测量周期 (100ms, 每秒采样 10 次)
#define DISPLAY_PERIOD_MS 200 // 显示刷新周期 (200ms, 每秒刷新 5 次)

bool VoltageCurrentMeterApp_Init(void) {
if (!Measurement_Service_Init()) {
return false; // Measurement Service 初始化失败
}
if (!Display_Service_Init()) {
return false; // Display Service 初始化失败
}
if (!Timer_HAL_Init()) {
return false; // Timer HAL 初始化失败
}
if (!Timer_HAL_SetPeriodMs(MEASUREMENT_PERIOD_MS)) {
return false; // 设置测量定时器周期失败
}
if (!Timer_HAL_Start()) {
return false; // 启动测量定时器失败
}

return true; // 应用初始化成功
}

void VoltageCurrentMeterApp_Run(void) {
MeasurementData_TypeDef measurement_data;
uint32_t last_display_time = 0; // 上次显示刷新时间 (毫秒)

while (1) {
// 1. 定时采样
if (Timer_HAL_IsTimeout()) {
Timer_HAL_ClearTimeoutFlag();

// 获取测量数据
measurement_data = Measurement_Service_GetData();

// 2. 定时显示 (每 DISPLAY_PERIOD_MS 毫秒刷新一次显示)
uint32_t current_time = // 获取当前时间 (可以使用系统滴答定时器或 FreeRTOS 的时间函数)
// 这里简化,假设有一个 GetTickMs() 函数返回系统启动后的毫秒数
// 例如: GetTickMs(); (需要自己实现 GetTickMs 函数)
0; // 简化示例,先设置为 0
if ((current_time - last_display_time) >= DISPLAY_PERIOD_MS) {
last_display_time = current_time;

// 显示电压和电流值
Display_Service_ShowVoltage(measurement_data.voltage);
Display_Service_ShowCurrent(measurement_data.current);
}
}

// 3. 其他任务 (例如按键扫描,数据处理,通信等,本例简化)
// ...
}
}

3.2.6. 主函数 (Src/main.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Src/main.c
#include "main.h"
#include "voltage_current_meter_app.h"

int main(void) {
// 1. 系统初始化 (时钟, 外设等)
System_Clock_Init(); // 假设有 System_Clock_Init 函数初始化系统时钟
// ... 其他系统初始化代码 ...

// 2. 应用初始化
if (!VoltageCurrentMeterApp_Init()) {
// 应用初始化失败,错误处理
while (1) {
// 错误指示,例如 LED 闪烁
}
}

// 3. 运行应用
VoltageCurrentMeterApp_Run();

return 0; // 理论上不会执行到这里
}

3.3. 代码框架总结

以上代码提供了一个基于分层架构和模块化设计的电压电流表项目的基本框架。

  • 分层架构: Driver, HAL, Service, APP 层级清晰,职责分明。
  • 模块化设计: 各个功能模块 (ADC HAL, Display HAL, Measurement Service, Display Service, VoltageCurrentMeterApp) 独立性强,易于维护和扩展。
  • 代码可读性: 代码包含注释,变量和函数命名具有意义,提高了代码可读性。
  • 代码可移植性: HAL 层的引入,使得代码具有一定的可移植性,更换硬件平台时,只需要修改 Driver 和 HAL 层代码。

4. 项目开发流程和实践验证

  • 需求分析: 明确项目功能、性能、可靠性、可扩展性等需求。
  • 系统设计: 根据需求进行系统架构设计、模块划分、接口定义等。
  • 硬件设计: 选择合适的 CW32F030 开发板、电压电流传感器、显示屏等硬件,设计电路原理图和 PCB (如果需要)。
  • 软件开发:
    • 底层驱动开发 (Driver Layer): 编写 CW32F030 外设驱动代码 (ADC, GPIO, SPI, I2C, Timer 等)。可以使用官方提供的 SDK 或自行编写。
    • 硬件抽象层开发 (HAL Layer): 封装底层驱动,提供统一的硬件访问接口。
    • 服务层开发 (Service Layer): 实现业务逻辑服务,例如测量服务、显示服务等。
    • 应用层开发 (APP Layer): 编写应用程序代码,实现用户界面和应用逻辑。
  • 集成测试: 将硬件和软件进行集成,进行功能测试、性能测试、可靠性测试等。
  • 系统验证: 在实际应用场景下进行系统验证,确保系统满足所有需求。
  • 维护升级: 对系统进行维护和升级,修复 Bug,增加新功能。

实践验证的关键技术和方法:

  • ADC 精度校准: 为了提高电压电流测量精度,需要进行 ADC 校准。可以使用标准电压源和电流源进行校准,记录校准数据,并在软件中进行补偿。
  • 数据滤波: 为了消除噪声干扰,提高测量数据的稳定性,可以使用数字滤波器对 ADC 采样数据进行滤波,例如移动平均滤波、中值滤波、卡尔曼滤波等。
  • 显示驱动优化: 为了提高显示刷新速度和效率,需要优化显示驱动代码,例如使用 DMA 传输数据、减少不必要的刷新操作等。
  • 定时器精确控制: 为了保证采样频率和显示刷新频率的准确性,需要精确配置和使用定时器。
  • 低功耗设计 (可选): 如果是电池供电,需要进行低功耗设计,例如使用低功耗模式、优化代码功耗、间歇性工作等。
  • 代码版本控制: 使用 Git 等版本控制工具管理代码,方便代码管理和团队协作。
  • 单元测试: 对各个模块进行单元测试,确保模块功能的正确性。
  • 集成测试和系统测试: 进行充分的集成测试和系统测试,发现和修复 Bug,确保系统质量。

5. 代码扩展方向 (后续版本可增加的功能)

  • 用户交互: 添加按键输入,实现菜单功能,例如切换显示单位、设置报警阈值、启动/停止数据记录等。
  • 数据存储: 将测量数据存储到外部存储器 (例如 SD 卡, Flash),实现数据记录功能。
  • 通信接口: 添加通信接口 (例如 UART, USB, Bluetooth, WiFi),将测量数据上传到上位机或云平台。
  • 报警功能: 设置电压和电流报警阈值,当测量值超过阈值时,发出声光报警。
  • 图形显示: 在显示屏上绘制电压电流波形图或趋势图。
  • 自校准功能: 实现系统自校准功能,定期或手动进行 ADC 校准。
  • 远程监控和控制: 通过网络或无线通信,实现远程监控和控制电压电流表。
  • 更高级的 UI: 使用 GUI 库 (例如 emWin, LittlevGL) 创建更美观、更易用的用户界面。
  • RTOS 引入: 对于更复杂的系统,可以引入 RTOS (例如 FreeRTOS, RT-Thread),提高系统的实时性和可管理性。

总结

这个基于 CW32F030 的电压电流表项目,采用分层架构和模块化设计,旨在构建一个可靠、高效、可扩展的嵌入式系统平台。提供的 C 代码示例虽然简化,但展示了清晰的代码结构和模块划分思路。在实际项目开发中,需要根据具体需求和硬件平台,进一步完善代码细节,进行充分的测试和验证,才能最终实现一个高质量的嵌入式产品。

希望这份详细的架构设计和代码示例能够帮助您理解嵌入式系统开发流程和代码组织方式。如果您有任何疑问或需要更深入的讨论,请随时提出。

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