基于STM32的DCM Buck变换器设计与数字PID控制
2026/9/16 13:49:54 网站建设 项目流程

简介:面向电子信息类毕业设计或课程设计场景,这份资料以STM32为主控,完成DCM模式下BUCK变换器的软硬件系统设计,适合正在做电源方向课题的高校学生,也适合嵌入式或电源工程师作为工程参考。资料共227个文件,核心包含60个C源码、55个头文件以及启动汇编文件,覆盖STM32驱动与应用逻辑;同时提供原理图、PCB和库文件,便于理解硬件连接;另有PDF、Word、PPT等设计文档及多张原理图和实物图片,帮助梳理设计流程。整个压缩包约32.62MB,结构按功能模块划分,从电路设计到代码实现均有清晰呈现。目前已有110人学习下载,属于真实毕业设计整理后的实战成果,对课题开题、方案论证、电路搭建和代码调试都有直接的借鉴价值。

1. 用 STM32 做 DCM 模式的 Buck 变换器,这组毕设到底在解什么

布克变换器用现成的电源芯片搭出来并不难:输入输出电容、反馈电阻、电感一接,稳压和过流保护全是芯片内部的事。但“基于 STM32”和“DCM 模式”加进来后,难度就变了。功率级要自己搭,开关管用定时器输出的 PWM 驱动,输出电压和电感电流通过 ADC 回读,闭环逻辑全部写在固件里。毕设真正考察的是三件事:电感电流在什么条件下进入断续、按 DCM 边界选多大电感、数字 PID 在 100kHz 开关频率下怎么整定。

这套组合在答辩时问得最细。DCM 的轻载效率、没有右半平面零点带来的环路优势,以及同一输出电压下 DCM 占空比和 CCM 明显不同,这几个点能直接看出你是不是真做过。本文以 12V 转 5V、0.8A 输出为目标走完整条链路:DCM 边界计算、功率级与采样硬件、STM32 固件闭环,最后用示波器验证断续波形和负载阶跃。

2. DCM Buck 的临界条件与 STM32 侧的控制策略选择

2.1 临界电流公式:先判断你的 Buck 到底在哪种模式

Buck 变换器处于 CCM 还是 DCM,不看负载,也不看输入电压,只看一个等效条件:一个开关周期结束前,电感电流是否已经降到零。

以 12V 输入、5V 输出为例。假定开关频率 fs=100kHz,占空比近似 D≈Vout/Vin=0.417。电感电流在导通期间从波谷上升到波峰,斜率为 (Vin-Vout)/L;关断后以斜率 Vout/L 下降。临界状态恰好是波谷到达零的时刻,对应负载电流:

Io_crit = Vout * (1-D) / (2 * L * fs)

这里的 D 是稳态工作点附近的占空比,工程上直接用 CCM 占空比估算即可,误差不会影响边界判断。代入 Vout=5V、1-D=0.583、fs=100kHz,得到 L=2.917/(2 * Io_crit * 100000)。如果想让负载在 0.8A 以内都维持 DCM,就取 Io_crit≈0.97A,算出来 L≈15uH。反过来,如果直接焊一个 100uH 的电感上去,这个 Buck 在 0.15A 附近就转入 CCM,和题目要求的 DCM 目标就偏了。

所以选电感的先后顺序是:先确定目标负载范围,再用边界公式反推电感上限,再考虑饱和电流和绕线电阻,而不是先抓一个电感看波形。表 1 是不同电感在 12V/5V/100kHz 下的临界负载电流。

表 1 不同电感下的 DCM/CCM 边界电流(fs=100kHz)

电感 L (uH)临界电流 Io_crit (A)0.5A 负载时的模式
101.46DCM
150.97DCM
220.66接近边界
470.31CCM

DCM 的电压增益公式和 CCM 完全不同。CCM 下 Vo/Vin=D,DCM 下:

Vo/Vin = 2D / (D + sqrt(D² + 8L*fs/R))

