编程技术分享

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

0%

简介:数控可调型直流稳压电源**

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

这个项目旨在设计并实现一个数控可调型直流稳压电源。用户可以通过按键或者其他输入方式设定输出电压和电流值,系统能够精确地控制输出,并实时显示当前的电压、电流等状态信息。此外,为了保证系统的安全性和可靠性,还需要具备过压、过流、过温等保护功能。

一、 需求分析

在项目初期,我们需要进行详细的需求分析,明确系统的各项功能和性能指标。这包括:

  1. 功能需求:

    • 电压调节: 用户能够设定输出电压值,并能够在一定范围内(例如 0-30V)精确可调。
    • 电流限制: 用户能够设定输出电流上限值,防止过流损坏负载。
    • 电压/电流显示: 实时显示当前的输出电压和电流值。
    • 保护功能:
      • 过压保护 (OVP): 当输出电压超过设定值时,自动切断输出,防止损坏负载。
      • 过流保护 (OCP): 当输出电流超过设定值时,自动限制电流或切断输出,保护电源和负载。
      • 过温保护 (OTP): 当系统内部温度过高时,自动降低输出功率或切断输出,保护系统自身。
      • 短路保护 (SCP): 当输出端发生短路时,快速切断输出。
    • 用户界面: 通过按键和数码管显示器进行人机交互,设定参数和显示状态。
    • 参数存储: 能够存储用户设定的常用电压和电流值,方便快速调用。
    • 精度要求: 输出电压和电流的精度需要满足一定的指标,例如电压精度 ±10mV,电流精度 ±10mA。
    • 响应速度: 电压和电流的调节响应速度需要足够快,满足动态负载的需求。
    • 可扩展性: 系统设计应考虑未来的功能扩展,例如增加通信接口、更高级的控制算法等。
  2. 非功能需求:

    • 可靠性: 系统需要稳定可靠地运行,保证长时间工作的稳定性。
    • 高效性: 系统需要具有较高的能量转换效率,减少能量损耗和发热。
    • 实时性: 系统需要实时响应用户操作和外部事件,保证控制的实时性。
    • 资源占用: 嵌入式系统的资源有限,代码和数据需要尽量精简,减少内存和 Flash 占用。
    • 可维护性: 代码结构清晰,模块化设计,方便后续的维护和升级。
    • 易用性: 用户界面友好,操作简单直观。
    • 成本: 在满足功能和性能的前提下,尽量降低硬件和软件的成本。

二、 代码设计架构

为了实现上述需求,并构建一个可靠、高效、可扩展的系统平台,我推荐采用分层架构,并结合模块化设计事件驱动机制。这种架构能够有效地组织代码,降低复杂度,提高可维护性和可扩展性。

2.1 分层架构

我们将系统软件分为以下几个层次:

  • 硬件抽象层 (HAL - Hardware Abstraction Layer): 最底层,直接与硬件交互。HAL 层封装了底层的硬件操作,向上层提供统一的硬件接口。这样可以屏蔽硬件差异,使得上层代码可以在不同的硬件平台上移植。HAL 层主要包括:

    • GPIO 驱动: 控制按键输入和数码管显示。
    • ADC 驱动: 读取电压和电流传感器的数据。
    • PWM 驱动: 控制 DC-DC 转换器的 PWM 输出,调节输出电压。
    • 定时器驱动: 提供定时功能,用于采样、控制和保护功能。
    • EEPROM 驱动 (可选): 用于存储用户配置参数。
    • UART 驱动 (可选): 用于调试和未来扩展通信功能。
  • 驱动层 (Driver Layer): 在 HAL 层之上,提供更高级的硬件驱动接口。驱动层将 HAL 层提供的基本硬件操作进行封装,提供更符合应用需求的驱动函数。例如:

    • 按键驱动: 检测按键按下、释放、长按等事件。
    • 数码管显示驱动: 控制数码管显示数字和字符。
    • ADC 采样驱动: 启动 ADC 采样,读取并处理 ADC 数据,转换为电压和电流值。
    • PWM 控制驱动: 设置 PWM 占空比,控制输出电压。
    • 保护模块驱动: 实现过压、过流、过温、短路等保护功能。
  • 服务层 (Service Layer): 在驱动层之上,提供核心的业务逻辑服务。服务层负责实现系统的主要功能,例如电压/电流控制、参数设定、显示管理、保护管理等。服务层不直接操作硬件,而是通过调用驱动层提供的接口来实现功能。服务层主要包括:

    • 电源控制服务: 实现电压和电流的 PID 控制算法,调节 PWM 输出,稳定输出电压和电流。
    • 参数配置服务: 处理用户参数设定,存储和加载参数。
    • 显示管理服务: 管理数码管显示内容,更新电压、电流等显示信息。
    • 保护管理服务: 监控电压、电流、温度等参数,检测异常情况,触发保护动作。
  • 应用层 (Application Layer): 最上层,负责用户交互和系统调度。应用层接收用户输入,调用服务层提供的服务,并将结果显示给用户。应用层主要包括:

    • 用户界面管理: 处理按键输入,响应用户操作,更新显示界面。
    • 系统初始化: 初始化各个模块,启动系统服务。
    • 主循环: 循环处理用户输入、执行控制算法、更新显示等任务。

