0
点赞
收藏
分享

微信扫一扫

《DMF407电机控制专题教程》第26章 无刷电机无感方波闭环控制

第26章无刷电机无感方波闭环控制​


前面我们已经学会了无感方波的基本控制,本章将使用PID来实现无刷电机无感控制的速度闭环控制速度+电流双闭环控制

本章分为如下几个小节:

26.1无感速度闭环控制

26.2 无感速度&电流双闭环控制



26.1 无感速度闭环控制

要实现速度闭环,首先需要先了解下无刷无感驱动的测速原理,这样才可以知道当前转速是否符合我们的设定值。

26.1.1 无刷无感测速原理

无感测速原理与有感类似,有感通过霍尔信号测速,无感通过过零信号测速,两者输出的波形一致。当转子只有一对级时,电机旋转一圈,每一相都会出现两次过零点,只需检测其中一相过零信号高电平持续时间,即可求出旋转一圈所需时间。详情可以回顾下24.1.1小节。

26.1.2 硬件设计

1、例程功能

  • 本实验以电机开发板的直流有刷/无刷电机驱动接口1为例。
  • 当按键0按下,就增大目标速度值;当按键1按下,就减小目标速度值。目标速度的绝对值大小决定电机的速度,它的正负决定电机的正反转方向。按下按键2则马上停止电机。
  • 屏幕显示按键功能、占空比、目标转速以及实际转速以及显示无刷电机电压、电流和温度、速度等等信息。
  • 可通过串口1即USB_TTL接口连接正点原子PID调试助手,查看PID波形
  • LED0闪烁指示程序运行。

2、硬件资源

1)LED灯:LED0 – PE0

2)独立按键

KEY0 – PE2

KEY1 – PE3

KEY2 – PE4

3)定时器:

TIM1_CH1:PA8

TIM1_CH2PA9

TIM1_CH3PA10

IO:PB13\14\15

使能引脚:SHDN: PF10

4)过零信号检测引脚

过零U相 PH10

过零VPH11

过零WPH12

5)无刷电机

6)无刷电机驱动板

712-60V的DC电源

8)ADC:ADC1_CH9:PB1

ADC1_CH0:PA0

ADC1_CH8:PB0

ADC1_CH6:PA6

ADC1_CH3:PA3

3、原理图

原理图部分和开环控制完全一致,这里就不重复叙述了,可以参考之前的章节。

26.1.3 程序设计

26.1.3.1 无刷电机无感速度闭环的配置步骤

1)电机基本驱动

实现电机的基础驱动函数,启停、6步换向组合、过零控制等等

2)初始化ADC(保留)

初始化ADC通道的IO,设置ADC工作方式、DMA等等

3)PID闭环控制实现

实现PID的初始参数赋值、PID计算等等

4)上位机通信协议

编写上位机通信代码,可在上位机上实时显示当前速度与目标速度的波形变化

5)编写中断服务函数

PWM中断用于无感驱动状态切换、换向控制、堵转检测、PID闭环控制等等

26.1.3.2 程序流程图

《DMF407电机控制专题教程》第26章 无刷电机无感方波闭环控制_上位机


图26.1.3.2.1 无刷电机无感速度闭环控制流程图

26.1.3.3 程序解析

这里我们只讲解核心代码,使用了PID闭环控制,因此需要PID部分的程序,计算过程一致,这里仅列出无感闭环的PID部分参数,如下。

/*定义PID参数相关宏*/​
#define KP 0.05000f /* P参数*/​
#define KI 0.00025f /* I参数*/​
#define KD 0.000150f /* D参数*/​
#define SMAPLSE_PID_SPEED 40 /* 采样率 单位ms*/