其中 R 是负载电阻。用 L=15uH、R=6.25Ω(5V/0.8A)代入,要稳住 5V 输出,占空比大约 0.38,而 CCM 是 0.417。这里第一个工程含义是 DCM 不能直接用 CCM 的开环占空比,否则输出电压偏高;第二个含义是 DCM 占空比与负载和电感都相关,闭环时 PID 的输出初始范围要留出余量。固件里判断当前工作模式,可以在调试阶段加一个简单的边界函数:

#define L_IND 15e-6f // 实际电感值 #define FS_SW 100e3f // 开关频率 uint8_t is_dcm(float iout) { float d_on = 0.38f; // DCM 稳态占空比 float i_crit = (5.0f * (1.0f - d_on)) / (2.0f * L_IND * FS_SW); return (iout < i_crit) ? 1 : 0; }

D_ON 如果按 CCM 估算值 0.417,i_crit 算出约 0.97A;按 DCM 实际工作点 0.38,算出约 1.03A。两个值差别不大,固件里判断模式用哪个都行,区别只在边界余量。实际控制并不需要实时切换算法,DCM 和 CCM 用同一套 PID 在跑,这个函数主要用于串口打印调试,确认负载变化时模式是否正确切换。

2.2 电压模式还是峰值电流模式:DCM 下控制方案选择

STM32 控制 Buck 的常见做法是电压模式:固定频率 PWM,ADC 只采样输出电压,PID 输出直接改占空比。优点是接线少、调起来直观,F103 用一个 ADC 通道就够用;缺点是它对输入电压突变的响应慢,负载阶跃时电压过冲要靠输出电容撑住。

另一种是峰值电流模式。做法是在开关管电流路径上串联小采样电阻,用比较器把采样电压和 PID 输出的电流基准做比较,电感电流达到基准时关断开关。这一方案在 DCM 下特别自然,因为每次开关都是从零电流开始上升,峰值基准和平均输出电流有确定的比例关系,响应速度远快于电压模式。

用 STM32 做峰值电流模式有两条路。G431 这类芯片内部自带模拟比较器和 DAC,比较器输出可以直接硬件封锁 PWM,不需要软件干预。F103 没有内部比较器,得外接一颗 LM393 或者用运放搭迟滞比较器,门限由 DAC 或 PWM 滤波给出。毕设里如果时间紧,先把电压模式跑通、把波形测完整拿到底分,再把峰值电流模式作为加分项加进去。

2.3 DCM 小信号的特性和数字 PID 的调整侧重点

DCM Buck 的功率级传递函数近似为单个极点加一个很远的零点,不像 CCM 在占空比大时有右半平面零点,环路补偿更简单。但 DCM 有一个副作用:功率级增益会随负载变化。PWM 占空比到输出的等效增益近似正比于 sqrt(R),轻载时增益明显高于重载。固定一组 PID 参数在 0.3A 时调稳,拿到 0.8A 时环路带宽可能翻倍,容易在负载阶跃时振荡。

数字实现上,控制频率取开关频率的 1/10 到 1/2 都可用。100kHz 开关频率下让 PID 每 8 到 16 个周期执行一次,也就是 6.25kHz 到 12.5kHz。采样点必须和 PWM 同步,否则输出电压纹波混叠进 ADC,PID 会不停抖。第 4 章给的例子就是让定时器触发 ADC 采样,从源头消除这个问题。

提示:DCM 轻载时电感电流有一段时间是零,输出电压完全靠输出电容维持。这段时间采样的电压接近真实稳态值,适合做闭环反馈;开关导通期间采样则会看到明显的纹波台阶。

3. 功率级与采样电路:STM32 周边的硬件参数计算

3.1 电感、输出电容与开关器件的选型

按第 2 章的结果,设计目标是 12V 输入、5V/0.8A 输出、fs=100kHz、L=15uH,让 Buck 在满载点附近仍维持 DCM。电感选型还要看饱和电流:DCM 的电感电流峰值是平均电流的两倍以上,按 0.8A 平均、峰值约 2A 算,电感饱和电流至少取 3A。