2.2 模块化设计

在每个层次内部,我们都采用模块化设计,将功能分解为独立的模块。每个模块负责特定的功能,模块之间通过清晰的接口进行通信。模块化设计的好处包括:

  • 高内聚低耦合: 模块内部功能高度相关,模块之间依赖性低,易于维护和修改。
  • 代码复用: 模块可以被多个项目复用,提高开发效率。
  • 并行开发: 不同的模块可以由不同的开发人员并行开发,加快开发速度。
  • 易于测试: 可以对每个模块进行单元测试,提高代码质量。

2.3 事件驱动机制

为了提高系统的实时性和响应性,我们采用事件驱动机制。系统不再是简单的轮询方式,而是等待事件的发生,然后根据事件类型进行处理。事件可以是:

  • 按键事件: 按键按下、释放、长按等。
  • 定时器事件: 定时器到达预设时间。
  • ADC 采样完成事件: ADC 转换完成,可以读取数据。
  • 保护事件: 过压、过流、过温、短路等保护条件触发。

事件驱动机制可以有效地减少 CPU 的空转,提高系统的效率和实时性。在我们的系统中,可以使用一个事件队列来管理待处理的事件,主循环从事件队列中取出事件,并调用相应的事件处理函数进行处理。

三、 具体C代码实现

下面我将给出每个层次和模块的具体C代码实现,并进行详细的注释和解释。由于代码量较大,这里只给出核心模块的代码示例,完整代码需要根据具体的硬件平台和需求进行完善。

3.1 硬件抽象层 (HAL)

  • hal_gpio.h: GPIO 驱动头文件
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
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

typedef enum {
GPIO_PIN_0,
GPIO_PIN_1,
GPIO_PIN_2,
// ... more pins
GPIO_PIN_MAX
} GPIO_PinTypeDef;

typedef enum {
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT
} GPIO_ModeTypeDef;

typedef enum {
GPIO_STATE_RESET,
GPIO_STATE_SET
} GPIO_StateTypeDef;

// 初始化 GPIO 引脚
void HAL_GPIO_Init(GPIO_PinTypeDef pin, GPIO_ModeTypeDef mode);

// 设置 GPIO 输出状态
void HAL_GPIO_WritePin(GPIO_PinTypeDef pin, GPIO_StateTypeDef state);

// 读取 GPIO 输入状态
GPIO_StateTypeDef HAL_GPIO_ReadPin(GPIO_PinTypeDef pin);

#endif // HAL_GPIO_H
  • hal_gpio.c: GPIO 驱动源文件 (示例,需要根据具体MCU平台实现)
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
#include "hal_gpio.h"

void HAL_GPIO_Init(GPIO_PinTypeDef pin, GPIO_ModeTypeDef mode) {
// 根据 pin 和 mode 配置 GPIO 寄存器
// 例如:
// if (pin == GPIO_PIN_0) {
// if (mode == GPIO_MODE_OUTPUT) {
// // 设置 GPIO_PIN_0 为输出模式
// } else {
// // 设置 GPIO_PIN_0 为输入模式
// }
// }
// ... 其他引脚配置
}

void HAL_GPIO_WritePin(GPIO_PinTypeDef pin, GPIO_StateTypeDef state) {
// 根据 pin 和 state 设置 GPIO 输出状态
// 例如:
// if (pin == GPIO_PIN_0) {
// if (state == GPIO_STATE_SET) {
// // 设置 GPIO_PIN_0 输出高电平
// } else {
// // 设置 GPIO_PIN_0 输出低电平
// }
// }
// ... 其他引脚配置
}

GPIO_StateTypeDef HAL_GPIO_ReadPin(GPIO_PinTypeDef pin) {
// 读取 GPIO 输入状态
// 例如:
// if (pin == GPIO_PIN_0) {
// // 读取 GPIO_PIN_0 的输入电平
// // 并返回 GPIO_STATE_SET 或 GPIO_STATE_RESET
// }
// ... 其他引脚配置
return GPIO_STATE_RESET; // 默认返回 RESET
}
  • hal_adc.h: ADC 驱动头文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef HAL_ADC_H