接着是定时器中断回调函数,在里边实现速度计算以及过零控制如下。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)​
{​
uint8_t i;​
static uint8_t times_count = 0; /* 定时器时间记录 */​
if(htim->Instance == ATIM_TIMX_PWM) /* 55us */​
{​
if(g_bldc_motor1.run_flag == RUN)​
{​
/******************* 三相电流采集 *******************/​
for(i=0; i<3; i++)​
{​
adc_val_m1[i] = g_adc_val[i+2];​
adc_amp[i] = adc_val_m1[i] – ​
adc_amp_offset[i][ADC_AMP_OFFSET_TIMES];​
if(adc_amp[i] < 0) ​
adc_amp_un[i] = 0; /* 反电动势电压为悬空绕组直接清0 */​
else if(adc_amp[i] >= 0) /* 去除反电动势引起的负电流数据 */​
adc_amp_un[i] = adc_amp[i];​
}​
/*运算母线电流(母线电流为任意两个有开关动作的相电流之和)*/​
adc_amp_bus = (adc_amp_un[0] + adc_amp_un[1] + ​
adc_amp_un[2])*ADC2CURT;​
}​
#ifdef H_PWM_L_ON​
/* 过零控制 */​
zero_ctr_loop();​
/*************** 速度环PID控制 *****************/​
具有一定速度(有速度测量说明已经进入过零闭环状态)后才能进入PID闭环控制 */​
if(g_bldc_motor1.run_flag == RUN && g_zero_ctr_status == 3)​
{​
temp_pwm1 = increment_pid_ctrl(&g_speed_pid,g_bldc_motor1.speed);​
FirstOrderRC_LPF(motor_pwm_s,temp_pwm1,0.085);​
/* 最低速度限制 */​
if(motor_pwm_s < 0)​
{​
/* 加速启动速度 */​
if(motor_pwm_s >= -600)​
motor_pwm_s = -600;​
g_bldc_motor1.pwm_duty = -motor_pwm_s;​
}​
else​
{​
if(motor_pwm_s <= 600)​
motor_pwm_s = 600;​
g_bldc_motor1.pwm_duty = motor_pwm_s;​
}​
}​

#endif​
}​
if(htim->Instance == TIM6)​
{​
/******************基准电压测量 ********************/​
times_count++;​
if(g_bldc_motor1.run_flag == STOP)​
{​
uint8_t i;​
uint32_t avg[3] = {0,0,0};​
adc_amp_offset[0][adc_amp_offset_p] = g_adc_val[2];​
adc_amp_offset[1][adc_amp_offset_p] = g_adc_val[3];​
adc_amp_offset[2][adc_amp_offset_p] = g_adc_val[4];​
adc_amp_offset_p ++;​
NUM_CLEAR(adc_amp_offset_p,ADC_AMP_OFFSET_TIMES);​
for(i=0; i<ADC_AMP_OFFSET_TIMES; i++)​
{​
avg[0] += adc_amp_offset[0][i];​
avg[1] += adc_amp_offset[1][i];​
avg[2] += adc_amp_offset[2][i];​
}​
for(i=0; i<3; i++)​
{​
avg[i] /= ADC_AMP_OFFSET_TIMES;​
adc_amp_offset[i][ADC_AMP_OFFSET_TIMES] = avg[i];​
}​
}​
if(times_count == SMAPLSE_PID_SPEED)​
{​
#if (LOCK_TAC==2)​
/********************* 堵塞处理 *******************/​
if(g_bldc_motor1.locked_rotor==1) /* 堵塞 */​
{​
clc++;​
if(clc > 50) /* 延迟2s后重新启动*/​
{​
clc = 0;​
pid_init();​
stop_motor1();​
g_speed_pid.SetPoint = 400.0; /* 400PRM */​
g_bldc_motor1.dir = CW; /* 初始方向正转 */​
g_bldc_motor1.pwm_duty = 600; /* 加速启动速度 */​
g_bldc_motor1.run_flag = RUN; /* 开启运行 */​
start_motor1(); /* 运行电机 */​
g_bldc_motor1.locked_rotor = 0;​
g_zero_ctr_status = 0; /* 堵塞状态需要重新定位初始位置 */​
}​
}​
#endif​
times_count=0;​
}​
}​
}

在中断回调函数里边同样会调用过零控制函数zero_ctr_loop,该函数内容与上节课完全一致,这里就不多赘述。主要来看下PID的控制环节,首先通过无感控制状态判断决定是否打开PID控制,当过零信号稳定就会进入PID控制,首先通过过零信号计算得出的电机转速,接着将电机转速进行滤波,然后带入PID计算,经过限幅将PWM带入到下一次中断时启用。这里我们还在中断中加上了堵转处理,当电机因意外或过流导致停机时,当堵转时间超过2s,此时会重新初始化PID并以400RPM的初始速度重新开启电机旋转。

main函数主要是通过按键设定闭环的目标速度值,内容如下。

