用STM32和PID算法,我给自己做了个可调压调流的桌面数控电源(附完整代码)
从零打造桌面级数控电源:STM32与PID算法的实战指南
1. 项目背景与设计思路
去年工作室搬迁时,我发现自己需要频繁切换不同电压的电源适配器来测试各种开发板。市面上的数控电源要么价格昂贵,要么功能单一,于是萌生了自己动手打造一台的想法。经过三个月的迭代,最终完成了一台基于STM32的数控电源,支持0-30V/0-5A可调范围,精度达到±0.05V和±10mA。
这个项目的核心挑战在于实现电压电流双闭环控制。与普通线性电源不同,我们采用BUCK降压拓扑,通过PWM控制MOSFET开关,配合PID算法实现快速响应和稳定输出。整个系统包含以下几个关键部分:
- 功率转换模块:IR2104驱动+IRL3803 MOSFET组合
- 采样电路:INA240电流检测放大器+差分电压采样
- 控制核心:STM32F103C8T6最小系统
- 人机交互:0.96寸OLED+旋转编码器
- 保护电路:输入过压/欠压、输出过流/过压四重保护
提示:选择STM32F103主要考虑其PWM分辨率(72MHz主频下可达1440级)和12位ADC的性价比优势
2. 硬件设计关键点
2.1 功率级设计
Buck电路的核心参数计算需要平衡效率、体积和成本。我的最终方案如下表所示:
| 参数 | 计算值 | 实际选用 | 备注 |
|---|---|---|---|
| 开关频率 | 100kHz | 98.4kHz | 基于STM32定时器配置 |
| 电感值 | 68μH | 82μH | 考虑20%余量 |
| 输入电容 | 47μF | 2×22μF并联 | 低ESR电解电容 |
| 输出电容 | 100μF | 47μF+10μF | 陶瓷电容滤波高频噪声 |
电感饱和电流的选择尤为重要,我通过以下公式验证:
I_sat ≥ I_out_max + ΔI_pp/2 ΔI_pp = (V_in - V_out) × V_out / (V_in × L × f_sw)实际测试中发现,使用铁硅铝磁环绕制的电感温升比现成功率电感低15℃左右。
2.2 采样电路设计
电流采样采用0.05Ω/3W的锰铜电阻配合INA240放大器,电路设计要点:
- 布局时采样电阻尽量靠近MOSFET源极
- 差分走线长度保持一致
- 在放大器输出端添加RC滤波(1kΩ+100nF)
电压采样则采用电阻分压+电压跟随器结构,特别注意:
- 分压电阻精度选0.1%
- 在ADC输入端添加TVS二极管防护
- 软件中实现数字滤波(移动平均+中值)
3. 软件架构与PID实现
3.1 控制逻辑框架
整个系统采用状态机模式运行,核心代码如下:
typedef enum { STATE_INIT, STATE_STANDBY, STATE_CV_MODE, STATE_CC_MODE, STATE_FAULT } PSU_State; void PSU_StateMachine(void) { static uint32_t last_update = 0; if(HAL_GetTick() - last_update < 10) return; switch(current_state) { case STATE_INIT: Hardware_Init(); current_state = STATE_STANDBY; break; case STATE_STANDBY: if(user_setting.enable) { PID_Reset(&vPID); current_state = STATE_CV_MODE; } break; case STATE_CV_MODE: CV_Control_Loop(); if(current > setting.current) { current_state = STATE_CC_MODE; } break; // 其他状态处理... } last_update = HAL_GetTick(); }3.2 PID算法优化
传统位置式PID在电源控制中存在积分饱和问题,我改用了增量式PID算法:
typedef struct { float kp, ki, kd; float error, last_error, prev_error; float output, last_output; float max_limit, min_limit; } PID_Controller; float PID_Update(PID_Controller* pid, float setpoint, float feedback) { pid->error = setpoint - feedback; float delta = pid->kp * (pid->error - pid->last_error) + pid->ki * pid->error + pid->kd * (pid->error - 2*pid->last_error + pid->prev_error); pid->output = pid->last_output + delta; // 抗积分饱和处理 if(pid->output > pid->max_limit) { pid->output = pid->max_limit; } else if(pid->output < pid->min_limit) { pid->output = pid->min_limit; } pid->prev_error = pid->last_error; pid->last_error = pid->error; pid->last_output = pid->output; return pid->output; }参数整定经验:
- 先调P直到出现轻微振荡
- 加入D抑制振荡
- 最后加入I消除静差
- 不同电压段可能需要不同的参数组
4. 保护机制与故障处理
完善的保护电路是电源设计的重中之重,我的实现方案包含硬件和软件双重保护:
硬件保护:
- 输入过压:TL431比较器直接关断MOSFET驱动
- 输出过流:ACS712霍尔传感器+快速比较器
- MOSFET过热:NTC贴片电阻+迟滞比较
软件保护(优先级从高到低):
- 输入电压异常(>32V或<8V)
- 输出短路检测(dI/dt>1A/ms)
- 温度监控(MOSFET>85℃)
- 功率器件失效检测(PWM占空比与输出电压不符)
故障恢复流程采用渐进式策略:
- 首次故障:立即关断,需手动复位
- 短时重复故障:自动延迟5秒后尝试恢复
- 持续故障:锁定并记录错误代码
5. 人机交互优化
旋转编码器的处理需要消抖和加速算法:
void Encoder_Handler(void) { static int32_t counter = 0; static uint8_t last_state = 0; static uint32_t last_time = 0; uint8_t current_state = HAL_GPIO_ReadPin(ENC_A_GPIO_Port, ENC_A_Pin) | (HAL_GPIO_ReadPin(ENC_B_GPIO_Port, ENC_B_Pin) << 1); // 状态机解码 if(last_state != current_state) { uint32_t now = HAL_GetTick(); uint32_t interval = now - last_time; // 加速算法:转速越快步进越大 int32_t step = (interval < 50) ? 5 : (interval < 100) ? 2 : 1; if((last_state == 0x00 && current_state == 0x02) || (last_state == 0x03 && current_state == 0x01)) { counter += step; } else { counter -= step; } last_state = current_state; last_time = now; } // 应用数值变化 if(abs(counter) >= ENC_THRESHOLD) { int8_t dir = (counter > 0) ? 1 : -1; Adjust_Setting(dir * (abs(counter)/ENC_THRESHOLD)); counter = 0; } }OLED显示采用自定义字体和分级菜单,关键信息包括:
- 实时电压/电流(大号字体)
- 设定值(反色显示)
- 工作模式(CV/CC)
- 温度及保护状态图标
6. 实测性能与改进方向
经过一周的连续老化测试,主要性能指标如下:
| 测试项目 | 条件 | 结果 |
|---|---|---|
| 电压精度 | 5V输出 | ±0.03V |
| 负载调整率 | 0-5A跳变 | <50mV跌落 |
| 纹波噪声 | 20V/3A输出 | 12mVpp |
| 转换效率 | 24V输入,12V/5A输出 | 89% |
发现的几个待改进问题:
- 轻载时(<100mA)效率骤降至65%
- 快速切换设定值时会有约200ms的过冲
- 编码器在潮湿环境下偶尔误触发
可能的解决方案:
- 增加PFM模式改善轻载效率
- 实现PID参数的自适应调整
- 改用光学编码器提升环境适应性