输出电容要同时满足电压纹波和负载阶跃两项要求。DCM 下一部分能量直接来自电感,另一部分由电容补充。粗算纹波用 ΔVout≈ΔIL*ESR,电解电容 ESR 若在 50mΩ 量级,2A 的纹波电流会产生 100mV 纹波。常见做法是电解电容并联陶瓷电容:470uF 电解负责容量,22uF X7R 陶瓷负责压低高频 ESR 纹波,整体纹波可以做到 30mV 以内。

开关管选 N-MOSFET,因为 Buck 的高端开关需要栅极电压高于输入电压。30V/30mΩ 的管子在这个功率下足够,AO3400(30V、约 32mΩ、SOT-23)便宜好买,驱动电流要求不高。续流管用肖特基二极管,SS54(5A/40V)的正向压降只有 0.4V 左右,反向恢复电荷几乎为零,对 DCM 的高频续流比较友好。

3.2 MOSFET 驱动电路:自举电容怎么取值

高端 N-MOS 的门极电压必须比源极(SW 节点)高 10V 左右。STM32 的 GPIO 无法直接驱动,必须用驱动芯片。常见方案是 IR2104S 加自举电容:驱动芯片内部有一个高压悬浮电源轨,靠自举电容给高端栅极驱动供电。

IR2104S 的接法是:输入口接 STM32 的 PWM,只用高侧输出,VB 与 VS 之间接自举电容。电容取值与开关频率和栅极电荷有关,习惯取 1uF 陶瓷电容,配一个快恢复二极管到 12V。计算依据是 C_boot > Qg_total / ΔV_boot,ΔV_boot 取实测 VB-VS 纹波的允许值,一般是 0.5V 以内。

#define QG_N 12e-9f // AO3400 栅极电荷,单位 C #define DV_BOOT 0.5f // VB-VS 允许压降,单位 V float cboot_min = QG_N / DV_BOOT; // 理论值 24nF float cboot_pick = cboot_min * 20.0f; // 实际取 0.47uF~1uF

AO3400 的 Qg 只有 12nC 左右,0.1uF 理论上都够,但实际充电路径上还有驱动芯片内部损耗和 PCB 寄生,建议至少 0.47uF 以上。驱动部分的典型参数如下:

器件参数说明
U1IR2104S半桥驱动,只用高侧
C_boot1uF/25V X7R自举电容,靠近 VB-VS
D_bootUS1M / SS14自举二极管,耐压大于 20V
R_g4.7Ω栅极串联电阻,限制 di/dt
C_in100uF+10uF输入母线电容,靠近功率级

R_g 的作用是限制 MOSFET 开关瞬态的栅极电流,防止振铃。R_g 取太大开关损耗上升,取太小栅极出现振铃,4.7Ω 到 10Ω 是粗调起点。

3.3 输出电压采样和电流采样链路

输出电压采样用电阻分压降到 STM32 ADC 的 3.3V 以内。5V 输出分压到 2.0V 比较合理,R1=27kΩ、R2=18kΩ,分压比 0.4。分压电阻后加一个 RC 滤波,1kΩ+10nF 是比较常见的组合,截止频率约 16kHz,能滤掉开关纹波而不会影响 12.5kHz 以下的有效控制信号。

电流采样看是否做峰值电流控制。F103 方案里用 50mΩ 采样电阻串联在高端 MOSFET 源极到输入地之间,用 INA240 这类仪表放大器放大 20 倍后接到 ADC。这个电阻的功率按 RMS 值计算,0.8A 负载 DCM 下 RMS 电流约 0.7A,50mΩ 上损耗约 24mW,0805 封装的 0.25W 电阻完全够用。

注意:采样电阻放在高端源极会导致 ADC 参考地与功率地之间有共模噪声。PCB 要单点接地,功率地铺完整铜皮,STM32 的模拟地单独走线回到输入电容负端,避免 ADC 数值在开关瞬间被拉偏。

3.4 STM32 最小系统和开发环境

F103C8T6 用 8MHz 晶振通过 PLL 倍频到 72MHz。8MHz 晶振配套的负载电容不是随手拿 22pF,需要按晶振手册的 CL 值和 PCB 寄生电容反推,一般取 10pF 到 22pF。开发环境方面,Keil5 要先装 STM32F1 器件支持包,或者用 STM32CubeMX 生成工程再导入 Keil5。很多同学卡在芯片包安装这一步,表现为 Keil 里找不到 STM32F103C8,先装好 DFP 再打开工程,就不会出这个问题。