int main(void)​
{​
uint8_t key,t;​
char buf[32];​
int16_t pwm_duty_temp=0;​
float current[3]= {0.0f};​
float current_lpf[4]= {0.0f};​
uint16_t strar_sf=0;/* 启动速度标识 */​
uint8_t display_motor_offest=0;​

HAL_Init(); /* 初始化HAL库 */​
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */​
delay_init(168); /* 延时初始化 */​
usart_init(115200); /* 串口初始化为115200 */​
led_init(); /* 初始化LED */​
key_init(); /* 初始化按键 */​
lcd_init(); /* 初始化LCD */​
bldc_init(168000/18-1,0);​
bldc_ctrl(MOTOR_1,CW,1000); /* 初始无刷电机接口1速度 */​
adc_nch_dma_init();​
pid_init();​

g_point_color = WHITE;​
g_back_color = BLACK;​
lcd_show_string(10,10,200,16,16,"BLDC Motor Test",g_point_color);​
lcd_show_string(10,30,200,16,16,"KEY0:Step++",g_point_color);​
lcd_show_string(10,50,200,16,16,"KEY1:Step--",g_point_color);​
lcd_show_string(10,70,200,16,16,"KEY2:Stop",g_point_color);​

while (1)​
{​
t++;​
if(t % 20 == 0)​
{​
sprintf(buf,"SetSpeed:%4d ",(int16_t)(*User_SetPoint)); ​
/* 显示设置速度 */​
lcd_show_string(10,110+display_motor_offest,200,16,16,buf,​
g_point_color);​
sprintf(buf,"M1 speed:%4d ",g_bldc_motor1.speed); /* 显示转速 */​
lcd_show_string(10,130+display_motor_offest,200,16,16,buf,​
g_point_color);​
sprintf(buf,"M1 pos:%4d ",g_bldc_motor1.pos); /* 显示测量速度 */​
lcd_show_string(10,150+display_motor_offest,200,16,16,buf,​
g_point_color);​
sprintf(buf,"PWM_Duty:%.1f%%",(float)((g_bldc_motor1.pwm_duty/​
(float)MAX_PWM_DUTY)*100)); /* 显示控制PWM占空比 */​
lcd_show_string(10,170+display_motor_offest,200,16,16,buf,​
g_point_color); ​
LED0_TOGGLE(); /* LED0(红灯) 翻转 */​

if(g_bldc_motor1.run_flag==STOP) /* 停机的电流显示 */​
{​
current_lpf[0]=0;​
current_lpf[1]=0;​
current_lpf[2]=0;​
}​
current[0]=adc_amp_un[0]* ADC2CURT;/* U */​
current[1]=adc_amp_un[1]* ADC2CURT;/* V */​
current[2]=adc_amp_un[2]* ADC2CURT;/* W */​

/* 一阶数字滤波 滤波系数0.1 用于显示 */​
FirstOrderRC_LPF(current_lpf[0],current[0],0.1f);​
FirstOrderRC_LPF(current_lpf[1],current[1],0.1f);​
FirstOrderRC_LPF(current_lpf[2],current[2],0.1f);​
FirstOrderRC_LPF(current_lpf[3],adc_amp_bus,0.1f);​
sprintf(buf,"Amp U:%.3fmA ",(float)current_lpf[0]);​
lcd_show_string(10,230,200,16,16,buf,g_point_color);​
sprintf(buf,"Amp V:%.3fmA ",(float)current_lpf[1]);​
lcd_show_string(10,250,200,16,16,buf,g_point_color);​
sprintf(buf,"Amp W:%.3fmA ",(float)current_lpf[2]);​
lcd_show_string(10,270,200,16,16,buf,g_point_color);​
sprintf(buf,"Amp Bus:%.3fmA ",(float)adc_amp_bus);​
lcd_show_string(10,290,200,16,16,buf,g_point_color);​
}​

key = key_scan(0);​
if(key == KEY0_PRES)​
{​
step_up();​
}​
if(key == KEY1_PRES) /* 按下KEY1开启电机 */​
{​
step_down(); ​
}​
if(key == KEY2_PRES) /* 按下KEY2关闭电机 */​
{​
stop_motor1(); /* 停机 */​
g_bldc_motor1.run_flag=STOP; /* 标记停机 */​
g_bldc_motor1.pwm_duty=0;​
pid_init(); /* 初始化PID */​
clc=0; /* 清零等待时间 */​
}​
delay_ms(10);​
}​
}​
void step_up(void)​
{​
g_bldc_motor1.run_flag=RUN; /* 开启运行 */​
start_motor1(); /* 开启运行 */​
if(g_bldc_motor1.dir==CW&&*User_SetPoint==0)​
{​
g_bldc_motor1.dir=CCW;​
}​
*user_setpoint+=400; /* 逆时针旋转下递增*/​
if(*user_setpoint==0)​
{​
stop_motor1(); /* 开启运行 */​
g_bldc_motor1.run_flag=STOP;/* 标记停机 */​
g_bldc_motor1.pwm_duty=0;​
pid_init(); /* 初始化PID */​
g_bldc_motor1.speed=0;​
motor_pwm_s=0;​
}​
if(*user_setpoint>=3200) /* 最高不超过3200prm */​
{​
*user_setpoint=3200;​
}​
debug_data_temp=*user_setpoint;​
}​
void step_down(void)​
{​
g_bldc_motor1.run_flag=RUN; /* 开启运行 */​
start_motor1(); /* 开启运行 */​
if(g_bldc_motor1.dir==CCW&&*user_setpoint==0)​
{​
g_bldc_motor1.dir=CW;​
}​
*user_setpoint-=400; /* 逆时针旋转下递增*/​
if(*user_setpoint==0)​
{​
stop_motor1(); /* 开启运行 */​
g_bldc_motor1.run_flag=STOP;/* 标记停机 */​
g_bldc_motor1.pwm_duty=0;​

pid_init(); /* 初始化PID */​
g_bldc_motor1.speed=0;​
motor_pwm_s=0;​
}​
if(*user_setpoint<=-3200) /* 最高不超过3200prm */​
{​
*user_setpoint=-3200;​
}​
debug_data_temp=*user_setpoint;​
}

