当前位置: 首页 > news >正文

用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电路的核心参数计算需要平衡效率、体积和成本。我的最终方案如下表所示:

参数计算值实际选用备注
开关频率100kHz98.4kHz基于STM32定时器配置
电感值68μH82μH考虑20%余量
输入电容47μF2×22μF并联低ESR电解电容
输出电容100μF47μ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放大器,电路设计要点:

  1. 布局时采样电阻尽量靠近MOSFET源极
  2. 差分走线长度保持一致
  3. 在放大器输出端添加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; }

参数整定经验:

  1. 先调P直到出现轻微振荡
  2. 加入D抑制振荡
  3. 最后加入I消除静差
  4. 不同电压段可能需要不同的参数组

4. 保护机制与故障处理

完善的保护电路是电源设计的重中之重,我的实现方案包含硬件和软件双重保护:

硬件保护

  • 输入过压:TL431比较器直接关断MOSFET驱动
  • 输出过流:ACS712霍尔传感器+快速比较器
  • MOSFET过热:NTC贴片电阻+迟滞比较

软件保护(优先级从高到低):

  1. 输入电压异常(>32V或<8V)
  2. 输出短路检测(dI/dt>1A/ms)
  3. 温度监控(MOSFET>85℃)
  4. 功率器件失效检测(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%

发现的几个待改进问题:

  1. 轻载时(<100mA)效率骤降至65%
  2. 快速切换设定值时会有约200ms的过冲
  3. 编码器在潮湿环境下偶尔误触发

可能的解决方案:

  • 增加PFM模式改善轻载效率
  • 实现PID参数的自适应调整
  • 改用光学编码器提升环境适应性
http://www.jsqmd.com/news/667526/

相关文章:

  • 从空气动力学到代码:Matlab仿真揭秘风机Pm-Wm动态关系
  • 别再死磕教材了!用Protege 5.5.0手把手教你构建第一个知识图谱本体(附避坑指南)
  • UE5——动画混合实战:从原理到高级应用
  • 网络工程师必看:GFP帧结构中的校验(CRC)与加扰到底在防什么?
  • PCB安规设计实战:从理论到Layout的爬电距离与电气间隙精准把控
  • 树莓派4B接口实战:用GPIO控制LED灯,USB连接外设的完整教程
  • Qwen3.5-9B Java八股文深度学习:源码级理解与高频面试题破解
  • Mybatis日志框架实战:从SLF4J门面到Log4j2配置详解
  • Altium Designer 21导入HFSS的DXF文件后,图层混乱、边框不对?看这篇就够了
  • LeetCode 139. 单词拆分:动态规划经典入门题
  • 大气层整合包系统架构解析与深度优化指南
  • DevEco Studio:快速生成一个类的构造函数
  • 告别乱码与格式之争:在Visual Studio C++项目中全面启用UTF-8与.editorconfig
  • 如何用Microsoft PICT在30分钟内生成高质量组合测试用例?提升测试效率的实战指南
  • 当注意力机制遇上全局工作空间理论:MITDeepMind联合推演的AGI意识涌现临界点(精确到10⁻⁴秒级时序建模)
  • 别再只盯着准确率了!用Python的sklearn搞定多分类模型的macro与micro F1-score计算
  • 别再踩坑了!Android 10+ 保存图片到相册的完整流程与权限处理(附完整代码)
  • DevEco Studio:快速生成getter和setter方法
  • 高效解决图表数据提取难题:WebPlotDigitizer完整实战指南
  • 金蝶云单据下推进阶:复杂子单据体与基础数据的精准转换
  • 告别高精地图:用RoadMap和AVP-SLAM的语义地图思路,低成本搞定自动驾驶定位
  • 【花雕动手做】小龙虾 MimiClaw 二次开发:控制四电机麦克纳姆轮实现全向运动
  • 飞书事件订阅避坑指南:从URL验证失败到解密报错,我踩过的那些坑(Java版)
  • Vue2项目实战:从AxiosError到ERR_NETWORK,一站式解决跨域请求难题
  • 【多变量输入单步预测】基于北方苍鹰算法(NGO)优化CNN-BiLSTM-Attention的风电功率预测研究(Matlab代码实现)
  • 告别图层导出噩梦:Photoshop批量导出工具让你工作效率提升300%
  • 开源Text-to-Music:基于Meta模型的本地音乐生成方案
  • Keil User Command实战:除了生成Bin/Hex,你的编译后脚本还能玩出什么花样?
  • 运维视角:在统信UOS服务器上部署达梦8数据库的自动化脚本与监控告警配置
  • 【26年6月英语六级】英语六级高频核心词汇1500个+历年真题PDF电子版