
简介一份基于STM32F103C8T6微控制器的数字电源设计资料面向具备嵌入式基础的中级开发者、电子专业学生及电源研发工程师。资料系统讲解电压电流可调型稳流稳压电源的实现方案采用同步Buck降压拓扑支持0-30V电压、0-5A电流范围具备恒压、恒流、恒功率三种工作模式并通过PID双闭环控制提升输出精度。文档从硬件与软件两条主线展开涵盖采样调理电路、OLED显示、旋转编码器交互、多重保护机制以及ADC采样、PID计算、PWM占空比调节、UART串口监控等关键环节还给出了核心头文件中的参数定义和保护阈值便于读者快速搭建原型并调试。压缩包内为1个PDF文档共378KB信息密度高适合实验室改造线性电源、开关电源教学及数字电源产品预研。该文档已有227人浏览学习内容完整具备实际参考价值。1. 一台数字电源难点不在“数字”而在“稳”基于 STM32 的 PID 控制数字电源标题里的“电压电流可调型稳流稳压系统”听起来像是给毕设贴标签但实际做起来最难的不是让 STM32 跑起来而是让输出电压和输出电流在负载变化时都能“稳得住”。模拟电源用运放加基准搭环路改一个参数要换电阻电容数字电源把这些全部搬进单片机ADC 采集电压电流、PID 算占空比、高级定时器输出 PWM。好处是参数改一行代码就能生效还能实时监控和记录波形。这篇文章适合手里有一块 STM32 开发板、想自己做一台可调电源的嵌入式或电力电子方向工程师。我会按硬件架构、PID 离散化、电压电流双环控制、调参验证这条线往下走所有代码都是可以在常见开发环境里直接改用的工程写法。2. 数字电源的硬件架构与外设分配先把 Buck 主回路和采样链路定下来2.1 为什么选 Buck 拓扑做可调电源标题没有限定功率变换器类型但“电压电流可调、输入输出共地”这类要求下最常见、最稳妥的开始方案是 Buck 降压变换器。比如输入用 24V 开关电源输出做到 0~20V / 0~3A这覆盖了大部分实验台和调试场景。Buck 的传递函数相对简单工作在连续导通模式CCM时是小信号模型最成熟的拓扑PID 的调试过程不会被右半平面零点这类问题干扰。想扩展成升降压可以在 Buck 基础上改成四开关 Buck-Boost但那是后话先把 Buck 跑稳再去碰双向和升降压。同步整流是值得一开始就做的决定。用两颗 MOSFET 代替二极管续流低压大电流下效率能提升 5~8 个百分点代价是 PWM 必须有互补输出和死区这正好是 STM32 高级定时器的强项。我的建议是高边管用 TIM1_CH1低边管用 TIM1_CH1N硬件上留死区电阻位软件里用 TIM1 的刹车和死区寄存器统一配置。2.2 STM32 选型和引脚分配的几个关键约束不是所有 STM32 型号都适合做电源控制。F103 系列虽然资料多但 ADC 采样率和高级定时器配置都偏基础如果想让电流环跑到 20kHz 以上建议选带 FPU 的 M4 内核型号比如 STM32F302 或 STM32F401。浮点运算单元在 PID 计算里能省掉大量软浮点开销同样的 72MHz 主频带 FPU 的型号可以多跑一倍的控制频率。下面是一份工程上常用的外设分配表引脚以实际开发板丝印为准功能STM32 外设关键引脚说明半桥 PWM 输出TIM1_CH1 / CH1NPA8 / PB13带死区的互补输出电感电流采样ADC1_IN0PA0由 TIM1_TRGO 触发采样输出电压采样ADC1_IN1PA1电阻分压后直接进 ADC输出电压设定ADC1_IN2PA210k 电位器分压输出电流设定ADC1_IN3PA3同上两个旋钮独立调节I2C OLED 显示I2C1PB6 / PB7显示当前电压电流设定值串口调试USART1PA9 / PA10用于 PID 参数在线调整这里有一个新手容易踩的坑所有 ADC 通道共用一个采样保持电容切换通道需要稳定的采样时间。电源控制里电压和电流必须“同一时刻”被采集最稳妥的办法是用双 ADC 同步模式或者把两个采样通道之间的间隔控制在一个固定节拍内。我习惯把 ADC 采样触发源设为 TIM1 的更新事件这样每次 PWM 周期开始ADC 自动启动转换CPU 不需要额外代码去对齐采样时刻。2.3 采样电路精度分压电阻、电流检测放大器和采样时刻电压采样没什么玄学输出 0~20V 经过 100k 和 10k 电阻分压后变成 0~1.82V直接进 ADC 参考电压 3.3V留出 40% 余量给过压保护判断。关键点是分压电阻要用 0.1% 精度的金属膜电阻两个电阻的温度系数尽量一致否则输出电压显示值和实测值会随着温度漂移。电流采样才是决定系统性能的地方。常见做法是输出端串联一个 10mΩ 的合金采样电阻再用差分放大器放大。INA240 这类电流检测放大器有固定的 50 倍或 100 倍增益3A 满量程在 10mΩ 上产生 30mV 压降放大 50 倍后是 1.5V正好适配 ADC 输入范围。如果实验室没有专用芯片用 LM358 搭差分放大也能工作但要注意共模电压范围和带宽普通运放在 100kHz 开关频率附近增益已经衰减电流环带宽会被硬件限制住。采样时刻对电流环的影响容易被低估。电感电流在开关管导通时上升、关断时下降如果在 PWM 周期的任意时刻触发 ADC采到的电流值会有很大的周期纹波分量PID 会因为反馈值抖动而出现“假振荡”。正确做法是把采样点放在 PWM 周期中点此时电感电流正好等于负载电流平均值。在 STM32 上利用 TIM1_TRGO 触发 ADC 注入组转换并把触发时刻配置在计数周期的 50% 位置就能拿到干净的平均电流。这个细节直接决定了后面 PID 的调试难度值得在画 PCB 之前就确定下来。3. PID 的离散化和定时器中断实现位置式还是增量式先算清楚再写代码3.1 位置式 PID 与增量式 PID 的选型对比数字电源里的 PID 作用对象是占空比输出范围天然受限0~100%。理论上位置式 PID 和增量式 PID 都能用但在电源这个场景里增量式算法有很实际的优势增量式 PID 输出的是占空比的增量每次计算完直接加到当前占空比上天然带有积分特性不会出现位置式算法里积分项持续累加导致的大幅超调。位置式 PID 则需要额外做积分限幅、输出限幅、积分清零逻辑代码量多出一倍不止。对比项位置式 PID增量式 PID输出语义直接输出占空比输出占空比变化量积分饱和风险高需要单独处理低输出限幅即控制量饱和上电初始化需要设定初始占空比从 0 开始无跳变微分噪声对误差直接微分噪声较大对误差增量微分幅度相当典型应用温度、流量等慢系统电机、数字电源等快系统电机控制里增量式 PID 占绝对主流因为它天然适合输出 PWM 占空比。电源控制也可以沿用同样的思路在定时器中断里计算一个delta_duty加到全局变量current_duty上再做一次边界检查就行。如果你希望代码更接近电力电子教材里的控制框图表达位置式加条件积分也可以但要花更多心思在抗饱和逻辑上。3.2 STM32 定时器中断里的 PID 离散化代码增量式 PID 的离散化公式是从差分方程推出来的当前误差减去上一次误差得到比例增量误差本身做积分增量两次误差差分做微分增量。代码里保留prev_error变量即可不需要维护积分项这是增量式在代码层面的最大优点。typedef struct { float Kp; float Ki; float Kd; float Ts; // 采样周期单位秒 float prev_error; // 上一次误差用于增量计算 float output; // 当前占空比输出范围 0.0f ~ 1.0f } IncPID; void IncPID_Init(IncPID *pid, float kp, float ki, float kd, float ts, float init_duty) { pid-Kp kp; pid-Ki ki; pid-Kd kd; pid-Ts ts; pid-prev_error 0.0f; pid-output init_duty; // 软启动初始占空比建议 0.05f } float IncPID_Update(IncPID *pid, float setpoint, float feedback) { float error setpoint - feedback; float delta_error error - pid-prev_error; // 增量式PIDdelta Kp*(e_k - e_k1) Ki*Ts*e_k Kd/Ts*(e_k - 2*e_k1 e_k2) // 这里用当前误差和前一次误差做差分省去二阶项实际多数场合足够 float delta pid-Kp * delta_error pid-Ki * pid-Ts * error; // 微分项在数字电源里一般不启用见3.3 pid-output delta; if (pid-output 0.95f) pid-output 0.95f; if (pid-output 0.0f) pid-output 0.0f; pid-prev_error error; return pid-output; }这段代码的Ts是 PID 采样周期而不是 PWM 周期。比如 PWM 跑 100kHz电流环每个 PWM 周期算一次Ts 0.00001电压环可以把计算频率降到 10kHzTs 0.0001。Kp的量纲对应占空比/误差如果把误差以伏特计、输出以 0 到 1 的占空比计Kp 的物理意义就是“每 1V 误差产生多大的占空比变化”。Ki * Ts合并在一起相当于离散积分系数所以在调参时你调整的是Ki和Ts的乘积改采样频率会影响积分效果这个要心里有数。代码里的初始化占空比设为 0.05是给 Buck 一个最小的软启动起点。如果从 0 占空比开始然后 PID 输出直接跳到 0.3输出电容的浪涌电流会非常大甚至触发硬件过流保护。后面第 4.4 节还会专门讲软启动。3.3 积分饱和与微分噪声的实际处理手法增量式 PID 天然缓解了积分饱和但并没有消除它。当输出被限制在 0.95 时如果误差仍然为正output会持续贴在饱和值一旦误差反向需要好几个周期才能从饱和区退回来。解决这个问题有两个惯用手法一是把输出值直接当作积分状态的估计饱和时冻结增量计算二是在增量计算前对误差做条件判断只在误差符号与当前 PID 输出方向一致时累加。微分项D在数字电源里通常不建议一开始就启用。电流采样信号即使做了中点采样依然有高频开关噪声残留误差的差分运算会把这种噪声放大几十倍导致占空比抖动。如果你确实需要在电压环里加微分来提升相位裕度正确的做法是先对误差做一阶低通滤波再计算差分// 对误差做滤波后再算微分 filtered_error (error - filtered_error) * 0.2f; // 简单一阶低通系数0.2 float derivative (filtered_error - pid-prev_filtered_error) / pid-Ts; pid-prev_filtered_error filtered_error;这个低通系数 0.2 意味着滤波截止频率约为采样频率的两成采样频率越高对真实高频分量的保留越好。实际项目里电流环我通常只用 PI电压环在输出电容较大时才加少量微分这个选择可以放心写进设计文档里。4. 电压电流可调与稳流稳压双环级联和 CC/CV 模式切换4.1 电流内环、电压外环的级联 PID 控制结构电压电流可调意味着系统需要在恒压CV和恒流CC两种模式之间切换而且不能出现切换时的振荡。工程上的标准做法是级联双环电压环在外面电流环在里面。电压环的输入是电压设定值和输出电压反馈输出是一个电流指令电流环以这个电流指令为给定以电感电流为反馈输出占空比。这个内外环结构有一个非常重要的物理意义电流环负责快速响应电压环负责稳态精度。电感电流是状态变量直接受占空比控制带宽可以做高输出电压是电容积分的结果天然比电流慢一拍。把电流环作为内环相当于先控制住电流再让电压慢慢跟上线性电源里常说的“电压源”就是这么实现的。// 双环级联示意中断里按“电压环 → 电流环 → 限幅 → 更新PWM”顺序执行 float voltage_loop_out PI_Voltage_Update(v_pid, v_setpoint, v_feedback); float i_reference voltage_loop_out; // 电压环输出作为电流给定 // 恒流限制电压环输出如果超过电流设定将电流给定限幅到设定值 if (i_reference i_setpoint) { i_reference i_setpoint; } float duty PI_Current_Update(i_pid, i_reference, i_feedback); TIM1-CCR1 (uint32_t)(duty * (float)TIM1_PERIOD);代码执行顺序不能反过来。如果先算电流环再算电压环电压环的响应会滞后一个控制周期系统的相位裕度变差。级联 PID 控制里内环永远先执行、外环后执行这和你用手拧电位器设定电压是一个道理先决定输出电流能到多大再决定电压能不能继续升。4.2 恒压恒流自动切换的最小实现上面代码里的if (i_reference i_setpoint)看起来简单实际已经包含了 CV/CC 切换的核心思想。当输出电压低于设定值时电压环输出会一直增大直到被电流设定值限幅挡住系统进入恒流模式当负载电阻足够大、电流需求低于设定值时电流环给定由电压环主导系统工作于恒压模式。不过这个限幅有一个隐患电压环的积分项在限幅期间继续累加导致电压环饱和。退出恒流模式后电压环需要几十个周期才能从饱和状态恢复表现为输出电压过冲。解决办法是给电压环增加条件积分功能只有当电压环输出未进入限幅区时才允许积分累加。// 条件积分电压环输出饱和时冻结积分 if (voltage_loop_out i_setpoint) { v_pid.integral v_pid.Ki * v_error; } else { // 保持原积分值不变等退出恒流后再恢复调节 }实际调试中上述切换逻辑需要一个几十毫伏的死区否则当负载恰好让电压和电流同时接近设定值时系统会在 CV/CC 之间反复跳变。最简单的死区实现是给电压环设定值做一个滞回窗口输出从下方接近设定值时以设定值为准从上方回落时减掉 20mV 作为切换阈值。4.3 设定值的人机交互电位器、按键与串口指令标题里“电压电流可调”最直接的实现是两颗电位器。STM32 的 ADC 读取电位器分压值映射到电压和电流的满量程范围。这样不需要额外电路旋钮拧起来手感也直观。电位器方案的缺点是精度受 ADC 位数和电位器自身噪声限制12 位 ADC 在 20V 满量程下理论分辨率约 5mV但实际抖动往往在 20mV 左右。如果你想让设定值可以精确输入串口指令是比按键更合适的方式。下面是一个基本的指令解析逻辑通过printf下发VSET:12.50这样的字符串void Parse_SetCommand(uint8_t *buf, uint16_t len, float *vset, float *iset) { // 期望格式: VSET:12.50 或 ISET:1.250 if (strncmp(buf, VSET:, 5) 0) { *vset strtof((char *)buf 5, NULL); if (*vset 20.0f) *vset 20.0f; } else if (strncmp(buf, ISET:, 5) 0) { *iset strtof((char *)buf 5, NULL); if (*iset 3.0f) *iset 3.0f; } }这个解析函数应该跑在主循环或低优先级任务里不要放进定时器中断。高位优先级的控制中断里只读vset和iset这两个全局变量保证控制周期不受通信阻塞影响。修改设定值时”目标值“不要直接覆盖而是把新值存到影子变量电压环的给定值从当前值以固定斜率爬向目标值这就是下一节要讲的软启动。4.4 软启动与占空比限制的工程实现数字电源的软启动本质上是一个斜坡发生器。设定值从 0 开始以每毫秒一个固定电压增量的速率爬升直到逼近最终目标。这个斜坡不仅覆盖上电过程也覆盖运行中修改电压设定的过程否则电压从 5V 跳到 20V 的瞬间输出电容会像短路一样吸收大电流。// 设定值斜坡rate表示每毫秒变化量单位为伏特/毫秒 #define VOLTAGE_RAMP_RATE 0.02f // 20V 需要1秒爬完 void Ramp_Voltage_Setpoint(float target_v, float *current_v) { if (*current_v target_v) { *current_v VOLTAGE_RAMP_RATE * 1.0f; // 1ms执行一次 if (*current_v target_v) *current_v target_v; } else if (*current_v target_v) { *current_v - VOLTAGE_RAMP_RATE * 1.0f; if (*current_v target_v) *current_v target_v; } }占空比限制和软启动要配合使用。启动阶段即使 PID 输出允许到 95%也应该在硬件级别把最大占空比限制在 50% 以下等输出电容充电到设定值附近再放开限制。STM32 的高级定时器可以用 TIM1 的重复计数器和比较值做主从逻辑但我更推荐在软件里限制 CCR1 写入值逻辑更直观且方便调试。电流采样在 DCM断续导通模式下的值会失真占空比限制可以顺带保证系统大部分时间工作在 CCM。5. 数字电源 PID 调参实践从开环到闭环的三步走5.1 先用固定占空比验证硬件再让 PID 介入PID 参数不是拿仿真软件算出来就能直接用的。我的调试顺序是先让系统工作在开环状态手动向 CCR1 写入一个中等占空比比如 0.4观察输出电压是否稳定、纹波是否在预期范围、电流采样是否干净。这一步能验证硬件链路的所有环节包括 ADC 采样、PWM 死区、供电和接地。如果开环输出电压就有低频波动说明采样电路或供电有问题此时调 PID 纯属浪费时间。开环正常后恢复闭环并让 PID 的积分初始值为 0从纯比例开始调。电容负载下的纯比例控制会产生稳态误差这是正常的用这个误差幅度可以直观判断系统的开环增益。逐步加大 Kp直到输出电压出现持续等幅振荡——此时记录 Kp 值取它的 40%~60% 作为工作参数。这一步等效于在真实硬件上做一次临界比例度实验。5.2 三个必调参数和一组能上手的初始值数字电源的采样频率和 PWM 频率直接决定参数数量级。下表给出一组针对 24V 输入、20V/3A 输出、PWM 100kHz 的参考初始值参数含义电流环参考值电压环参考值Kp比例系数0.80.3Ki积分系数200.050.0Kd微分系数0.0默认关闭2.0可选需滤波采样频率控制周期100kHz10kHz输出限幅占空比上下限0~0.95给定给电流环这些参数有一个重要的前提误差用实际物理量伏特、安培输出用归一化的占空比。Kp0.8 表示 1A 电流误差会产生 0.8 的占空比增量这个数值听起来很大但因为输出会被限幅到 0.95实际的动态范围是够的。如果系统从 0 到满负载阶跃Kp 太大可能出现振荡那就先加到 0.3让阻尼更充裕。参数调优最快的手段是用串口打印关键中间量。把设定值、反馈值、PID 输出、占空比 4 个变量按printf(v %.2f i %.2f d %.2f\n, ...)的格式输出采样率如果不是 100kHz 级别打印本身不影响控制但能直观看到环路在不同 Kp 下是收敛还是发散。也可以用 STM32 的 DAC 引脚输出 PID 计算后的占空比信号用示波器直接看控制器的输出波形相比串口有更快的时域分辨率。5.3 验证“稳流稳压”和“电压电流可调”的最终手段负载阶跃测试是验证稳流稳压系统最有效的方法。接一个直流电子负载设为恒流模式先输出 0.2A然后瞬间切换到 3A用示波器观察输出电压的瞬态跌落和恢复时间。一个好的电压环应该在 1ms 内恢复到设定值的 0.1% 误差带内恢复过程可以有一个小过冲但不能出现振荡。如果恢复时间过长优先加大电压环 Kp如果过冲明显减小电压环 Ki 或加入微分项。电压电流可调验证相对简单两个电位器都转到 50%确认显示的电压和电流设定值正确然后固定电流设定为 1A旋转电压旋钮输出电压应平稳爬升到负载允许的最大值如果负载电阻太小输出电压会被恒流限制住此时系统应该自动进入 CC 模式而不是硬拉着输出电压到设定值。最后检查的一个坑是 ADC 参考电压。只有确认 VREF 接的是独立基准源而非 MCU 供电电压才谈得上电压显示精度。STM32 的内部 VREFINT 可以作为软件校准基准每次开机先测一次内部基准电压反推实际 VREF 的电压值再对所有 ADC 结果做比例校正。这个技巧能直接让显示的电压值和万用表读数误差从 2% 压到 0.3% 以内是数字电源“可调可显”的最低门槛。本文还有配套的精品资源点击获取