无感闭环切换方向时还需跳出PID过程,重新建立稳定的无感旋转过程,然后再次进入PID控制,因此按键控制逻辑相对多一点,并且PID还需要重新初始化,防止变量没有清零造成失控的现象。

26.1.4 下载验证

下载代码后,可以看到LED0在闪烁,说明程序已经正常在跑了,LCD上显示按键功能、占空比以及电机速度信息和显示程序计算的电压、电流等信息,当我们按下KEY0,目标速度将增大;按下KEY1,目标速度将减小;按下KEY2,电机将停止。目标速度为正数时,电机正转,反之电机反转。我们再打开PID调试助手(注意接上串口1的USB_TTL连接至电脑),选择对应的串口端口,接着勾选通道1和2,点击“开始”按钮,即可开始显示波形,如下图26.1.4.1,大家可以自己下载程序并点击按键测试效果。

《DMF407电机控制专题教程》第26章 无刷电机无感方波闭环控制_闭环控制_02


图26.1.4.1 PID调节效果

图26.1.4.1中,橙线代表目标速度,红线代表实际速度,当我们按下KEY0,目标速度增大,橙线先发生变化,而红线(实际速度)会逐渐靠近橙线(目标速度);按下KEY1,目标速度将减小,曲线的变化同理;按下KEY2,电机将停止,目标速度将为0。

注意:1、本实验需要使用USB数据线连接开发板的串口1到电脑,并启动电机之后才会有波形变化;2、如果发现波形不对,请检查电机接线;3、PID系数并不是通用的,如果PID曲线不理想,大家需要根据自己的实际系统去调节


26.2 无感速度+电流双闭环控制

要实现无感的速度+电流双闭环控制,只需在速度环的基础上加上电流环部分即可。三相电流的采集已经在前面的课程(第23章)学习过了,所以下面我们直接进入代码实现。

26.2.1 硬件设计

1、例程功能

  • 本实验以电机开发板的直流有刷/无刷电机驱动接口1为例。
  • 通过PID电流环的初始化设置转矩大小,即目标电流大小。目标电流越大,转矩越大。
  • 当按键0按下,就增大目标速度值;当按键1按下,就减小目标速度值。目标速度的绝对值大小决定电机的速度,它的正负决定电机的正反转方向。按下按键2则马上停止电机。
  • 屏幕显示按键功能、占空比、目标转速以及实际转速以及显示无刷电机电压、电流和温度、速度等等信息。
  • 可通过串口1即USB_TTL接口连接正点原子PID调试助手,查看PID波形
  • LED0闪烁指示程序运行。

2、硬件资源

1)LED灯:LED0 – PE0

2)独立按键

KEY0 – PE2

KEY1 – PE3

KEY2 – PE4

3)定时器:

TIM1_CH1:PA8

TIM1_CH2PA9

TIM1_CH3PA10

IO:PB13\14\15

使能引脚:SHDN: PF10

4)过零信号检测引脚

过零U相 PH10

过零VPH11

过零WPH12

5)无刷电机

6)无刷电机驱动板

712-60V的DC电源

8)ADC:ADC1_CH9:PB1

ADC1_CH0:PA0

ADC1_CH8:PB0

ADC1_CH6:PA6

ADC1_CH3:PA3

3、原理图

《DMF407电机控制专题教程》第26章 无刷电机无感方波闭环控制_闭环控制_03


图26.2.1.1 无刷电机接口1原理图

接口涉及的IO如下。

直流无刷驱动板

IO口

驱动板U

PA8、PB13

驱动板V

PA9、PB14

驱动板W

PA10、PB15

过零U相

PH10

过零V相

PH11

过零W相

PH12

输出使能

PF10

电压

PB1

温度

PA0

电流U相

PB0

电流V相

PA6

电流W相

PA3

