15.4 数字BUCK变换器项目2:闭环系统设计

# 15.4 数字BUCK变换器项目2:闭环系统设计

本实验在开环系统基础上引入数字PI控制,通过动态调节占空比实现输出电压的精确稳压,提升系统抗干扰能力。
核心功能

  • PI闭环控制:消除输入电压波动、负载变化带来的误差。
  • 动态参数调节:通过编码器实时调整PI参数。
  • 多模式切换:支持1V/3.3V输出快速切换,验证动态响应。

# 15.4.1 数字PI控制原理

# 1. PI控制公式

离散时间域表达式:
$$ u(k) = K_p \cdot e(k) + K_i \cdot \sum_{i=0}^{k} e(i) $$
其中:

  • $u(k)$:控制器的输出
  • $e(k)$:误差信号
  • $K_p$:比例系数
  • $K_i$:积分系数

# 2. 参数作用

  • 比例系数($K_p$):快速响应误差,过大会导致振荡,过小则响应迟缓。
  • 积分系数($K_i$):消除稳态误差,过大引起超调,过小则静差残留。

# 15.4.2 STM32CubeMX配置

# 1. 新增配置(基于开环工程)

  1. 开启TIM1中断:用于逐周期PI计算。
  2. 编码器按键配置:PB4引脚设置为上拉输入,用于PI参数切换。

# 15.4.3 闭环软件设计

# 1. 新增和修改初始化变量

变量pidtest[2]数组中存储着1V和3.3V对应的参考值,具体对应关系,可参考后续PI闭环函数的参考值Voref的设定。

//显示变量
uint16_t value[2];  //存放adc读取的模拟量 0:采样电流值1:采样电压值
float Vout;          //存放转换后实际的电压值
float Iout;          //存放转换后实际的电流值
char Vo_str[30];    //存储字符形式的电压显示
char Io_str[30];    //存储字符形式的电流显示
char P_str[30];     //存储字符形式的P值显示
char I_str[30];     //存储字符形式的I值显示
//编码变量
int Encoder_cnt[2]={0,0}; //存储P值和I值对应原始编码值
int PI_select=0;     //0: p值   1:I值
int Star_pos[2]={0,59};    //存储*号跳变的位置
//翻转变量
int32_t   pidtest[2]={310,1024}; //310-1V 1024-3.3V
int32_t   pidcnt=0;  //0: 1V  1: 3.3V
//按键扫描变量
char flag=0;  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 2. 编写PI闭环函数

/****************环路变量定义**********************/
int32_t Error=0;       //电压误差     Q12格式
int32_t u0=0;         //电压环输出量 Q24格式
int32_t Dutycycle=0; //电压环输出占空比 Q12格式
int32_t Voref=1024; //1024-3.3V
int32_t KP=0;       //电压环PI环路P值,Q12格式
int32_t KI=0;       //电压环PI环路I值,Q12格式
#define INTEGREAL_MAX    15097856//最大占空比90%的积分量Q24格式
#define INTEGREAL_MIN    1675264 //最小占空比10%的积分量 Q24格式
#define Dutycycle_MAX    3686    //最大占空比 90% Q12格式
#define Dutycycle_MIN    409     //最小占空比 10% Q12格式
void PositionalPI_Update(void)
{
//环路积分量Q12
static  int32_t   Integral=0;
//计算电压误差量
Error= Voref - value[1];
//电压环路输出  
u0= Integral + Error*KP;
//积分项(抗饱和处理)
Integral = Integral + Error*KI;
if(Integral < INTEGREAL_MIN ) Integral = INTEGREAL_MIN;
if(Integral > INTEGREAL_MAX ) Integral = INTEGREAL_MAX;
//计算输出并限幅
Dutycycle = u0>>12;     //右移12位相当于/4096
if(Dutycycle > Dutycycle_MAX) Dutycycle = Dutycycle_MAX;
if(Dutycycle < Dutycycle_MIN) Dutycycle = Dutycycle_MIN;
//更新对应寄存器
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,Dutycycle*720
>>12);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 3. 编写输出翻转函数

实现输出参考电压的周期性切换,模拟负载切换,方便后续的PI调试。

void Outputvoltage_Change (void)
{
  pidcnt++;               //pidcnt自增
  pidcnt%=2;              //使pidcnt在0,1之间切换
  Voref=pidtest[pidcnt];//更改输出
}
1
2
3
4
5
6

# 4. 修改OLED显示函数

将占空比显示删掉,增加P值和I值显示

