基于STM32的Boost数控电源:PID闭环可编程升压方案设计与实现

发布时间:2026/10/17 8:09:15
基于STM32的Boost数控电源:PID闭环可编程升压方案设计与实现
简介一套围绕STM32微控制器与Boost升压拓扑的数控电源完整工程资料面向嵌入式电源开发者、电子竞赛学生及数字控制电源研究者解决从Boost主回路、采样反馈到恒流/恒压闭环控制的实践落地问题。压缩包32.82MB共510个文件主力类型为C/H源代码、uvprojx与sln工程配置、hex/axf编译产物另含PDF/DOCX说明文档及上位机监控程序可根据文件后缀快速定位源码、固件或文档。项目已有1609人学习下载适合需要基于真实工程理解数字电源控制策略的读者。资料内含基于STM32F0系列的PWM调制与ADC采样代码、恒流恒压切换逻辑、上位机通信协议说明及部分设计文档便于从零搭建或二次开发数控Boost电源。1. 基于STM32的Boost数控电源一台不用手拧电位器的可编程升压方案做实验经常遇到一个尴尬的场景手里板卡需要一路精确的 12.8V 供电翻遍桌面全是 12V 固定输出模块要么就是只能靠螺丝刀反复微调电位器的可调模块调完开机跑十分钟电压又飘了。这套基于 STM32 的 Boost 数控电源解决的就是这个痛点——用按键设定目标电压STM32 通过 ADC 实时采样输出电压再靠 PID 闭环调整 Boost 拓扑的 PWM 占空比让输出稳定在设定值上。整个项目从硬件原理图、控制代码到调试记录都打包好了网上做 Boost 的资料虽然丰富但能把原理图、完整代码和调参经验放在一起的成套方案并不多对想自己搭一台可编程电源的电子类学生和嵌入式开发者来说属于拿来就能复现的实用项目。这个方案里最反直觉的一点是Boost 升压电源的闭环控制比降压更难做难点不在升压本身而在它的控制环路存在右半平面零点PID 参数调不好就会出现负载突变时电压塌下去又冲上来。后面会花一整章讲这个问题怎么排查。如果你已经有 STM32 开发基础看这套代码会觉得结构很熟悉如果你是第一次接触数控电源从编译烧录到带载验证也有完整流程可以照着走。2. 系统架构与技术选型12V 进、30V 出的 Boost 链路与控制方案2.1 为什么选 Boost 拓扑以及它和普通模块的本质区别数控电源的“数控”两个字关键不在功率级拓扑而在电压设定值从哪里来。普通 Boost 模块用电阻分压网络设定输出电压出厂焊死一组电阻想要别的电压就得换电阻或者拧电位器。数控方案则是把分压网络中的下臂电阻换成可控基准源——MCU 输出一个精确的基准电压误差放大器把输出电压采样和这个基准作比较环路自动调节占空比。选择 Boost 拓扑的直接原因是输入输出关系输入 12V需要输出 5V 到 30V 可调输入始终低于输出Boost 是不二之选。另一个原因是效率同步整流 Boost 在 12V 升 20V 附近效率能到 90% 以上比先降压再升压的二级方案少一次变换损耗。整套系统的设计指标可以这样描述输入 DC 12V输出 5V 到 30V 连续可调最大输出电流 2A电压设定分辨率 0.1V控制频率 100Hz 左右。这个量级决定了主控不需要多强一颗主频 72MHz 的 STM32F103 绰绰有余外设资源刚够——定时器输出 PWM、ADC 采集两路模拟量、I2C 驱动 OLED、两个 GPIO 读按键。2.2 硬件链路从功率级到采样反馈的每一级作用硬件上整条链路可以分成五段主控最小系统、功率级、驱动电路、采样反馈、人机交互。主控部分就是常规的 STM32F103C8T6 最小系统板外部晶振 8MHz内部倍频到 72MHz 跑全部逻辑。功率级核心元件为一只 100uH 功率电感、一颗低压降肖特基二极管、一颗 N 沟道 MOSFET 和输出侧的电解电容与陶瓷电容并联组合。驱动电路值得多说一句。Boost 的 MOSFET 是低边开关源极接地栅极驱动不需要自举电路用一颗通用栅极驱动芯片或者直接由 MCU 的 IO 通过三极管推挽驱动都行。常见做法是用栅极驱动芯片因为 MCU 的 GPIO 驱动能力有限频率一高波形边沿变缓开关损耗会明显上升。这里选择的是独立驱动芯片方案PWM 信号从 STM32 定时器输出后先经过驱动芯片整形再送给 MOSFET 栅极。采样反馈段是数控电源的“眼睛”。输出电压经过两个 1% 精度电阻分压到 ADC 可接受的范围常见做法是分压比 11:1把 30V 降到 2.73V刚好落在 STM32 ADC 的 0 到 3.3V 量程内。采样值经过运放跟随器缓冲后送入 ADC 引脚避免 ADC 采样瞬间的输入阻抗变化影响分压点电压。人机交互用两个按键加一块 0.96 寸 OLED按键负责电压加减OLED 实时显示设定值和实际输出电压。2.3 软件框架控制环路与任务划分软件上整个工程按功能分成三个层次底层外设初始化、控制环路、交互任务。底层外设包括 PWM 定时器初始化、ADC 初始化、I2C 初始化、GPIO 配置这部分用标准外设库或者 HAL 库都行核心是拿到一个定时器输出中心对齐 PWM另一个定时器触发 ADC 采样。控制环路是整个数控电源的软件核心。PID 计算跑在定时器中断里中断频率设成 100Hz每次中断做三件事读取最新 ADC 采样值、计算当前输出电压、执行 PID 运算后更新 PWM 占空比寄存器。交互任务放在主循环里按键扫描加软件防抖电压设定值修改后立即更新 PID 的目标参考值OLED 每 200 毫秒刷新一次显示。这里要特别说明控制频率的选取。Boost 的开关频率是 100kHz 左右但 PID 环路没必要跑这么快100Hz 的控制频率对应 10ms 控制周期对于输出端有电容滤波的电源系统来说足够跟上负载变化。控制频率太低会导致动态响应差负载突变时电压跌落明显太高则 PID 运算量增大而且容易把开关噪声引入环路。10ms 周期是我这套电源实际调出来的经验值能在动态响应和稳定性之间取得平衡。3. 核心代码拆解基准生成、ADC 采样与 PID 闭环的完整实现3.1 用 DAC 还是 PWM 滤波生成电压基准数控电源需要一个可编程的电压基准。两种常见实现方式一是用 STM32 内置 DAC 直接输出模拟电压二是用 PWM 加 RC 低通滤波产生直流电平。F103 系列部分型号有 DAC但输出范围是 0 到 3.3V经过误差放大器之后还要配合分压电阻才能映射到输出电压范围。PWM 滤波方式理论上通用性更好但 RC 滤波的纹波和响应速度会影响环路表现。这套代码用的是内部 DAC 方案。电压设定值在软件里换算成 DAC 输出值DAC 输出接到误差放大器的同相输入端作为基准。DAC 是 12 位分辨率对应基准电压步进约 0.8mV反射到输出电压侧步进约 9mV完全满足 0.1V 的设定精度要求。void DAC_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; // DAC_OUT1 引脚 GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); DAC_InitTypeDef DAC_InitStructure; DAC_InitStructure.DAC_Trigger DAC_Trigger_None; // 软件触发 DAC_InitStructure.DAC_WaveGeneration DAC_WaveGeneration_None; DAC_InitStructure.DAC_OutputBuffer DAC_OutputBuffer_Enable; DAC_Init(DAC_Channel_1, DAC_InitStructure); DAC_Cmd(DAC_Channel_1, ENABLE); }关键点是 DAC 输出缓冲器必须使能。如果不使能DAC 输出阻抗很高接入误差放大器后会因为负载效应导致基准电压偏移。另外这里使用了软件触发方式每次修改 DAC 值后调用一次 DAC_SoftwareTriggerCmd 即可生效不需要定时器触发。设定电压到 DAC 值的换算在交互层完成。电压设定值 12.8V 先除以分压比得到基准电压再除以 3.3V 乘以 4096 得到 DAC 寄存器值。这一步有个精度细节分压比本身有误差所以在最终校准环节会用万用表读数反推修正系数而不是完全依赖理论计算值。3.2 ADC 采样与滑动平均滤波输出电压采样直接决定控制精度这里用定时器触发 ADC 采样采样结果在中断里读取。ADC 配置为连续采样模式每次转换完成后触发 DMA 传输把结果搬到一个环形缓冲区。连续采样加 DMA 的好处是 CPU 不需要等待转换采样数据不会因为中断延迟而丢失。#define SAMPLE_BUF_SIZE 8 uint16_t adc_buf[SAMPLE_BUF_SIZE]; volatile uint8_t sample_index 0; volatile uint32_t sample_sum 0; void ADC_DMA_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; // PA1 模拟输入 GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)adc_buf; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize SAMPLE_BUF_SIZE; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; DMA_Init(DMA1_Channel1, DMA_InitStructure); ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; ADC_InitStructure.ADC_ContinuousConvMode ENABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5); DMA_Cmd(DMA1_Channel1, ENABLE); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); }DMA 环形缓冲模式下每次 DMA 传输完成都会把数据写入缓冲区的下一个位置指针循环绕回。这样 MCU 任何时刻去读缓冲区拿到的都是最近 8 次采样的原始数据。每次 PID 计算前对这 8 个数据做平均等效于一个滑动平均滤波器能有效抑制输出电压上的高频纹波对采样值的干扰。ADC 采样时间配置为 239.5 个周期这是 F103 最长的采样时间选项。输入信号经过分压电阻和运放跟随器输出阻抗较低理论上不需要这么长的采样时间但实测中发现采样时间短时读数有明显跳变原来是 PCB 上开关节点的高频噪声耦合到了采样线上延长采样时间等效于加强内部采样电容的充电时间读数稳定很多。3.3 位置式 PID 控制器实现控制核心是一个离散化的位置式 PID。每次控制周期计算当前误差、误差积分和误差微分输出值直接映射到 PWM 占空比寄存器。位置式的好处是结构简单输出量和 PWM 占空比一一对应调试时可以直接输出中间变量观察。typedef struct { float target; // 目标电压基准电压值 float feedback; // 当前采样电压 float kp; float ki; float kd; float integral; float last_error; float out_min; // 占空比下限 float out_max; // 占空比上限 } pid_t; float PID_Update(pid_t *pid, float target, float feedback) { float error target - feedback; pid-integral error; // 积分限幅防止饱和 if (pid-integral 100) pid-integral 100; if (pid-integral -100) pid-integral -100; float derivative error - pid-last_error; pid-last_error error; float output pid-kp * error pid-ki * pid-integral pid-kd * derivative; // 输出限幅对应 PWM 占空比范围 if (output pid-out_max) output pid-out_max; if (output pid-out_min) output pid-out_min; return output; }PID 参数是这套电源的精华。实际调出来的参数为 kp0.8ki0.05kd0.1。kp 控制对误差的即时反应太大容易在负载突变时产生过冲ki 消除稳态误差但积分项会让环路对阶跃响应变慢所以限制了积分上限kd 的微分项在 Boost 拓扑里使用要谨慎因为输出纹波会被微分放大kd 太大会导致 PWM 占空比抖动反而引入噪声。这组参数是在 12V 输入、空载到 1A 负载跳变条件下调出来的换硬件后需要重新标定。PID 输出值直接写入定时器比较寄存器。控制周期 10ms定时器 PWM 频率 100kHz自动重装载值设为 720占空比寄存器范围 0 到 720。PID 输出 0 到 100 的浮点值换算成寄存器值就是乘以 7.2。实际代码里没有每次做浮点乘法而是把输出限幅到整数范围后直接赋值减少中断内的运算时间。3.4 按键调压与 OLED 显示刷新交互部分最核心的是按键防抖和电压设定步进。两个按键分别对应电压加减步进 0.1V长按 1 秒后步进切到 1V 快速调整这个逻辑在很多电源上都有类似实现。#define KEY_PLUS_PIN GPIO_Pin_0 #define KEY_MINUS_PIN GPIO_Pin_1 volatile uint8_t key_plus_pressed 0; volatile uint8_t key_minus_pressed 0; volatile uint16_t key_longpress_count 0; void Key_Scan(void) { static uint8_t last_plus 1; static uint8_t last_minus 1; uint8_t plus_now GPIO_ReadInputDataBit(GPIOB, KEY_PLUS_PIN); uint8_t minus_now GPIO_ReadInputDataBit(GPIOB, KEY_MINUS_PIN); // 按键消抖前后两次状态一致才算稳定 if (plus_now 0 last_plus 0) { key_plus_pressed 1; } if (minus_now 0 last_minus 0) { key_minus_pressed 1; } last_plus plus_now; last_minus minus_now; }OLED 显示部分在 200ms 周期内刷新一行设定电压和一行当前输出电压。这里有个容易忽略的细节OLED 刷新和 PID 控制循环共享同一个电压设定值变量如果不加保护可能出现在 ADC 中断里读到半个字的问题。这套代码把设定值定义成 16 位整数一次读取在 72MHz 主频下是原子操作不存在撕裂问题这也是推荐在主循环和中断之间只传递简单数据类型的原因。4. 调试避坑指南数控 Boost 常见问题与排查4.1 现象上电瞬间输出电压冲到 40V 以上第一次给板子上电示波器捕捉到输出端瞬间冲高到 40V 以上然后慢慢回落到设定值如果负载比较敏感板子可能直接就烧了。原因排查下来有两层第一层是 PID 的积分初始值不为零上电瞬间误差很大积分项快速累积把占空比推到最大值第二层是 Boost 拓扑在输出电容未充电时输入电压会通过电感和二极管直接给电容充电即使 PWM 占空比为 0输出也会先冲到接近输入电压的位置。解决方法是双管齐下开机时先把 PID 积分项清零、占空比寄存器置 0等输出电容预充到接近设定值再使能闭环控制。代码里做了一个软启动状态机开机后先以固定小占空比开启 PWM持续 500ms 再切入 PID 闭环。4.2 现象带载后输出电压下降且伴随低频纹波空载时电压稳如泰山一接上 1A 电子负载电压立刻下降 300mV并且输出波形上出现大约几十赫兹的低频振荡。这类问题十有八九出在地线上。输出电流流过功率地平面在采样电阻的接地点产生压降采样电路的地和功率地共用了一段走线把功率地上的纹波直接引入了 ADC 参考地。解决方法是把采样电路的参考地单独走线从输出电容的负极用一条独立引线回到主控板的模拟地形成单点接地。重新布线后低频振荡消失带载电压误差控制在 20mV 以内。4.3 现象PID 参数无论怎么调都振荡按理论上调 kp 加快响应结果系统持续振荡PWM 占空比波形呈现周期性抖动输出电压纹波从 20mV 变成 200mV。Boost 拓扑的右半平面零点决定了它的相位裕度天生不如降压拓扑单纯抬高 kp 不会让响应变快只会把系统推入振荡区。解决办法是先降低 kp 到系统明显变“钝”的值再按 1:1:2 的比例同时调整。另一个有效手段是降低控制频率从 100Hz 降到 50Hz给环路留出更多稳定性余量。我最后稳定下来的参数就是在 100Hz 控制频率下kp 从 2.0 降到了 0.8 才稳定。4.4 现象OLED 显示电压与万用表读数不一致偏差 0.3V设定 12.0V万用表测到 12.3VOLED 显示 12.0V意味着 ADC 采到的值偏小需要提高采样值或者调整分压比。排查发现分压电阻用的是 1% 精度两个电阻的误差叠加起来确实可能达到 3% 左右对应 30V 满量程就是 0.9V 的误差。解决方法是做软件校准用万用表实测输出电压在代码里记录实际值与 ADC 换算值的偏差生成一个偏移修正量。更严谨的做法是取两点校准30% 量程处测一次、90% 量程处测一次线性拟合出增益和偏置两个系数。4.5 现象PWM 占空比超过 85% 后输出电压不再上升设定 30V 输出时PWM 占空比已经推到上限但输出电压卡在 26V 左右上不去。这属于 Boost 电路的物理限制。占空比越高电感电流的斜坡越大同时输出二极管导通时间变短要么是电感饱和了要么是输入电流超过 MOSFET 和电感的额定值。排查方法是测电感电流波形饱和时电流波形会出现明显畸变。解决方案是换更大饱和电流的电感或者把输出功率目标下调。实际测试中12V 输入升到 26V 已经是 2A 输出下的极限想稳定输出 30V要么降低电流指标要么改用耦合电感方案。5. 从零跑通工程编译、接线与带载验证流程5.1 工程结构拿到源码先看这四块整套源码拿到手之后先把目录结构过一遍。核心目录是 App、Bsp、Control 和 Doc 四个部分。App 目录放主循环和中断入口Bsp 目录是底层驱动Control 目录是 PID 和状态机相关代码Doc 目录放原理图和调试记录。文件清单大致如下。目录 / 文件作用需要修改的频率App/main.c主循环、任务调度低Bsp/bsp_pwm.cPWM 定时器初始化改频率时Bsp/bsp_adc.cADC 与 DMA 配置换采样引脚时Bsp/bsp_oled.cOLED 驱动改显示内容时Control/pid.cPID 参数与算法高频修改Control/power_ctrl.c状态机与软启动中等Doc/schematic.pdf硬件原理图无5.2 编译烧录标准工程流程三步走完工程基于 STM32 标准外设库构建开发环境用 Keil MDK 或者 VS Code 加 GCC 工具链都行。拿到工程后第一步确认芯片型号选择正确F103C8 的 flash 是 64KB如果代码编译后超过这个容量需要检查优化等级。通常工程默认开 -O1 优化编译后用 ST-Link 下载器烧录。接线方面严格按照原理图连接12V 电源接到功率输入端STM32 最小系统板单独用 USB 5V 供电功率地和信号地在主控板附近单点相连。烧录时注意 ST-Link 和目标板共地否则下载时可能报连接错误。第一次上电不要在输出端接负载先空载验证电压闭环是否正常。# 使用 ST-Link 命令行工具烧录也可以直接用 Keil 的下载按钮 st-flash write build/power_supply.bin 0x08000000烧录完成后观察 OLED 是否点亮。如果不亮优先检查 I2C 地址和 OLED 电源0.96 寸 OLED 的 I2C 地址通常是 0x3C如果显示白屏在驱动代码里把地址改到 0x3D 再试。这段排查逻辑同样适用于其他 I2C 传感器模块。5.3 带载验证三个步骤确认电源性能达标验证流程按空载、带载、动态响应三步走。空载阶段开机后 OLED 显示 5.0V 设定值按加号键逐步升到 24V万用表读数跟设定值偏差不超过 0.1V。如果偏差过大进入校准流程在代码里调整 ADC 偏移量。带载阶段接一台电子负载设定 12V 输出负载从 0A 逐步加大到 1.5A观察输出电压跌落的幅度。合格标准是 1.5A 内电压跌落不超过 50mV如果跌落过大检查采样地接线和 PID 的 kp 值。动态响应阶段用示波器观察负载电流从 0.5A 跳变到 1.5A 时的电压波形。理想的响应是电压在 5ms 内恢复稳定超调不超过 200mV。如果出现振荡按第 4.3 节的方法调 PID。这一步是判断整套电源设计是否合格的关键指标也是后续改硬件后回归测试的必测项目。6. 二次开发思路加电流环、上位机与校准表的三种改法如果想把这套电源从“能出电压”升级到“能用”第一个改造建议是加恒流模式。原方案只有电压环实际使用中经常需要限制输出电流防止烧掉被测板卡做的电源系统。改造思路不复杂在采样链路里加一个低边电流采样电阻用一颗运放放大压降后送入 ADC 的另一通道再在控制代码里增加一个电流 PID电压环和电流环的输出经过一个低值选择器哪个计算结果低就接管 PWM 占空比。这个“二选一”的结构比双环级联简单调试难度也低一点。第二个改造是上位机控制。目前按键调压已经够用但做自动化测试时需要批量设置电压并记录输出值。串口是最便捷的通道在现有代码里加一个串口中断解析定义简单的 ASCII 协议比如 SET_VOLTAGE:12.5 表示设定电压QUERY_STATUS 回复当前电压电流。配合上位机脚本可以完成长时间稳定性测试和电压扫描记录。第三个改造是线性校准表。我自己的习惯是不满足于两点校准而是在 12V 到 24V 之间每 1V 取一个点用万用表记录实际电压和 ADC 采样值的偏差生成一张 13 行的校准表存在 Flash 里。运行时根据设定电压查表插值输出电压精度能做到 0.05V 以内。这个做法把采样电阻误差、DAC 非线性、运放偏置的误差一次性全部吸收了。这套资源我前后调了两周最大的教训是每次改完 PID 参数必须强制走一遍空载到带载再到动态响应的完整验证流程不能只盯着空载电压稳不稳等接上负载才发现问题。从那以后我每次做电源调试都先准备好电子负载和示波器再动代码这个习惯帮我少走了很多弯路希望帮到你。本文还有配套的精品资源点击获取