表26.2.1.1 无刷相关IO口说明

驱动板和电机的连接方式之前已经介绍过了,驱动板和开发板连接只需要使用排线连接即可(霍尔传感器接口可不接,JP3跳线帽注意接H&Z与ZERO即可),实物连接如下。

《DMF407电机控制专题教程》第26章 无刷电机无感方波闭环控制_初始化_04


图26.2.1.2 开发板&驱动板连接图

26.2.2 程序设计

26.2.2.1 无刷电机速度+电流双闭环的配置步骤

1)实现电机基本驱动

实现电机的基础驱动函数,启停、6步换向组合等等

2)初始化ADC

初始化ADC通道的IO,设置ADC工作方式、DMA等等

3)PID闭环控制实现

实现PID的初始参数赋值、PID计算等等

4)上位机通信协议

编写上位机通信代码,可在上位机上实时显示当前速度与目标速度的波形变化

5)编写中断服务函数

PWM中断用于换向控制、堵转检测等等,添加PID的周期计算调用

26.2.2.2 程序流程图

《DMF407电机控制专题教程》第26章 无刷电机无感方波闭环控制_闭环控制_05


图26.2.2.2.1 无刷电机速度+电流双闭环控制流程图

26.2.2.3 程序解析

这里我们只讲解核心代码,定时器及ADC的相关程序都和之前一致,这里就不重复列出了,速度+电流双闭环的PID参数的定义有不同。实际内容如下所示。

/*定义PID参数相关宏*/​
#define S_KP 0.05000f /* 速度环的P参数需远小于电流环的P */​
#define S_KI 0.00025f /* I参数*/​
#define S_KD 0.000150f /* D参数*/​
/*定义PID参数相关宏*/​
#define C_KP 2.00f /* P参数*/​
#define C_KI 0.20f /* I参数*/​
#define C_KD 0.01f /* D参数*/​
#define SMAPLSE_PID_SPEED 40 /* 采样率 单位ms*/