如果后面想换成 G431 做更高开关频率,CubeMX 里改型号重新生成工程,定时器和 ADC 配置基本能平移过去,只有高级定时器的死区寄存器略有差异。国内常见的 APM32F103、GD32F103 在多数框架下可以直接用 STM32 源码,CubeMX 生成的工程也通常能跑。但不要一上来就怀疑型号兼容性,用 ST-LINK Utility 读一次芯片 ID 确认实际型号再开始排查。

4. STM32 固件实现:PWM、ADC 与 PID 闭环代码

4.1 用高级定时器产生 100kHz PWM 并配置死区

F103 的 TIM1 是高级定时器,输出带互补通道和死区控制,用来驱动 IR2104S 很合适。这里只用 CH1 输出,互补通道关闭。PWM 频率由定时器时钟和 ARR 决定:72MHz/(ARR+1)=100kHz,所以 ARR=719。脉冲宽度 CCR 初始值设 270,对应占空比约 37.5%。

// TIM1_CH1 初始化,100kHz PWM TIM_HandleTypeDef htim1; htim1.Instance = TIM1; htim1.Init.Prescaler = 0; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 719; // 72MHz/(719+1)=100kHz htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; HAL_TIM_PWM_Init(&htim1); TIM_OC_InitTypeDef sConfigOC = {0}; sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 270; // 初始占空比 270/720=37.5% sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);

PWM1 模式的意思是计数器计数到 CCR 之前输出有效电平,到 ARR 重新开始。占空比=(CCR+1)/(ARR+1),CCR=719 时是 100% 占空比。控制里随时改 CCR1 就能实时调压。要注意 F103 高级定时器必须先 HAL_TIM_PWM_Start 一次,否则 CH1 引脚一直是低电平。如果后面要用死区,配置sConfigOC.OCIdleState和死区寄存器TIM1->BDTR,IR2104S 直接用单端 PWM 不需要死区。

表 2 TIM1 PWM 关键参数

参数说明
ARR719PWM 周期计数
Prescaler072MHz 不分频
CCR 初值27037.5% 软启动起点
PID 调用周期10 个 PWM 周期10kHz 控制频率

4.2 ADC 同步采样:用 TIM1 触发加 DMA 搬运

电压采样如果在主循环里随意读,与 PWM 不同步,100kHz 的开关纹波会混进 ADC 结果,PID 看到的是一路乱跳的数字。正确做法是用定时器触发 ADC 采样,让 ADC 在每个 PWM 周期同一时刻启动转换。

ADC_HandleTypeDef hadc1; hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1TRGO; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; HAL_ADC_Init(&hadc1); DMA_HandleTypeDef hdma_adc1; hdma_adc1.Instance = DMA1_Channel1; hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_adc1.Init.Mode = DMA_CIRCULAR; HAL_DMA_Init(&hdma_adc1); __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1); HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buffer, 32);

DMA 开循环模式,32 个样本不断写入缓冲区。PID 每次取平均,能进一步滤掉高频噪声,并对 ADC 偶发的坏点有平滑作用。ExternalTrigConvEdge 必须设为上升沿,采样时刻落在 TIM1 计数器溢出瞬间,正好是开关周期的起点。此时电感电流刚从零开始上升,输出电压接近纹波谷值,数值稳定,适合作为反馈。CubeMX 里还需要把 TIM1 的 Master-Slave Mode 配置为 Update 事件触发 TRGO,否则 ADC 外部触发不会生效。

4.3 PID 控制器代码与占空比限幅

DCM 下最容易写对的是位置式 PID,输出直接代表占空比目标,范围限制在 2% 到 90%,留出余量给输入电压跌落等瞬态工况。积分项必须加抗饱和,否则从空载切入负载时积分会冲到很大,输出电压恢复过程出现明显超调。

