嵌入式C语言实现无操作系统的同步Buck控制:数字电源PID闭环代码从零写起
无OS跑同步Buck,核心不是“写个PID函数”,而是把控制环钉在PWM周期里:每个开关周期采一次输出、算一次增量、更新一次占空比,全程不动态分配、不调库、不进延时。裸机数字电源的稳定,靠的是时序确定性和输出限幅,不是算法多复杂。
一、硬件前提:互补PWM必须带死区
同步Buck高边和低边MOS是互补导通,两管同时导通=母线直接短路炸管。高级定时器(TIM1/HRTIM)配互补通道+死区插入:
#define PWM_PERIOD 1000 // 100kHz,计数周期
#define DEAD_TIME 20 // 死区计数,按驱动延迟实测
#define MAX_DUTY 920 // 92%,留余量
#define MIN_DUTY 40 // 4%,保证干净开通
死区时间要用示波器量HS关→LS开、LS关→HS开的间隔,别只看寄存器配置。驱动IC有传播延迟,软件死区必须覆盖它。
二、控制环结构:增量式PI
电压单环先用PI,D项在电源里容易放大采样噪声,初期设0。
typedef struct {
float kp, ki;
float integ;
float err_prev;
float out; // 当前占空比(计数单位)
float out_min, out_max;
} pi_t;
pi_t volt_pi = {
.kp=0.15f, .ki=0.02f,
.integ=0, .err_prev=0,
.out=200, .out_min=MIN_DUTY, .out_max=MAX_DUTY
};
增量式写法,输出是占空比增量,天然抗突变:
float pi_update(pi_t*p, float ref, float fb, float dt){
float err = ref - fb;
/* 积分累加,先抗饱和 */
p->integ += p->ki * err * dt;
if(p->integ > p->out_max) p->integ = p->out_max;
if(p->integ < p->out_min) p->integ = p->out_min;
float pterm = p->kp * (err - p->err_prev);
float iterm = p->ki * err * dt;
p->err_prev = err;
p->out += pterm + iterm; // 增量叠加
if(p->out > p->out_max) p->out = p->out_max;
if(p->out < p->out_min) p->out = p->out_min;
return p->out;
}
关键点:积分项在累加时就限幅,输出触顶不再继续累加同向误差,避免“过冲后半天回不来”。
三、ADC采样时机
别在main里随便采。用PWM更新事件触发ADC,采样点落在低边导通中期(输出纹波较稳的位置):
volatile uint16_t adc_raw = 0;
volatile uint8_t adc_ready = 0;
void ADC_IRQHandler(void){
adc_raw = ADC_READ_VOUT();
adc_ready = 1;
ADC_CLEAR_EOC();
}
无OS时,主循环轮询adc_ready,或者把控制逻辑直接放进PWM更新中断(周期10μs量级,代码必须短)。
四、主控制节拍(裸机轮询版)
void power_loop(void){
static float filt = 0;
if(!adc_ready) return;
adc_ready = 0;
/* 一阶滤波,抑制采样毛刺 */
float vout = adc_raw * ADC_V_SCALE;
filt += 0.3f*(vout - filt);
/* 保护优先于闭环 */
if(filt > VOUT_OVP) { pwm_force_off(); fault_set(FAULT_OVP); return; }
if(adc_current() > I_OCP){ pwm_force_off(); fault_set(FAULT_OCP); return; }
/* 软启动:参考电压斜坡 */
static float vref_ramp = 0;
if(vref_ramp < V_REF_TARGET)
vref_ramp += SOFTSTART_STEP;
float duty = pi_update(&volt_pi, vref_ramp, filt, CTRL_DT);
pwm_set_compare((uint16_t)duty);
}
软启动必须爬坡给参考值,不能直接把12V设进去——否则上电瞬间占空比打满,电感电流冲击很大。
五、DCM下同步管处理
轻载时电感电流会到零,低边MOS继续导通反而把能量倒灌回去。工程做法:
检测电感电流过零
过零后关低边管,切异步整流(体二极管续流)
或直接在轻载区关闭同步管驱动
if(inductor_current() <= 0 && load_light())
ls_mos_disable();
else
ls_mos_enable();
这一步不做,轻载效率会明显变差,还可能振荡。
六、写CCR的时机
占空比更新必须在PWM周期边界、更新事件后写,避免中途改比较值导致半高半低脉冲。高级定时器用Preload+Update事件装载:
pwm_set_compare(duty); // 实际写CCRx,等下一个UEV生效
别在HS导通期间改CCR,否则可能出现一个异常窄脉冲。
七、落坑清单
互补PWM没配死区→上下管直通炸管
位置式PID不抗饱和→过冲后慢恢复
采样点在开关边沿→读到尖峰
上电直接给满参考→冲击电流
动态malloc/printf进中断→时序崩
过压保护只告警不关PWM→输出冲高
轻载不关同步管→倒灌振荡
CCR中途更新→窄脉冲打管
积分项不限位→空载切负载超调
没留最小/最大占空比→占空比跑到0或100%
八、落地顺序
先配互补PWM+死区,示波器量上下管波形
开ADC触发采样,固定采样窗口
写增量PI,积分限幅+输出限幅
主循环轮询adc_ready,算占空比
加软启动斜坡和过压/过流硬保护
轻载切异步整流
先空载后带载,逐步调Kp/Ki
阶跃负载看超调,再微调微分
无OS数字电源的本质是“时序即控制”:控制环不是被调度出来的,是被PWM周期逼出来的。PID只是其中一小段,真正决定稳不稳的是——死区防直通、采样窗口固定、积分抗饱和、占空比硬限幅、保护优先于闭环。这几条守住,同步Buck裸机跑起来比带RTOS还稳;守不住,算法再漂亮也是先炸管再谈精度。
接下来你想优先完善哪一部分:参数整定、双环控制还是保护恢复?