其中C_KP,C_KI,C_KD为电流环的PID参数,S_KP,S_KI,S_KD为速度环的PID参数,PID的计算过程和之前的一致,这里就不贴出来了,大家感兴趣可以打开源码查看。重点内容PID控制在定时器的中断回调函数里进行,在bldc_tim.c中实现,贴出的程序删除了部分代码避免篇幅太长,中断服务函数内容如下。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)​
{​
uint8_t i;​
static uint8_t times_count=0;/*定时器时间记录*/​
if(htim->Instance == ATIM_TIMX_PWM)//55us​
{​
… …​
#ifdef H_PWM_L_ON​
/* 过零控制 */​
zero_ctr_loop();​
/**************** PID控制 ****************/​
if(g_bldc_motor1.run_flag == RUN && g_zero_ctr_status == 3) ​
{​
/* 速度+电流双环控制 */​
pid_s_count++;​
pid_c_count++;​
/* 速度环 */​
if(pid_s_count>2)​
{​
… … /*省略部分代码 */ ​
if(debug_switch == 0)​
{​
temp_pwm1 = increment_pid_ctrl(&g_speed_pid,g_bldc_motor1.speed);​
FirstOrderRC_LPF(motor_pwm_s,temp_pwm1,0.5);​
if(motor_pwm_s < 0)​
{​
/*加速启动速度*/​
if(motor_pwm_s >= -600)​
motor_pwm_s = -600;​
motor_pwm_sl = -motor_pwm_s;​
}​
else​
{​
if(motor_pwm_s <= 600)​
motor_pwm_s = 600;​
motor_pwm_sl = motor_pwm_s;​
}​
*user_setpoint = debug_data_temp;/*重新保持上位机指令要求*/​
pid_s_count = 0;​
}​
}​
if(debug_switch == 0)​
{​
/* 电流环 */​
if(pid_c_count > 1)​
{​
/*换向尖峰电流大于设定的电流值将导致PID调节转至电流环调节 速度环无法起作用,转速无法调节*/​
if(adc_amp_bus > (g_current_pid.SetPoint - 20))​
{​
cf_count++;/* 滤除换向尖峰电流的影响 */​
if(cf_count > 4)​
{​
cf_count = 0;​
temp_pwm2 = ​
increment_pid_ctrl(&g_current_pid,adc_amp_bus);​
FirstOrderRC_LPF(motor_pwm_c,temp_pwm2,0.085);​
}​
}​
else​
{​
cf_count = 0;​
temp_pwm2 = ​
increment_pid_ctrl(&g_current_pid,adc_amp_bus);​
FirstOrderRC_LPF(motor_pwm_c,temp_pwm2,0.085);​
}​
pid_c_count = 0;​
}​
/* 电流环输出值大于速度环输出则使用速度环调节 */​
if(motor_pwm_c > motor_pwm_sl)​
{​
g_bldc_motor1.pwm_duty = motor_pwm_sl;​
if(motor_pwm_s < 0) /* 正反转积分控制 */​
g_current_pid.Ui = -g_speed_pid.Ui;​
else​
g_current_pid.Ui = g_speed_pid.Ui;​
}​
else /* 速度环输出值大于电流环输出则使用电流环调节 */​
{​
g_bldc_motor1.pwm_duty = motor_pwm_c;​
if(g_bldc_motor1.dir == CCW)​
g_speed_pid.Ui = -g_current_pid.Ui;​
else​
g_speed_pid.Ui = g_current_pid.Ui;​
}​
} ​
}​
/* 上位机 -> 开发板 方向变化处理 */​
if(debug_switch == 1 && abs((int)(*user_setpoint)) >= 400)​
{​
if(*user_setpoint > 0)​
g_bldc_motor1.dir = CW;​
else​
g_bldc_motor1.dir = CCW;​
start_motor1(); /* 开启运行 */​
g_zero_ctr_status = 0;​
g_bldc_motor1.run_flag = RUN; /* 开启运行 */​
debug_switch = 0;​
}​

#endif​
}​
if(htim->Instance == TIM6)​
{​
/***************** 采集电机停机状态下的偏置电压 ******************/​
times_count++;​
if(g_bldc_motor1.run_flag == STOP)​
{​
uint8_t i;​
uint32_t avg[3] = {0,0,0};​
adc_amp_offset[0][adc_amp_offset_p] = g_adc_val[2];​
adc_amp_offset[1][adc_amp_offset_p] = g_adc_val[3];​
adc_amp_offset[2][adc_amp_offset_p] = g_adc_val[4];​
adc_amp_offset_p ++;​
NUM_CLEAR(adc_amp_offset_p,ADC_AMP_OFFSET_TIMES);​
for(i=0; i<ADC_AMP_OFFSET_TIMES; i++)​
{​
avg[0] += adc_amp_offset[0][i];​
avg[1] += adc_amp_offset[1][i];​
avg[2] += adc_amp_offset[2][i];​
}​
for(i=0; i<3; i++)​
{​
avg[i] /= ADC_AMP_OFFSET_TIMES;​
adc_amp_offset[i][ADC_AMP_OFFSET_TIMES] = avg[i];​
}​
}​
/***************** 定时判断电机是否发生堵塞 *******************/​
if(times_count == SMAPLSE_PID_SPEED)​
{​
#if (LOCK_TAC == 2)​
if(g_bldc_motor1.locked_rotor == 1) /* 到达一定速度后可进入闭环控制 */​
{​
clc++;​
if(clc > 50) /* 延迟2s后重新启动 */​
{​
clc = 0;​
pid_init();​
stop_motor1();​
g_speed_pid.SetPoint = 400.0; /* 400PRM */​
g_bldc_motor1.dir = CW;​
g_bldc_motor1.pwm_duty = 600; /* 加速启动速度 */​
g_bldc_motor1.run_flag = RUN; /* 开启运行 */​
start_motor1(); /* 运行电机 */​
g_bldc_motor1.locked_rotor = 0;​
g_zero_ctr_status = 0; /* 堵塞状态需要重新定位初始位置 */​
}​
}​
#endif​
times_count = 0;​
}​
}​
}

在定时器1更新中断里首先优先进行电流环的PID计算,这里需要进行滤波,避免换向尖峰对PID的影响,接着通过过零跳变数量计算电机转速,经过计数分频,进入速度环PID计算函数,带入上述计算的速度,得到速度环计算出的PWM输出值,然后将两个环的输出做比较,如果电流环较大则使用速度环输出,否则使用电流环输出,最终赋值到定时器,在下一次中断时使用新的PWM用于驱动无刷电机。这里我们还在中断中加上了堵转处理,当电机因意外或过流导致停机时,当堵转时间超过2s,此时会重新初始化PID并以400RPM的初始速度重新开启电机旋转。接着看来先下主函数main.c内容如下。

