简介:一套围绕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 参数是这套电源的精华。实际调出来的参数为 kp=0.8,ki=0.05,kd=0.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.3V,OLED 显示 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.c | PWM 定时器初始化 | 改频率时 |
| Bsp/bsp_adc.c | ADC 与 DMA 配置 | 换采样引脚时 |
| Bsp/bsp_oled.c | OLED 驱动 | 改显示内容时 |
| Control/pid.c | PID 参数与算法 | 高频修改 |
| 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 参数必须强制走一遍空载到带载再到动态响应的完整验证流程,不能只盯着空载电压稳不稳,等接上负载才发现问题。从那以后我每次做电源调试都先准备好电子负载和示波器再动代码,这个习惯帮我少走了很多弯路,希望帮到你。
本文还有配套的精品资源,点击获取