#define HAL_ADC_H

typedef enum {
ADC_CHANNEL_VOLTAGE,
ADC_CHANNEL_CURRENT,
ADC_CHANNEL_TEMP,
ADC_CHANNEL_MAX
} ADC_ChannelTypeDef;

// 初始化 ADC
void HAL_ADC_Init(void);

// 启动 ADC 转换,选择通道
void HAL_ADC_StartConversion(ADC_ChannelTypeDef channel);

// 获取 ADC 转换结果
uint16_t HAL_ADC_GetValue(ADC_ChannelTypeDef channel);

#endif // HAL_ADC_H
  • hal_adc.c: ADC 驱动源文件 (示例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "hal_adc.h"

void HAL_ADC_Init(void) {
// 初始化 ADC 模块,配置时钟、分辨率等参数
// ... 具体 ADC 初始化代码
}

void HAL_ADC_StartConversion(ADC_ChannelTypeDef channel) {
// 选择 ADC 通道,启动 ADC 转换
// ... 具体 ADC 启动转换代码,选择对应的 ADC 通道
}

uint16_t HAL_ADC_GetValue(ADC_ChannelTypeDef channel) {
// 获取 ADC 转换结果,并返回
// ... 具体读取 ADC 数据寄存器的代码
return 0; // 示例,实际应读取 ADC 值
}
  • hal_pwm.h: PWM 驱动头文件
1
2
3
4
5
6
7
8
9
10
#ifndef HAL_PWM_H
#define HAL_PWM_H

// 初始化 PWM 模块
void HAL_PWM_Init(void);

// 设置 PWM 占空比 (0-100%)
void HAL_PWM_SetDutyCycle(uint8_t dutyCycle);

#endif // HAL_PWM_H
  • hal_pwm.c: PWM 驱动源文件 (示例)
1
2
3
4
5
6
7
8
9
10
11
12
#include "hal_pwm.h"

void HAL_PWM_Init(void) {
// 初始化 PWM 模块,配置时钟、频率等参数
// ... 具体 PWM 初始化代码
}

void HAL_PWM_SetDutyCycle(uint8_t dutyCycle) {
// 设置 PWM 占空比
// ... 具体设置 PWM 占空比寄存器的代码
// 注意要将 dutyCycle 转换为 PWM 寄存器可接受的值
}

3.2 驱动层 (Driver Layer)

  • drivers_keypad.h: 按键驱动头文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef DRIVERS_KEYPAD_H
#define DRIVERS_KEYPAD_H

typedef enum {
KEY_NONE,
KEY_UP,
KEY_DOWN,
KEY_ENTER,
KEY_CANCEL,
KEY_MAX
} KeyCodeTypeDef;

// 初始化按键驱动
void Keypad_Init(void);

// 获取按键事件
KeyCodeTypeDef Keypad_GetKey(void);

#endif // DRIVERS_KEYPAD_H
  • drivers_keypad.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
#include "drivers_keypad.h"
#include "hal_gpio.h" // 假设按键连接到 GPIO

// 定义按键连接的 GPIO 引脚 (需要根据实际硬件连接修改)
#define KEY_UP_PIN GPIO_PIN_0
#define KEY_DOWN_PIN GPIO_PIN_1
#define KEY_ENTER_PIN GPIO_PIN_2
#define KEY_CANCEL_PIN GPIO_PIN_3

void Keypad_Init(void) {
// 初始化按键相关的 GPIO 引脚为输入模式
HAL_GPIO_Init(KEY_UP_PIN, GPIO_MODE_INPUT);
HAL_GPIO_Init(KEY_DOWN_PIN, GPIO_MODE_INPUT);
HAL_GPIO_Init(KEY_ENTER_PIN, GPIO_MODE_INPUT);
HAL_GPIO_Init(KEY_CANCEL_PIN, GPIO_MODE_INPUT);
}

KeyCodeTypeDef Keypad_GetKey(void) {
if (HAL_GPIO_ReadPin(KEY_UP_PIN) == GPIO_STATE_RESET) { // 按键按下 (假设低电平有效)
return KEY_UP;
} else if (HAL_GPIO_ReadPin(KEY_DOWN_PIN) == GPIO_STATE_RESET) {
return KEY_DOWN;
} else if (HAL_GPIO_ReadPin(KEY_ENTER_PIN) == GPIO_STATE_RESET) {
return KEY_ENTER;
} else if (HAL_GPIO_ReadPin(KEY_CANCEL_PIN) == GPIO_STATE_RESET) {
return KEY_CANCEL;
} else {
return KEY_NONE; // 没有按键按下
}
}
  • drivers_display.h: 数码管显示驱动头文件
1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef DRIVERS_DISPLAY_H
#define DRIVERS_DISPLAY_H

// 初始化数码管显示驱动
void Display_Init(void);

// 显示数字 (0-9999)
void Display_ShowNumber(uint16_t number);

// 显示字符串 (例如 "VOLT", "CURR")
void Display_ShowString(const char *str);

#endif // DRIVERS_DISPLAY_H
  • drivers_display.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 "drivers_display.h"
#include "hal_gpio.h" // 假设数码管段选和位选连接到 GPIO

// 定义数码管段选和位选连接的 GPIO 引脚 (需要根据实际硬件连接修改)
// ... 定义段选和位选引脚宏

// 数码管段码表 (共阴极)
const uint8_t segmentCodes[] = {
0x3F, // 0
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F // 9
};

void Display_Init(void) {
// 初始化数码管相关的 GPIO 引脚为输出模式
// ... 初始化段选和位选引脚
}

void Display_ShowNumber(uint16_t number) {
// 将数字分解为各位,并控制数码管显示
uint8_t digits[4];
digits[0] = number % 10;
digits[1] = (number / 10) % 10;
digits[2] = (number / 100) % 10;
digits[3] = (number / 1000) % 10;

// 循环控制每个数码管显示对应的数字
for (int i = 0; i < 4; i++) {
// 选择对应的数码管位 (例如通过位选引脚)
// ... 位选代码

// 设置段码 (例如通过段选引脚)
// ... 段选代码,根据 segmentCodes[digits[i]] 设置段码
}
}

void Display_ShowString(const char *str) {
// ... 显示字符串的代码,例如将字符串转换为对应的数码管显示
// (可以根据需求实现简单的字符显示,或者只显示预定义的字符串)
}
  • drivers_adc_sample.h: ADC 采样驱动头文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef DRIVERS_ADC_SAMPLE_H
#define DRIVERS_ADC_SAMPLE_H

typedef struct {
float voltage; // 电压值 (单位 V)
float current; // 电流值 (单位 A)
float temperature; // 温度值 (单位 °C)
} ADCSampleTypeDef;

// 初始化 ADC 采样驱动
void ADCSample_Init(void);

// 获取 ADC 采样数据
ADCSampleTypeDef ADCSample_GetData(void);

#endif // DRIVERS_ADC_SAMPLE_H
  • drivers_adc_sample.c: ADC 采样驱动源文件 (示例)
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
#include "drivers_adc_sample.h"
#include "hal_adc.h" // 引用 HAL ADC 驱动

// 定义 ADC 通道和转换系数 (需要根据实际硬件电路和传感器参数校准)
#define VOLTAGE_ADC_CHANNEL ADC_CHANNEL_VOLTAGE
#define CURRENT_ADC_CHANNEL ADC_CHANNEL_CURRENT
#define TEMP_ADC_CHANNEL ADC_CHANNEL_TEMP

#define VOLTAGE_SCALE_FACTOR 0.01 // 假设 ADC 值到电压的转换系数
#define CURRENT_SCALE_FACTOR 0.001 // 假设 ADC 值到电流的转换系数
#define TEMP_SCALE_FACTOR 0.1 // 假设 ADC 值到温度的转换系数

void ADCSample_Init(void) {
HAL_ADC_Init(); // 初始化 HAL ADC 驱动
}

ADCSampleTypeDef ADCSample_GetData(void) {
ADCSampleTypeDef sampleData;

// 采样电压
HAL_ADC_StartConversion(VOLTAGE_ADC_CHANNEL);
uint16_t voltageAdcValue = HAL_ADC_GetValue(VOLTAGE_ADC_CHANNEL);
sampleData.voltage = (float)voltageAdcValue * VOLTAGE_SCALE_FACTOR;

// 采样电流
HAL_ADC_StartConversion(CURRENT_ADC_CHANNEL);
uint16_t currentAdcValue = HAL_ADC_GetValue(CURRENT_ADC_CHANNEL);
sampleData.current = (float)currentAdcValue * CURRENT_SCALE_FACTOR;

// 采样温度 (可选)
HAL_ADC_StartConversion(TEMP_ADC_CHANNEL);
uint16_t tempAdcValue = HAL_ADC_GetValue(TEMP_ADC_CHANNEL);
sampleData.temperature = (float)tempAdcValue * TEMP_SCALE_FACTOR;

return sampleData;
}
  • drivers_pwm_control.h: PWM 控制驱动头文件
1
2
3
4
5
6
7
8
9
10
#ifndef DRIVERS_PWM_CONTROL_H
#define DRIVERS_PWM_CONTROL_H

// 初始化 PWM 控制驱动
void PWMControl_Init(void);

// 设置输出电压 (单位 V)
void PWMControl_SetVoltage(float voltage);

#endif // DRIVERS_PWM_CONTROL_H
  • drivers_pwm_control.c: PWM 控制驱动源文件 (示例,开环控制,实际应使用 PID 闭环控制)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "drivers_pwm_control.h"
#include "hal_pwm.h" // 引用 HAL PWM 驱动

// 定义电压到 PWM 占空比的转换系数 (需要根据实际硬件电路和 DC-DC 转换器特性校准)
#define VOLTAGE_TO_DUTY_FACTOR 3.0 // 假设每伏特对应 3% 的占空比

void PWMControl_Init(void) {
HAL_PWM_Init(); // 初始化 HAL PWM 驱动
}

void PWMControl_SetVoltage(float voltage) {
// 将目标电压转换为 PWM 占空比
uint8_t dutyCycle = (uint8_t)(voltage * VOLTAGE_TO_DUTY_FACTOR);

// 限制占空比在 0-100% 范围内
if (dutyCycle > 100) {
dutyCycle = 100;
} else if (dutyCycle < 0) {
dutyCycle = 0;
}

HAL_PWM_SetDutyCycle(dutyCycle); // 设置 PWM 占空比
}
  • drivers_protection.h: 保护模块驱动头文件
1
2
3
4
5
6
7
8
9
10
#ifndef DRIVERS_PROTECTION_H
#define DRIVERS_PROTECTION_H

// 初始化保护模块
void Protection_Init(void);

// 检查并处理保护事件
void Protection_Check(ADCSampleTypeDef sampleData);

#endif // DRIVERS_PROTECTION_H
  • drivers_protection.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
#include "drivers_protection.h"
#include "drivers_pwm_control.h" // 用于控制 PWM 输出
#include "drivers_display.h" // 用于显示错误信息

// 定义保护阈值 (需要根据实际需求设定)
#define OVP_THRESHOLD_VOLTAGE 35.0 // 过压保护阈值 (V)
#define OCP_THRESHOLD_CURRENT 5.0 // 过流保护阈值 (A)
#define OTP_THRESHOLD_TEMP 80.0 // 过温保护阈值 (°C)

void Protection_Init(void) {
// 初始化保护模块,例如初始化保护状态标志等
}

void Protection_Check(ADCSampleTypeDef sampleData) {
// 过压保护
if (sampleData.voltage > OVP_THRESHOLD_VOLTAGE) {
// 触发过压保护动作,例如关闭 PWM 输出,显示错误信息
PWMControl_SetVoltage(0.0); // 关闭 PWM 输出
Display_ShowString("OVP"); // 显示过压错误
// ... 可以添加其他保护动作,例如报警等
return; // 触发保护后直接返回,不再进行其他保护检查
}

// 过流保护
if (sampleData.current > OCP_THRESHOLD_CURRENT) {
// 触发过流保护动作
PWMControl_SetVoltage(0.0); // 关闭 PWM 输出
Display_ShowString("OCP"); // 显示过流错误
// ...
return;
}

// 过温保护 (如果使用了温度传感器)
if (sampleData.temperature > OTP_THRESHOLD_TEMP) {
// 触发过温保护动作
PWMControl_SetVoltage(0.0); // 关闭 PWM 输出
Display_ShowString("OTP"); // 显示过温错误
// ...
return;
}

// 正常状态,可以清除之前的错误显示 (如果需要)
// Display_ShowString(""); // 清空显示
}

3.3 服务层 (Service Layer)

  • services_power_control.h: 电源控制服务头文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef SERVICES_POWER_CONTROL_H
#define SERVICES_POWER_CONTROL_H

// 初始化电源控制服务
void PowerControl_Init(void);

// 设置目标输出电压 (单位 V)
void PowerControl_SetTargetVoltage(float voltage);

// 设置目标输出电流限制 (单位 A)
void PowerControl_SetTargetCurrent(float current);

// 获取当前的输出电压 (单位 V)
float PowerControl_GetCurrentVoltage(void);

// 获取当前的输出电流 (单位 A)
float PowerControl_GetCurrentCurrent(void);

#endif // SERVICES_POWER_CONTROL_H
  • services_power_control.c: 电源控制服务源文件 (示例,PID 控制算法,需要根据实际系统参数进行 PID 参数整定)
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
#include "services_power_control.h"
#include "drivers_adc_sample.h"
#include "drivers_pwm_control.h"

// PID 控制参数 (需要根据实际系统特性整定)
#define KP 1.0
#define KI 0.1
#define KD 0.01

static float targetVoltage = 0.0; // 目标电压
static float targetCurrent = 0.0; // 目标电流限制
static float integralError = 0.0; // 积分误差
static float previousError = 0.0; // 上次误差

void PowerControl_Init(void) {
// 初始化 PID 控制器参数
integralError = 0.0;
previousError = 0.0;
}

void PowerControl_SetTargetVoltage(float voltage) {
targetVoltage = voltage;
}

void PowerControl_SetTargetCurrent(float current) {
targetCurrent = current;
}

float PowerControl_GetCurrentVoltage(void) {
ADCSampleTypeDef sampleData = ADCSample_GetData();
return sampleData.voltage;
}

float PowerControl_GetCurrentCurrent(void) {
ADCSampleTypeDef sampleData = ADCSample_GetData();
return sampleData.current;
}

void PowerControl_ControlLoop(void) {
ADCSampleTypeDef sampleData = ADCSample_GetData();
float currentVoltage = sampleData.voltage;

// 计算电压误差
float error = targetVoltage - currentVoltage;

// 积分项
integralError += error;

// 微分项
float derivativeError = error - previousError;

// PID 控制输出
float output = KP * error + KI * integralError + KD * derivativeError;

// 限制输出范围 (转换为 PWM 占空比的范围)
// ... 根据实际 PWM 占空比范围进行限制

// 设置 PWM 占空比
PWMControl_SetVoltage(output);

// 更新上次误差
previousError = error;
}
  • services_parameter_config.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 SERVICES_PARAMETER_CONFIG_H
#define SERVICES_PARAMETER_CONFIG_H

// 初始化参数配置服务
void ParameterConfig_Init(void);

// 获取设定的电压值
float ParameterConfig_GetVoltageSetting(void);

// 获取设定的电流限制值
float ParameterConfig_GetCurrentLimitSetting(void);

// 设置电压值
void ParameterConfig_SetVoltageSetting(float voltage);

// 设置电流限制值
void ParameterConfig_SetCurrentLimitSetting(float current);

// 保存参数到 EEPROM (可选)
void ParameterConfig_SaveParameters(void);

// 加载参数从 EEPROM (可选)
void ParameterConfig_LoadParameters(void);

#endif // SERVICES_PARAMETER_CONFIG_H
  • services_parameter_config.c: 参数配置服务源文件 (示例,参数存储在静态变量中,实际可以使用 EEPROM 或 Flash 存储)
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
#include "services_parameter_config.h"

static float voltageSetting = 12.0; // 默认电压设置
static float currentLimitSetting = 2.0; // 默认电流限制设置

void ParameterConfig_Init(void) {
// 初始化参数配置服务,例如加载默认参数或从 EEPROM 加载
// ...
}

float ParameterConfig_GetVoltageSetting(void) {
return voltageSetting;
}

float ParameterConfig_GetCurrentLimitSetting(void) {
return currentLimitSetting;
}

void ParameterConfig_SetVoltageSetting(float voltage) {
voltageSetting = voltage;
}

void ParameterConfig_SetCurrentLimitSetting(float current) {
currentLimitSetting = current;
}

void ParameterConfig_SaveParameters(void) {
// 保存参数到 EEPROM 或 Flash
// ...
}

void ParameterConfig_LoadParameters(void) {
// 从 EEPROM 或 Flash 加载参数
// ...
}
  • services_display_management.h: 显示管理服务头文件
1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef SERVICES_DISPLAY_MANAGEMENT_H
#define SERVICES_DISPLAY_MANAGEMENT_H

// 初始化显示管理服务
void DisplayManagement_Init(void);

// 更新显示内容 (电压、电流)
void DisplayManagement_UpdateDisplay(float voltage, float current);

// 显示错误信息 (例如 "OVP", "OCP")
void DisplayManagement_ShowError(const char *errorMsg);

#endif // SERVICES_DISPLAY_MANAGEMENT_H
  • services_display_management.c: 显示管理服务源文件 (示例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "services_display_management.h"
#include "drivers_display.h" // 引用显示驱动
#include <stdio.h> // 用于 sprintf

void DisplayManagement_Init(void) {
Display_Init(); // 初始化显示驱动
}

void DisplayManagement_UpdateDisplay(float voltage, float current) {
char displayStr[20];
sprintf(displayStr, "V:%.2fA:%.2f", voltage, current); // 格式化显示字符串
Display_ShowString(displayStr); // 显示字符串 (需要根据实际显示驱动修改)
// 或者可以分别显示电压和电流值,例如:
// Display_ShowString("VOLT");
// Display_ShowNumber((uint16_t)(voltage * 100)); // 显示电压值,放大 100 倍显示两位小数
// Display_ShowString("CURR");
// Display_ShowNumber((uint16_t)(current * 1000)); // 显示电流值,放大 1000 倍显示三位小数
}

void DisplayManagement_ShowError(const char *errorMsg) {
Display_ShowString(errorMsg); // 显示错误信息字符串
}
  • services_protection_management.h: 保护管理服务头文件
1
2
3
4
5
6
7
8
9
10
#ifndef SERVICES_PROTECTION_MANAGEMENT_H
#define SERVICES_PROTECTION_MANAGEMENT_H

// 初始化保护管理服务
void ProtectionManagement_Init(void);

// 检查并处理保护事件
void ProtectionManagement_CheckProtection(ADCSampleTypeDef sampleData);

#endif // SERVICES_PROTECTION_MANAGEMENT_H
  • services_protection_management.c: 保护管理服务源文件 (示例)
1
2
3
4
5
6
7
8
9
10
11
12
13
#include "services_protection_management.h"
#include "drivers_protection.h" // 引用保护模块驱动
#include "services_display_management.h" // 引用显示管理服务

void ProtectionManagement_Init(void) {
Protection_Init(); // 初始化保护模块驱动
}

void ProtectionManagement_CheckProtection(ADCSampleTypeDef sampleData) {
Protection_Check(sampleData); // 调用保护模块驱动进行保护检查和处理
// 如果 Protection_Check 函数没有处理显示错误信息,可以在这里处理
// 例如:如果 Protection_Check 返回错误代码,则调用 DisplayManagement_ShowError 显示错误信息
}

3.4 应用层 (Application Layer)

  • app_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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include "hal_delay.h" // 假设有延时函数
#include "drivers_keypad.h" // 引用按键驱动
#include "drivers_adc_sample.h" // 引用 ADC 采样驱动
#include "services_power_control.h" // 引用电源控制服务
#include "services_parameter_config.h" // 引用参数配置服务
#include "services_display_management.h" // 引用显示管理服务
#include "services_protection_management.h" // 引用保护管理服务

int main(void) {
// 系统初始化
HAL_Delay_Init(); // 初始化延时函数 (如果需要)
Keypad_Init(); // 初始化按键驱动
ADCSample_Init(); // 初始化 ADC 采样驱动
PowerControl_Init(); // 初始化电源控制服务
ParameterConfig_Init(); // 初始化参数配置服务
DisplayManagement_Init(); // 初始化显示管理服务
ProtectionManagement_Init(); // 初始化保护管理服务

// 加载参数 (可选)
ParameterConfig_LoadParameters();

// 设置初始目标电压和电流 (从参数配置服务获取)
PowerControl_SetTargetVoltage(ParameterConfig_GetVoltageSetting());
PowerControl_SetTargetCurrent(ParameterConfig_GetCurrentLimitSetting());

while (1) {
// 1. 获取按键输入
KeyCodeTypeDef key = Keypad_GetKey();
if (key != KEY_NONE) {
// 处理按键事件
switch (key) {
case KEY_UP:
// 增加电压设置
ParameterConfig_SetVoltageSetting(ParameterConfig_GetVoltageSetting() + 0.1); // 每次增加 0.1V
PowerControl_SetTargetVoltage(ParameterConfig_GetVoltageSetting());
break;
case KEY_DOWN:
// 减少电压设置
ParameterConfig_SetVoltageSetting(ParameterConfig_GetVoltageSetting() - 0.1); // 每次减少 0.1V
PowerControl_SetTargetVoltage(ParameterConfig_GetVoltageSetting());
break;
case KEY_ENTER:
// 确认设置,保存参数 (可选)
ParameterConfig_SaveParameters();
break;
case KEY_CANCEL:
// 取消操作,恢复上次设置 (如果需要)
break;
default:
break;
}
}

// 2. ADC 采样
ADCSampleTypeDef sampleData = ADCSample_GetData();

// 3. 电源控制 (PID 控制环)
PowerControl_ControlLoop();

// 4. 保护检查
ProtectionManagement_CheckProtection(sampleData);

// 5. 更新显示
DisplayManagement_UpdateDisplay(PowerControl_GetCurrentVoltage(), PowerControl_GetCurrentCurrent());

// 6. 延时 (控制循环周期)
HAL_Delay_ms(10); // 例如 10ms 循环周期
}

return 0;
}

四、 项目中采用的各种技术和方法

在这个数控可调型直流稳压电源项目中,我们采用了多种经过实践验证的技术和方法,以确保系统的可靠性、高效性和可扩展性:

  1. 分层架构: 将软件系统划分为硬件抽象层、驱动层、服务层和应用层,降低了系统的复杂度,提高了代码的可维护性和可移植性。

  2. 模块化设计: 将每个层次的功能进一步分解为独立的模块,模块之间通过清晰的接口进行通信,提高了代码的复用性和可测试性。

  3. 事件驱动机制: 采用事件驱动的方式处理用户输入和系统事件,提高了系统的实时性和响应性,减少了 CPU 的空转。

  4. PID 控制算法: 使用 PID (比例-积分-微分) 控制算法来实现精确的电压和电流控制,保证输出的稳定性和精度。PID 控制算法是一种经典且成熟的闭环控制算法,广泛应用于各种工业控制系统中。

  5. 硬件抽象层 (HAL): HAL 层封装了底层的硬件操作,使得上层代码可以独立于具体的硬件平台,方便进行硬件平台的切换和代码的移植。

  6. 软件工程实践: 在整个开发过程中,遵循软件工程的最佳实践,包括需求分析、架构设计、详细设计、编码、测试、文档编写等,保证软件的质量和可维护性。

  7. 代码注释和文档: 代码中添加详细的注释,并编写相应的文档,方便代码的理解和维护,也方便团队协作开发。

  8. 版本控制工具 (例如 Git): 使用版本控制工具管理代码,方便代码的版本管理、协同开发和 bug 追踪。

  9. 测试驱动开发 (TDD) (可选): 在开发过程中,可以考虑采用测试驱动开发的方法,先编写测试用例,然后编写代码,确保代码的正确性和可靠性。

  10. 持续集成/持续交付 (CI/CD) (可选): 对于更复杂的项目,可以考虑引入 CI/CD 流程,自动化构建、测试和部署过程,提高开发效率和软件质量。

五、 测试验证和维护升级

5.1 测试验证

在系统开发完成后,需要进行全面的测试验证,确保系统满足需求规格,并具有良好的性能和可靠性。测试主要包括以下几个方面:

  • 单元测试: 对每个模块进行单元测试,验证模块的功能是否正确,接口是否符合规范。可以使用单元测试框架 (例如 CUnit, CMocka) 来辅助进行单元测试。

  • 集成测试: 将各个模块组合起来进行集成测试,验证模块之间的协作是否正常,数据传递是否正确。

  • 系统测试: 对整个系统进行全面的功能测试和性能测试,验证系统是否满足所有的功能需求和性能指标。系统测试包括:

    • 功能测试: 测试电压调节、电流限制、显示功能、保护功能、参数存储等各项功能是否正常工作。
    • 性能测试: 测试电压和电流的精度、响应速度、纹波噪声、效率等性能指标是否满足要求。
    • 可靠性测试: 进行长时间的运行测试,验证系统的稳定性、可靠性和容错能力。例如进行老化测试、环境测试等。
    • 用户界面测试: 测试用户界面的友好性、易用性和操作性。
  • 回归测试: 在代码修改或升级后,进行回归测试,确保新的修改没有引入新的 bug,并且没有影响原有功能。

5.2 维护升级

嵌入式系统的维护升级是一个持续的过程。在系统发布后,可能需要进行 bug 修复、功能升级、性能优化等维护升级工作。为了方便系统的维护升级,我们在系统设计时就考虑了以下几点:

  • 模块化设计: 模块化的代码结构使得修改和升级某个模块的代码时,不会影响到其他模块,降低了维护的风险。

  • 清晰的接口: 模块之间通过清晰的接口进行通信,使得模块的替换和升级更加容易。

  • 良好的文档: 完善的文档可以帮助维护人员快速理解代码,定位问题和进行修改。

  • 预留升级接口: 在硬件和软件设计中,可以预留一些升级接口,例如预留通信接口 (UART, USB, Ethernet) 用于远程升级,预留 Flash 空间用于存储新的固件。

  • OTA (Over-The-Air) 升级 (可选): 对于需要频繁升级的系统,可以考虑实现 OTA 升级功能,通过无线网络进行固件升级,提高升级的便利性。

总结

这个数控可调型直流稳压电源项目,从需求分析到代码实现,再到测试验证和维护升级,展示了一个完整的嵌入式系统开发流程。通过采用分层架构、模块化设计、事件驱动机制、PID 控制算法等经过实践验证的技术和方法,我们建立了一个可靠、高效、可扩展的系统平台。提供的 C 代码示例涵盖了各个层次和模块的核心功能,可以作为项目开发的参考和起点。在实际项目中,还需要根据具体的硬件平台、需求细节和性能指标进行进一步的完善和优化。希望这个详细的方案能够帮助你理解嵌入式系统开发的全过程,并为你的项目开发提供有益的指导。

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