int main(void)​
{​
uint16_t adc_vbus,adc_temp;​
uint8_t debug_cmd=0;​
float current_lpf[4]= {0.0f};​
uint8_t key,t;​
char buf[32];​
float current[3]= {0.0f};​
int16_t speed_diplay=0;​
float user_setpoint_temp=0.0;​

HAL_Init(); /* 初始化HAL库 */​
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */​
delay_init(168); /* 延时初始化 */​
usart_init(115200); /* 串口初始化为115200 */​
led_init(); /* 初始化LED */​
key_init(); /* 初始化按键 */​
lcd_init(); /* 初始化LCD */​
bldc_init(168000/18-1,0);​
bldc_ctrl(MOTOR_1,CW,1000); /* 初始无刷电机接口1速度 */​
adc_nch_dma_init();​
pid_init();​

g_point_color = WHITE;​
g_back_color = BLACK;​
lcd_show_string(10,10,200,16,16,"BLDC Motor Test",g_point_color);​
lcd_show_string(10,30,200,16,16,"KEY0:Step++",g_point_color);​
lcd_show_string(10,50,200,16,16,"KEY1:Step--",g_point_color);​
lcd_show_string(10,70,200,16,16,"KEY2:Stop",g_point_color);​

#if DEBUG_ENABLE /*开启调试*/​
debug_init(); /*PID调试初始化*/​
debug_send_motorcode(BLDC_MOTOR ); /*直流无刷电机*/​
debug_send_motorstate(IDLE_STATE); /*电机空闲*/​
/* 初始化同步数据(选择第x组PIDX,目标速度地址,P,I,D参数)到上位机 */​
debug_send_initdata(TYPE_PID1,user_setpoint,C_KP,C_KI,C_KD);​
debug_send_initdata(TYPE_PID2,user_setpoint,S_KP,S_KI,S_KD); ​
#endif​

while (1)​
{​
… … /* 省略部分代码 */​

key = key_scan(0);​
if(key == KEY0_PRES)​
{​
step_up();​
}​
else if(key == KEY1_PRES) /* 按下KEY1开启电机 */​
{​
step_down(); ​
}​
else if(key == KEY2_PRES) /* 按下KEY2关闭电机 */​
{​
stop_motor1(); /* 停机 */​
g_bldc_motor1.run_flag = STOP; /* 标记停机 */​
g_bldc_motor1.pwm_duty = 0;​
motor_pwm_sl = 0;​
motor_pwm_c = 0;​
g_zero_ctr_status = 0;​
g_bldc_motor1.speed = 0;​
pid_init(); /* 初始化PID */​
}​
delay_ms(10);​

#if DEBUG_ENABLE​
/* Debug发送部分 */​
/* 主要显示参数 */​
debug_send_valtage(adc_vbus * ADC2VBUS); /* 发送电压 */​
debug_send_speed(g_bldc_motor1.speed); /* 发送速度 */​
debug_send_distance((uint64_t)(g_bldc_motor1.sum_pos)); ​
debug_send_temp(50,get_temp(adc_temp)); /* 发送电机温度、驱动板温度 */​
debug_send_current((float)(current_lpf[0]/1000),​
(float)(current_lpf[0]/1000),​
(float)(current_lpf[0]/1000));/*发送电流*/​
/* 电流波形和速度波形 */​
debug_send_wave_data(1,(int16_t)g_bldc_motor1.speed); /*1发送实际速度​
debug_send_wave_data(2,(int16_t)*user_setpoint); /*2发送目标速度 */ ​
debug_send_wave_data(3,current_lpf[0]); /* 选择通道3 发送实际电流U */​
debug_send_wave_data(4,current_lpf[1]); /* 选择通道4 发送实际电流V */​
debug_send_wave_data(5,current_lpf[2]); /* 选择通道5 发送实际电流W */​
debug_send_wave_data(6,current_lpf[3]); /* 选择通道6 发送实际母线电流 */ ​
debug_send_wave_data(7,adc_amp_bus); /* 选择通道7 发送实际电流W */​
debug_send_wave_data(8,g_current_pid.SetPoint); /*8实际母线电流 */ ​
/* Debug接收部分 */​
debug_receive_pid(TYPE_PID1,(float*)&g_current_pid.Proportion, (float*)&g_current_pid.Integral,(float*)&g_current_pid.Derivative);​
debug_receive_pid(TYPE_PID2,(float*)&g_speed_pid.Proportion, (float*)&g_speed_pid.Integral,(float*)&g_speed_pid.Derivative);​
debug_cmd = debug_receive_ctrl_code(); /* 读取命令 */​
if(debug_cmd == HALT_CODE) /* 停机 */​
{​
stop_motor1();​
pid_init();​
g_bldc_motor1.run_flag = STOP; /* 标记停机 */​
g_bldc_motor1.pwm_duty = 0;​
}​
else if(debug_cmd == RUN_CODE) /* 运行 */​
{​
g_bldc_motor1.run_flag = RUN; /* 运行标记 */​
*user_setpoint = 400; /* 自动设置目标 */​
debug_data_temp = *user_setpoint;​
start_motor1(); /* 启动电机 */​
debug_send_motorstate(RUN_STATE); /* 电机运行 */​
}​
else if (debug_cmd == BREAKED) /* 刹车*/​
{​
*user_setpoint = 0; /* 减速直至0 */​
debug_send_motorstate(BREAKED_STATE); /* 电机刹车 */​
}​
#endif​
delay_ms(10);​
}​
}​
void step_up(void)​
{​
g_bldc_motor1.run_flag = RUN; /* 开启运行 */​
start_motor1(); /* 开启运行 */​

if(g_bldc_motor1.dir == CCW && *user_setpoint == 0) /* 切换方向条件*/​
{​
g_bldc_motor1.dir = CW; ​
}​
*user_setpoint += 400; /* 递增​
if(*user_setpoint == 0)​
{​
g_bldc_motor1.run_flag = STOP; /* 标记停机 */​
stop_motor1(); /* 停机 */​
pid_init(); /* 初始化PID */​
g_bldc_motor1.speed = 0;​
g_zero_ctr_status = 0;​
motor_pwm_sl = 0;​
motor_pwm_c = 0;​
g_bldc_motor1.pwm_duty = 0;​
}​
if(*user_setpoint >= 3200) /* 限速 */​
*user_setpoint = 3200;​
debug_data_temp = *user_setpoint;​
}​
void step_down(void)​
{​
g_bldc_motor1.run_flag = RUN; /* 开启运行*/​
start_motor1(); /* 运行电机*/​
/*切换方向条件*/​
if(g_bldc_motor1.dir == CW && *user_setpoint == 0)​
{​
g_bldc_motor1.dir = CCW; ​
}​
*user_setpoint -= 400; /* 递增 */​
if(*user_setpoint == 0)​
{​
g_bldc_motor1.run_flag = STOP; /* 标记停机*/​
stop_motor1(); /* 停机*/​
pid_init(); /* 初始化PID*/​
g_bldc_motor1.speed = 0;​
g_zero_ctr_status = 0;​
motor_pwm_sl = 0;​
motor_pwm_c = 0;​
g_bldc_motor1.pwm_duty = 0;​
}​
if(*user_setpoint <= -3200) /* 限速*/​
*user_setpoint = -3200;​
debug_data_temp = *user_setpoint;​
}