typedef struct { 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 ref, float fb) { float err = ref - fb; float out; pid->integral += err; // 抗积分饱和:积分限幅跟随输出限幅 if (pid->integral > pid->out_max) pid->integral = pid->out_max; if (pid->integral < -pid->out_max) pid->integral = -pid->out_max; out = pid->kp * err + pid->ki * pid->integral + pid->kd * (err - pid->last_error); pid->last_error = err; if (out > pid->out_max) out = pid->out_max; if (out < pid->out_min) out = pid->out_min; return out; }

PID 在另一个定时器中断里每 10 个 PWM 周期调用一次,控制频率=100kHz/10=10kHz。返回值换算成 CCR:ccr = (uint16_t)(duty * 720.0f),duty 范围 0.02 到 0.90。

初值给 Kp=0.5、Ki=0.02、Kd=0。Kp 的单位是“占空比比例/伏误差”,0.5 意味着 200mV 误差会拉出 10% 占空比变化。DCM 轻载增益高,Kp 超过 1.0 后容易出现电感电流嘶嘶声;用示波器看到高频抖动就降 Kp。Ki 从零往上加,每加一次做一次负载阶跃测试。Kd 在 ADC 有滤波之后才起作用,否则只是放大噪声。

4.4 过压保护和软启动逻辑

固件里除了 PID,还要写两个通用保护。输出电压高于 5.5V 或低于 3.8V 时立即关断 PWM;ADC 采到的电流超过 1.2A 时锁存关断,手动复位才恢复。关断用 HAL_TIM_PWM_Stop,但要加保护标志位,防止主循环又把 PWM 启动回来。

软启动的思路是让占空比从 10% 以固定斜率上升,直到 PID 输出去接管。上升斜率按每 10 毫秒加 1%,从 0 到目标约 300ms 到 500ms。这个阶段绕过 PID,在主流程里直接小步长更新 CCR,避免上电瞬间冲击电流,同时给自举电容充电留出时间。

提示:不要在控制循环里调用 HAL_Delay。F103 的中断和 DMA 在持续工作,一旦卡在阻塞延时里,ADC 缓冲区停更,PID 会以为电压不变,积分项可能被慢慢推上去。这个现象和常见的“延时函数卡死”是一个原因,根因在中断优先级配置或头文件里中断打开顺序不对。控制循环保持短、快、无阻塞。

5. 用示波器验证 DCM 边界和闭环稳定性:几个实操技巧

5.1 用开关节点波形确认 DCM 边界

没有电流探头,看 SW 节点电压也能判断。CCM 下 MOSFET 关断期间,SW 被续流二极管钳在约 -0.3V,持续到下次开通;DCM 下电感电流到零后,SW 电位会浮起来,被输出电容通过电感拉到 Vo 附近。波形上出现一段等于输出电压的平台,就说明确实断续了。把负载往上调,平台逐渐缩短直到消失,那个点对应实际边界电流,可以和公式算出的约 0.97A 对照。

5.2 负载阶跃调 PID,只看两个指标

用电子负载在 0.2A 和 0.8A 之间按 100Hz 跳变,记录电压过冲和恢复时间。过冲大加 Kp;出现 20kHz 以上振铃先清零 Kd;电压缓慢爬升后超过稳定值再回落是 Ki 过大;几十赫兹低频抖动则是采样与 PID 执行不同步。每次只改一个参数,记录结果,毕业答辩直接拿这张表讲:

KpKiKd过冲 mV恢复 ms现象
0.5002202.8无振荡
0.8001501.9轻微高频噪声
0.80.0201601.2稳定
1.00.050.52804.5振铃,不可用

5.3 三个容易翻车的量测细节

第一,探头地线接太远会量出一堆假振铃,用弹簧地线直接搭在功率地铜皮上再看 SW 波形。第二,输出纹波要开 20MHz 带宽限制,关掉时看到的高频毛刺不是 DC-DC 的真实纹波,会误导你给输出电容加过大的容量。第三,自举电容太小会导致上电后 PWM 停发,表现为输出电压上不来或掉电重启;把 VB-VS 电压接到示波器,如果低于 8V,加大自举电容到 1uF 以上再验证。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询