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. 新增配置(基于开环工程)
- 开启TIM1中断:用于逐周期PI计算。
- 编码器按键配置: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
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
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
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
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
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
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
2
3
4
5
6
7
8
9
10
11
12
# 15.4.4 PI调试过程
# 1. 调试步骤
比例系数(P)调试:
- 首先保持积分系数为0,用编码器逐步增加P的值。
- 当P值为1时,观察输出电压跳变时的波形,波形无超调
- 逐步增大P,在P=3.2时上升沿出现小幅超调,此时输出电压存在较大稳态误差
积分系数(I)调试:
- 保持P不变,逐步增加I。
- 按下编码器,切换为控制I值,旋转编码器逐步增加I的值,当I=0.04时,振荡幅度变化不大,但稳态误差几乎消除
- 继续增大I,发现波形的振荡越来越严重,在I=0.14时,出现了4个周期振荡,说明I过大
适当降低P值以消除振荡:
- 编码器逐步减小P的值,发现振荡在逐步下降,最终在P=1.4时效果最佳,超调不多,且振荡在一个周期后达到目标值
- 编码器逐步减小P的值,发现振荡在逐步下降,最终在P=1.4时效果最佳,超调不多,且振荡在一个周期后达到目标值
# 2. 最终参数
- 理想值:P=1.4,I=0.04
- 实际效果:输出电压稳定在3.3V,误差<±0.05V

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