前言

电源方向资料免费开源到QQ群1:280730348QQ群2:725438563

嵌入式方向资料免费开源到QQ群1:976387827

博客地址edadong.com,博文同步发布在知乎、bilibili,其中bilibili主要以视频为主。对开源项目的疑惑请尽量在b站下方留言,其次在群内商讨,目前BUCK和BOOST电路均有群友复刻成功,逆变电路、双向DC充放电电路我也验证过。

下面是BUCK电路的PID控制方案和具体电路,更详细的讲解请移步Bilibili:EDAdong。

软件思路

PID其实属于比较常见且通用的闭环调节算法,经典古老但实用。在一般的单环或者双环闭环控制环节,拥有较为不错的效果。本次分享的PID属于增量式PID,大家可以去了解一下增量式PID和位置式PID的区别,一般来说在电源设计里面增量式PID会用的比较多一点。传统PID中,P环是根据当前误差量输出反馈量,I环是根据当前误差和上一次误差之差输出反馈量,D环是根据上次I环误差量和此次I环误差量进行刹车反馈,通过上一次误差量抑制当前误差。P环比较通俗易懂一点,所以传统PID里面P环会用的比较多一点。放在增量式PID中,传统PID的P环其实跟增量式的I环一样,所以这一点有所区分,大家需要注意。我通常是P环和I环一起调,以I环为主导。

image-20250323142231953

本次PID代码在基于BUCK的实战演练项目中修改,直接可用,话不多说直接上代码

在PID.c文件中,可以看到buck_pid的调节函数,第一个参量NOW代表的是你当前采集到的数据(比如在本设计中就是电压所转换成的ADC值,范围0-4096)。第二个参量Target代表你要追踪的目标值(比如在本设计里面把目标电压转换成对应ADC值)。第三个参量SIGNLE_ADD_NUM_LIMIT代表单次增加量,每次PID调节的增量范围,这个值不能太大,否则系统就容易震荡起来。程序中设定的PID周期为1ms,每秒能执行1000次,每次如果拉满的话,那么1s理论上就能调节5000,但我们重装载值也就3600,而且越接近目标值,增量就会越小,所以这个参数你可以根据自己的系统去调节,目前我用的5基本跑单环速度和精度都没问题。第四个参量SUM_OUTPUT_NUM_LIMIT代表输出限制,比如我们重装载值为3600,那么这个值就可以设置成3599,确保占空比不会继续递增。后面的两个参量kp和ki分别代表增量式PID里面的积分调节和比例调节。

pid.c

#include "pid.h"
#include "main.h"
PID_Struct pid1;

float OUT=50;
float buck_pid(float NOW,float Target,float SIGNLE_ADD_NUM_LIMIT,float SUM_OUTPUT_NUM_LIMIT,float kp,float ki)//SIGNLE_ADD_NUM_LIMIT是单次增加最大值 如100即为单次转换输出不能超过+-100
{
//SUM_OUTPUT_NUM_LIMIT 是总输出限位 如100 即总输出不能超出+-100的范围 防止长时间低于或高于 对于突变反应不及时

pid1.Pv=NOW;
pid1.Ek=Target-pid1.Pv;
pid1.Pout=kp*(pid1.Ek-pid1.Ek_1);
pid1.Iout=ki*pid1.Ek*pid1.T/pid1.Ti;
pid1.Dout=pid1.Kp_D*pid1.Td*(pid1.Ek-pid1.Ek_1-pid1.Ek_1+pid1.Ek_2)/pid1.T;
pid1.OUT_Single=pid1.Pout+pid1.Iout+pid1.Dout;
if(pid1.OUT_Single>SIGNLE_ADD_NUM_LIMIT)pid1.OUT_Single=SIGNLE_ADD_NUM_LIMIT;
else if(pid1.OUT_Single<-SIGNLE_ADD_NUM_LIMIT)pid1.OUT_Single=-SIGNLE_ADD_NUM_LIMIT;
OUT+=pid1.OUT_Single;
if(OUT>SUM_OUTPUT_NUM_LIMIT)OUT=SUM_OUTPUT_NUM_LIMIT;
else if(OUT<0)OUT=0;
pid1.Ek_2=pid1.Ek_1;
pid1.Ek_1=pid1.Ek;
return OUT;
}

void init_buck_PID()
{
pid1.Kp_P=0;
pid1.Kp_I=1;
pid1.Kp_D=0;
pid1.T=1;//Ms
pid1.Td=2.5;
pid1.Ti=150;
pid1.Pv=1000;
pid1.Ek_2=0;
pid1.Ek_1=0;
pid1.Ek=0;
}

pid.h

#ifndef _PID_H
#define _PID_H

typedef struct
{
float Sv;//期望值
float Pv;//实际值
float Ek_1;
float Ek_2;
float Ek;
float T;//采样周期
float Ti;//积分常数
float Td;//微分常数
float Kp_P;//系数
float Kp_I;//系数
float Kp_D;//系数
float OUT_Single;
float Pout;
float Iout;
float Dout;
}PID_Struct;
float buck_pid(float NOW,float Target,float SIGNLE_ADD_NUM_LIMIT,float SUM_OUTPUT_NUM_LIMIT,float kp,float ki);
void init_buck_PID(void);
extern float OUT;
#endif


在定时器中断中(20KHz)每进入20次进行一次PID调节,也就是单次调节时间为1ms。

