|
本帖最后由 正点原子 于 2022-8-22 18:04 编辑
1)实验平台:正点原子DMF407电机开发板
2)平台购买地址: https://detail.tmall.com/item.htm?&id=677230699323
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/stm32dj/ATK-DMF407.html
4)对正点原子电机开发板感兴趣的同学可以加群讨论: 592929122
第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_CH2:PA9
TIM1_CH3:PA10
IO:PB13\14\15
使能引脚:SHDN: PF10
4)过零信号检测引脚
过零U相 – PH10
过零V相 – PH11
过零W相 – PH12
5)无刷电机
6)无刷电机驱动板
7)12-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 程序流程图
图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 = g_adc_val[i+2];
adc_amp = adc_val_m1 –
adc_amp_offset[ADC_AMP_OFFSET_TIMES];
if(adc_amp < 0)
adc_amp_un = 0; /* 反电动势电压为悬空绕组直接清0 */
else if(adc_amp >= 0) /* 去除反电动势引起的负电流数据 */
adc_amp_un = adc_amp;
}
/*运算母线电流(母线电流为任意两个有开关动作的相电流之和)*/
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];
avg[1] += adc_amp_offset[1];
avg[2] += adc_amp_offset[2];
}
for(i=0; i<3; i++)
{
avg /= ADC_AMP_OFFSET_TIMES;
adc_amp_offset[ADC_AMP_OFFSET_TIMES] = avg;
}
}
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,大家可以自己下载程序并点击按键测试效果。
图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_CH2:PA9
TIM1_CH3:PA10
IO:PB13\14\15
使能引脚:SHDN: PF10
4)过零信号检测引脚
过零U相 – PH10
过零V相 – PH11
过零W相 – PH12
5)无刷电机
6)无刷电机驱动板
7)12-60V的DC电源
8)ADC:ADC1_CH9:PB1
ADC1_CH0:PA0
ADC1_CH8:PB0
ADC1_CH6:PA6
ADC1_CH3:PA3
3、原理图
图26.2.1.1 无刷电机接口1原理图
接口涉及的IO如下。
表26.2.1.1 无刷相关IO口说明
驱动板和电机的连接方式之前已经介绍过了,驱动板和开发板连接只需要使用排线连接即可(霍尔传感器接口可不接,JP3跳线帽注意接H&Z与ZERO即可),实物连接如下。
图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 程序流程图
图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];
avg[1] += adc_amp_offset[1];
avg[2] += adc_amp_offset[2];
}
for(i=0; i<3; i++)
{
avg /= ADC_AMP_OFFSET_TIMES;
adc_amp_offset[ADC_AMP_OFFSET_TIMES] = avg;
}
}
/***************** 定时判断电机是否发生堵塞 *******************/
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,大家可以自己下载程序并点击按键测试效果。
图26.2.3.1 PID调节效果
图26.2.3.1中,橙线代表目标速度,红线代表实际速度,紫线代表实际母线电流,粉线代表目标电流(由于PID初始化时已经设定了电流环的目标值,所以粉线为直线),当我们按下KEY0,目标速度增大,橙线先发生变化,而红线(实际速度)会逐渐靠近橙线(目标速度);可以看到紫线(实际电流)并未靠近粉线(目标电流),是由于代码中将两个环的输出做比较,如果电流环较大则使用速度环输出,否则使用电流环输出导致的。当我们手动给电机加负载,就能明显看到实际电流会在目标电流附近波动;按下KEY1,目标速度将减小,曲线的变化同理;按下KEY2,电机将停止,目标速度将为0。
注意:1、电流环的波形存在小幅振荡属于正常现象,如果希望波形更稳定,可以适当地调整PID系数以及增大滤波次数;2、滤波次数越多则系统的响应越慢,大家需要在系统的响应速度和稳定性之间寻找平衡点;3、PID系数并不是通用的,如果PID曲线不理想,大家需要根据自己的实际系统去调节。 |
阿莫论坛20周年了!感谢大家的支持与爱护!!
如果天空是黑暗的,那就摸黑生存;
如果发出声音是危险的,那就保持沉默;
如果自觉无力发光,那就蜷伏于牆角。
但是,不要习惯了黑暗就为黑暗辩护;
也不要为自己的苟且而得意;
不要嘲讽那些比自己更勇敢的人。
我们可以卑微如尘土,但不可扭曲如蛆虫。
|