void  OLED_disp(void)
{
//将采样值转换成实际值
Vout = value[1]*13.2/4096;
Iout = value[0]*1.65/4096;
//把实际值转化为字符串
sprintf(Vo_str,"Vout:%.2fV",Vout);
sprintf(Io_str,"Iout:%.2fA",Iout);
sprintf(P_str,"P:%.1f",KP/4096.0);
sprintf(I_str,"I:%.2f",KI/4096.0);
//屏幕刷新数据
OLED_PrintString(45,0,"BUCK",&font16x16,OLED_COLOR_NORMAL);
OLED_PrintString(16,15,Vo_str,&font16x16,OLED_COLOR_NORMAL);
OLED_PrintString(16,30,Io_str,&font16x16,OLED_COLOR_NORMAL);
OLED_PrintString(8,45,P_str,&font16x16,OLED_COLOR_NORMAL);
OLED_PrintString(69,45,I_str,&font16x16,OLED_COLOR_NORMAL);
OLED_PrintString(Starpos[PI_select],45,"*",&font16x16,
OLED_COLOR_NORMAL);
OLED_ShowFrame();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 5. 修改编码控制函数

编码器控制PI系数,动态调整系数,减少频繁烧录,方便后续调试。

void Encoder(void)
{
//读取编码值
  Encoder_cnt[PI_select]=__HAL_TIM_GET_COUNTER(&htim2); 
if(Encoder_cnt[PI_select] >= 60000)// 下限保护处理
{
    Encoder_cnt[PI_select] = 0;
    __HAL_TIM_SET_COUNTER(&htim2,0);
}
else if(Encoder_cnt[PI_select] > 100)//上限保护处理
{
    Encoder_cnt[PI_select] = 100;
    __HAL_TIM_SET_COUNTER(&htim2,100);
}
if(PI_select ==0)
    KP=Encoder_cnt[PI_select]*409;
else
    KI=Encoder_cnt[PI_select]*82;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 6. 编写按键扫描函数

通过编码器按键引脚来切换所要控制的PI系数

void Scan_Key(void)
{
  // 按键没有按下的时候
  if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4) == GPIO_PIN_SET) 
    flag = 0; // 标志位清零
  //按键按下的时候
  if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4) == GPIO_PIN_RESET) 
  {
  if(flag==0)//如果按键没有一直按着就执行,如果标志位为1说明还没有松手
  {
    OLED_PrintString(sit[PI_select],45," ",&font16x16,
    OLED_COLOR_NORMAL);
    PI_select ++;
    PI_select %=2;
    //更新编码器对应值的功能
    __HAL_TIM_SET_COUNTER(&htim2,Encoder_cnt[PI_select]);
  }
  flag = 1; // 将标志位置1锁住 表示已经按下
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 7. 在中断回调函数中正确调用函数

PI闭环函数放在TIM1中断内,翻转函数和按键扫描函数放在定时器3中断内

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim == &htim1)          //htim1定时器触发 10us
  PositionalPI_Update();//调用BUCK电压环控制PID计算函数
  if(htim == &htim3)          //htim3定时器触发  200ms
{
  Scan_Key();        //按键扫描函数
OLED_disp();       //OLED显示函数
Encoder();         //编码控制函数
Outputvoltage_Change();         //输出翻转函数
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 15.4.4 PI调试过程

# 1. 调试步骤

  1. 比例系数(P)调试

    • 首先保持积分系数为0,用编码器逐步增加P的值。
    • 当P值为1时,观察输出电压跳变时的波形,波形无超调 闭环实验结果
    • 逐步增大P,在P=3.2时上升沿出现小幅超调,此时输出电压存在较大稳态误差 闭环实验结果
  2. 积分系数(I)调试

    • 保持P不变,逐步增加I。
    • 按下编码器,切换为控制I值,旋转编码器逐步增加I的值,当I=0.04时,振荡幅度变化不大,但稳态误差几乎消除 闭环实验结果
    • 继续增大I,发现波形的振荡越来越严重,在I=0.14时,出现了4个周期振荡,说明I过大 闭环实验结果
  3. 适当降低P值以消除振荡

    • 编码器逐步减小P的值,发现振荡在逐步下降,最终在P=1.4时效果最佳,超调不多,且振荡在一个周期后达到目标值 闭环实验结果

# 2. 最终参数

  • 理想值:P=1.4,I=0.04
  • 实际效果:输出电压稳定在3.3V,误差<±0.05V
闭环实验结果

# 15.4.5 关键说明

  • 死区保护:占空比限制10%~90%,避免MOS管直通。
  • 定点数运算:采用Q12/Q24格式,避免浮点运算开销。
  • 动态切换:200ms周期切换输出电压,验证系统响应。