//中断服务函数
void TIM1_UP_IRQHandler(void)
{
static u16 pid_count;
if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) //检查更新中断发生与否
{
pid_count++;
//PID调节
if(pid_count==20)
{
pid_count=0;
if(pid_mode==1&&protect_status==0)
{
buck_pwm = buck_pid(ADC_ConvertedValue[0],(Target_V/3.3*4096/V_xishu),5,3599,kp,ki);
set_pwm(TIM1,1,buck_pwm,3599);
}
else if(pid_mode==0&&protect_status==0)
{
buck_pwm=0;
set_pwm(TIM1,1,buck_pwm,1000);
}
else if(protect_status==1)
{
buck_pwm=0;
set_pwm(TIM1,1,buck_pwm,1000);
}
}
TIM_ClearITPendingBit(TIM1, TIM_IT_Update); //清除TIMx更新中断标志
}
}

主函数中只需要定义float kp和float ki两个参量,extern到main.h里面,再在main函数中初始化init_buck_PID();即可

#include "stm32f10x.h" 
#include "main.h"
#include "delay.h"
#include "timer.h"
#include "timer3.h"
#include "oled.h"
#include "show.h"
#include "adc.h"
#include "sw.h"
#include "key.h"


u8 pid_mode=0;//=0不工作,=1工作
u8 pid_status=0;//=0为电压环,=1为电流环
u16 buck_pwm=500;//BOOST电路的PWM

float DC_V=0.0f;//电压数据
float Target_V=10.00f;//追踪的电压数据
float DC_I=0.0f;//电流数据

float V_yuzhi=13.00f;//保护的电压阈值
float I_yuzhi=3.00f;//保护的电流阈值

u8 protect_status=0;//保护状态
u8 sw_status=0;//开关状态

float kp=0.01;
float ki=1;
/************************************************************
电压电流检测:电压PA0,电流PA1
PWM波驱动引脚:PA8
芯片使能引脚:PA9
OLED屏幕:SCL:PB1,SDA:PB0
继电器控制位:PA10
按键:ADD:PB12 REDUCE:PB13 SET:PB14 MODE:PB15
************************************************************/
int main(void)
{
u8 key;
Delay_Init(); //延时功能初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级

BUCK_PWM_Init(); //BUCK电路初始化,20KHz工作频率,带中断。
Init_adc(); //ADC初始化
SW_Init( ); //继电器初始化
KEY_Init(); //按键初始化

TIM3_ENABLE_30S(); //开启定时器3工作,实现电压电流数据采集
OLED_Init(); //初始化OLED

loop:
SW_OFF;
SD_OFF;
sw_status=0;
pid_mode=0;//失能PID
OLED_Clear(); //OLED清零
canshu_view(); //启动界面
while(1)
{
key=KEY_Scan(0); //扫描按键
if(key==SW_PRES)
{
SD_ON; //打开芯片引脚使能
SW_ON; //打开继电器电路准备工作
Delay_Ms(500);
break;
}
OLED_ShowNum(72,0,DC_V,2,16);
OLED_ShowNum(96,0,(u16)(DC_V*100)%100,2,16);
OLED_ShowNum(72,2,DC_I,2,16);
OLED_ShowNum(96,2,(u16)(DC_I*100)%100,2,16);
OLED_ShowNum(72,4,Target_V,2,16);
OLED_ShowNum(96,4,(u16)(Target_V*100)%100,2,16);
if(sw_status==0)
{
OLED_ShowCHinese(84,6,13);OLED_ShowCHinese(100,6,14);
}
else
{
OLED_ShowCHinese(84,6,11);OLED_ShowCHinese(100,6,12);
}
Delay_Ms(50);
}
while(1)
{
key=KEY_Scan(0);
OLED_ShowNum(72,0,DC_V,2,16);
OLED_ShowNum(96,0,(u16)(DC_V*100)%100,2,16);
OLED_ShowNum(72,2,DC_I,2,16);
OLED_ShowNum(96,2,(u16)(DC_I*100)%100,2,16);
OLED_ShowNum(72,4,Target_V,2,16);
OLED_ShowNum(96,4,(u16)(Target_V*100)%100,2,16);
if(sw_status==0)
{
OLED_ShowCHinese(84,6,13);OLED_ShowCHinese(100,6,14);
}
else
{
OLED_ShowCHinese(84,6,11);OLED_ShowCHinese(100,6,12);
}

if(key==SW_PRES)
{
pid_mode=1;
sw_status=1;
}
else if(key==ADD_PRES)
{
Target_V=Target_V+0.100001f;
}
else if(key==REDUCE_PRES)
{
Target_V=Target_V-0.099999f;
}
else if(key==CHOICE_PRES)
{
SW_OFF;
SD_OFF;
sw_status=0;
pid_mode=0;//失能PID
goto loop;
}

if((DC_V>V_yuzhi)||(DC_I>I_yuzhi))
{
SW_OFF;
SD_OFF;
protect_status=1;
sw_status=0;
pid_mode=0;//失能PID
goto loop;
}
}
}


资料放百度网盘链接了:

通过网盘分享的文件:post12资料合集
链接: https://pan.baidu.com/s/1O4975ZoDPHOWwQN88SApsA?pwd=fvay 提取码: fvay
–来自百度网盘超级会员v6的分享