main函数主要初始化基本外设以及BLDC等等,然后添加上位机通信协议,将实际转速数据、目标转速数据、实际电流、目标电流分别发送给上位机的通道1、2、7、8上进行显示。按键判断逻辑:按下KEY0目标转速加400RPM,按下KEY1目标转速减400RPM,按下KEY2停止电机旋转

26.2.3 下载验证

下载代码后,可以看到LED0在闪烁,说明程序已经正常在跑了,LCD上显示按键功能、占空比以及电机速度信息和显示程序计算的电压、电流等信息,当我们按下KEY0,目标速度将增大;按下KEY1,目标速度将减小;按下KEY2,电机将停止。目标速度为正数时,电机正转,反之电机反转。我们再打开PID调试助手(注意接上串口1的USB_TTL连接至电脑),选择对应的串口端口,接着勾选通道1和2以及通道7和8,点击“开始”按钮,即可开始显示波形。大家可以使用手给电机增加点负载,可以明显感觉到电机一开始会因为负载增加速度减慢,后续逐渐加速上去直至和目标速度一致,如下图26.2.3.1,大家可以自己下载程序并点击按键测试效果。

《DMF407电机控制专题教程》第26章 无刷电机无感方波闭环控制_初始化_06


图26.2.3.1 PID调节效果

图26.2.3.1中,橙线代表目标速度,红线代表实际速度,紫线代表实际母线电流,粉线代表目标电流(由于PID初始化时已经设定了电流环的目标值,所以粉线为直线),当我们按下KEY0,目标速度增大,橙线先发生变化,而红线(实际速度)会逐渐靠近橙线(目标速度);可以看到紫线(实际电流)并未靠近粉线(目标电流),是由于代码中将两个环的输出做比较,如果电流环较大则使用速度环输出,否则使用电流环输出导致的。当我们手动给电机加负载,就能明显看到实际电流会在目标电流附近波动;按下KEY1,目标速度将减小,曲线的变化同理;按下KEY2,电机将停止,目标速度将为0。

注意:1、电流环的波形存在小幅振荡属于正常现象,如果希望波形更稳定,可以适当地调整PID系数以及增大滤波次数;2、滤波次数越多则系统的响应越慢,大家需要在系统的响应速度和稳定性之间寻找平衡点;3、PID系数并不是通用的,如果PID曲线不理想,大家需要根据自己的实际系统去调节。


举报

相关推荐

0 条评论