前言

电源方向资料免费开源到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的分享