AT32F421驱动BLDC 配合上位机控制与调参
- 🔧AT32 电机控制与调参上位机软件:
ArteryMotorMonitor
:https://www.arterytek.com/cn/support/motor_control.jsp?index=0
- 🌿测试电机参数:2204-12N14P,无感BLDC,极对数7
- 📘测试工程:
bldc_1shunt_sensorless
(在上面页可以下载到AT32F421_MC_Library_Project_V2.1.2
)
👉 个人也是初始使用该调参上位机软件,在探索使用过程中,遇到不少问题,例如状态不正常,电机不转等问题。建议初次使用先仔细阅读相关文档和原理图,如果使用官方提供的驱动板可以略过,直接参照文档测试即可,对于采用自制驱动板的需要参考官方的原理图进行连接,软件工程也需要做相对应修改。
-
🍁官方提供的驱动原理图部分参考:
AT-MOTOR-EVB-V2.0_SCH.pdf
(个人采用的EG2133,和官方使用的驱动芯片功能一样,下桥臂低电平开启)
-
🌿控制引脚:(参考
AT-MOTOR-EVB-V2.0_SCH.pdf
原理图)
==== 3相PWM控制 ====
U 上桥臂 PA8
V 上桥臂 PA9
W 上桥臂 PA10
----------------
U 下桥臂 PB13
V 下桥臂 PB14
W 下桥臂 PB15
BRK 刹车 PB12 PB8
------------
==== HALL ====
PB4
PB5
PB0
==== 比较器反电动势检测 ====
PA5
PA6
PA7
=== VBUS电压 ====
PA4
--- 温度 ---
PB1
🛠工程参数设定
- 🔖电机主要参数在设置文件
motor_control_drive_param.h
中配置:
- 🌿驱动电机参数:
/* choose sensor */
//#define HALL_SENSORS //带霍尔传感器检测
#define SENSORLESS //无传感器控制的模式(通用)
#define BLDC_SENSORLESS_ADC //六步方波无传感器控制以 ADC 检测反电势的模式
//#define BLDC_SENSORLESS_COMP //六步方波无传感器控制以比较器检测反电势的模式
.......
/* Motor parameters */
#define RS_LL (1.89) /* Stator resistance, ohm 电机线间电阻值(unit: Ω)*/
#define LS_LL (0.002387) /* Stator inductance, H 电机线间电感值(unit: H)*/
#define POLE_PAIRS (14/2) //电机极对数
#define KE (1.0) /* Back EMF constant(line-to-line, peak voltage), V/rpm 电机 KE 值(unit: V/rpm)*/
#define NOMINAL_CURRENT (1.7) //电机额定电流 (unit: ampere)
- 🌿反电势检测模式:(决定调参控制上位机软件控制界面)
/* choose how to start up */
//#define INIT_ANGLE_STARTUP //无传感器之初始角度检测启动(通用)
#define ALIGN_AND_GO_STARTUP //无传感器之对齐启动(通用)
//#define OPENLOOP_STARTUP //无传感器之开环启动(通用)
📗启动步骤和条件
- 以下内容和官方提供的
AT32F421_MC_Library_Project
中相关文档说明基本相同。
-
- 加载工程配置
- 加载工程配置
-
- 打开对应串口,连接目标板串口1引脚(PB6/Tx1,PB7/Rx1)
- 打开对应串口,连接目标板串口1引脚(PB6/Tx1,PB7/Rx1)
-
- 按下播放键(4.)即可周期性更新 UI 接口的数据以及与目标板通过串口实时通信, 如发送启动/停止电机
的命令、实时调速、调试电流 PID 参数以及监控参数绘制波形等。
- 按下播放键(4.)即可周期性更新 UI 接口的数据以及与目标板通过串口实时通信, 如发送启动/停止电机
-
- 发波启动条件:
ESC_STATE_SAFTY_READY
:
- 发波启动条件:
- 🌟如果不是使用的官方的驱动板,像我采用自制的驱动板,硬件参数不一样,可以在对应的驱动程序中,对相关参数。进行修改或检测内容进行屏蔽。(屏蔽位置:滴答定时器回调中
SysTick_Handler
)
void SysTick_Handler(void)
{
int16_t sp_value = 0;
flag_status mode_switch1 = SET;
sp_value = adc_in_tab[ADC_POTENTIO_IDX] - SP_OFFSET;
mode_switch1 = gpio_input_data_bit_read(MODE1_BUTTON_PORT, MODE1_BUTTON_PIN);
switch(esc_state_old)
{
case ESC_STATE_IDLE:
if(ctrl_source == CTRL_SOURCE_EXTERNAL)
{
if((sp_value <= 0) && (param_initial_rdy == SET) && (curr_offset_rdy == SET))
{
esc_state = ESC_STATE_SAFETY_READY;
}
else
{
esc_state = ESC_STATE_IDLE;
}
}
else
{
if((param_initial_rdy == SET) && (curr_offset_rdy == SET))
{
esc_state = ESC_STATE_SAFETY_READY;
}
else
{
esc_state = ESC_STATE_IDLE;
}
}
break;
case ESC_STATE_SAFETY_READY:
if(ctrl_mode == ID_MANUAL_TUNE || ctrl_mode == IQ_MANUAL_TUNE)
{
if(start_stop_btn_flag == SET)
{
esc_state = ESC_STATE_I_TUNE;
}
}
else if(ctrl_mode == SPEED_CTRL)
{
if(ctrl_source == CTRL_SOURCE_EXTERNAL)
{
if(sp_value >= SP_RUN_POINT)
{
if (mode_switch1 == SET) /* CCW*/
{
hall_ccw_ctrl_para();
rotor_speed.dir = CCW;
current.volt_sign = -1;
}
else /* CW*/
{
hall_cw_ctrl_para();
rotor_speed.dir = CW;
current.volt_sign = 1;
}
esc_state = ESC_STATE_ANGLE_INIT;
}
else
{
esc_state = ESC_STATE_SAFETY_READY;
}
}
else if(ctrl_source == CTRL_SOURCE_SOFTWARE)
{
if((speed_ramp.cmd_final != 0) && (start_stop_btn_flag == SET))
{
if(speed_ramp.cmd_final > 0) /* CW*/
{
hall_cw_ctrl_para();
rotor_speed.dir = CW;
current.volt_sign = 1;
}
else if(speed_ramp.cmd_final < 0) /* CCW*/
{
hall_ccw_ctrl_para();
rotor_speed.dir = CCW;
current.volt_sign = -1;
}
esc_state = ESC_STATE_ANGLE_INIT;
}
else
{
esc_state = ESC_STATE_SAFETY_READY;
}
}
}
else if(ctrl_mode == TORQUE_CTRL)
{
if(ctrl_source == CTRL_SOURCE_EXTERNAL)
{
if(sp_value >= SP_RUN_POINT)
{
if (mode_switch1 == SET) /* CCW*/
{
hall_ccw_ctrl_para();
rotor_speed.dir = CCW;
current.volt_sign = -1;
}
else /* CW*/
{
hall_cw_ctrl_para();
rotor_speed.dir = CW;
current.volt_sign = 1;
}
esc_state = ESC_STATE_ANGLE_INIT;
}
else
{
esc_state = ESC_STATE_SAFETY_READY;
}
}
else if(ctrl_source == CTRL_SOURCE_SOFTWARE)
{
if((current.Ibus.Iref != 0) && (start_stop_btn_flag == SET))
{
if(current.Ibus.Iref > 0) /* CW*/
{
hall_cw_ctrl_para();
rotor_speed.dir = CW;
current.volt_sign = 1;
}
else if(current.Ibus.Iref < 0) /* CCW*/
{
hall_ccw_ctrl_para();
rotor_speed.dir = CCW;
current.volt_sign = -1;
}
esc_state = ESC_STATE_ANGLE_INIT;
}
else
{
esc_state = ESC_STATE_SAFETY_READY;
}
}
}
else if(ctrl_mode == OPEN_LOOP_CTRL)
{
if(ctrl_source == CTRL_SOURCE_EXTERNAL)
{
if(sp_value >= SP_RUN_POINT)
{
openloop.period_ref = openloop.olc_init_period;
openloop.volt_ref = openloop.olc_init_volt;
esc_state = ESC_STATE_STARTING;
}
else
{
esc_state = ESC_STATE_SAFETY_READY;
}
}
else if(ctrl_source == CTRL_SOURCE_SOFTWARE)
{
if(start_stop_btn_flag == SET)
{
openloop.period_ref = openloop.olc_init_period;
openloop.volt_ref = openloop.olc_init_volt;
esc_state = ESC_STATE_STARTING;
}
else
{
esc_state = ESC_STATE_SAFETY_READY;
}
}
}
if(I_auto_tune.state_flag == PROCESSING)
{
current_auto_tuning(&I_auto_tune);
set_current_pid_param(&I_auto_tune, &pid_is);
I_auto_tune.state_flag = SUCCEED;
}
break;
case ESC_STATE_ANGLE_INIT:
/* Determine the initial position at the beginning(BLDC sensor-less) */
angle_init_func();
/* Stop motor */
if((ctrl_source == CTRL_SOURCE_SOFTWARE && start_stop_btn_flag == RESET) ||
(ctrl_source == CTRL_SOURCE_EXTERNAL && sp_value <= SP_STOP_POINT))
{
esc_state = ESC_STATE_FREE_RUN;
}
break;
case ESC_STATE_STARTING:
if(ctrl_mode == OPEN_LOOP_CTRL)
{
esc_state = ESC_STATE_RUNNING;
}
else
{
if (sense_hall_steps < SENSE_HALL_TIMES)
{
sys_counter++;
if (sys_counter >= REBOOT_PERIOD_MS)
{
esc_state = ESC_STATE_SAFETY_READY;
}
}
else
{
esc_state = ESC_STATE_RUNNING;
start_state = START_STATE_STABLE_RUN;
sys_counter = 0;
/* Set parameter for switching to closed-loop control */
rdy_to_close_loop_param();
}
}
/* Stop motor */
if((ctrl_source == CTRL_SOURCE_SOFTWARE && start_stop_btn_flag == RESET) ||
(ctrl_source == CTRL_SOURCE_EXTERNAL && sp_value <= SP_STOP_POINT))
{
esc_state = ESC_STATE_FREE_RUN;
}
break;
case ESC_STATE_RUNNING:
if(ctrl_mode == SPEED_CTRL)
{
if(ctrl_source == CTRL_SOURCE_EXTERNAL)
{
if (sp_value <= SP_STOP_POINT)
{
speed_ramp.cmd_final = 0;
if(rotor_speed.filtered <= MIN_CONTROL_SPEED)
{
esc_state = ESC_STATE_FREE_RUN;
}
}
else
{
if (mode_switch1 == SET) /* CCW*/
{
if (sp_value >= SP_RUN_POINT)
{
speed_ramp.cmd_final = -MIN_CONTROL_SPEED - (((sp_value << 1) * SP_TO_SPD_CMD) >> 15);
}
else
{
speed_ramp.cmd_final = -MIN_CONTROL_SPEED;
}
}
else /* CW*/
{
if (sp_value >= SP_RUN_POINT)
{
speed_ramp.cmd_final = MIN_CONTROL_SPEED + (((sp_value << 1) * SP_TO_SPD_CMD) >> 15);
}
else
{
speed_ramp.cmd_final = MIN_CONTROL_SPEED;
}
}
}
command_ramp(&speed_ramp);
#if defined WITHOUT_CURRENT_CTRL
volt_cmd = pid_controller(&pid_spd_volt, (speed_ramp.command - rotor_speed.filtered));
#else
current.Ibus.Iref = pid_controller(&pid_spd, (speed_ramp.command - rotor_speed.filtered));
#endif
}
else if(ctrl_source == CTRL_SOURCE_SOFTWARE)
{
if (start_stop_btn_flag == RESET)
{
esc_state = ESC_STATE_FREE_RUN;
}
else
{
if(abs(speed_ramp.cmd_final) < MIN_CONTROL_SPEED)
{
if(rotor_speed.dir == CW)
speed_ramp.cmd_final = MIN_CONTROL_SPEED;
else
speed_ramp.cmd_final = -MIN_CONTROL_SPEED;
}
command_ramp(&speed_ramp);
#if defined WITHOUT_CURRENT_CTRL
volt_cmd = pid_controller(&pid_spd_volt, (speed_ramp.command - rotor_speed.filtered));
#else
current.Ibus.Iref = pid_controller(&pid_spd, (speed_ramp.command - rotor_speed.filtered));
#endif
}
}
}
else if(ctrl_mode == TORQUE_CTRL)
{
if(ctrl_source == CTRL_SOURCE_EXTERNAL)
{
if (sp_value <= SP_STOP_POINT)
{
current.Ibus.Iref = 0;
if(rotor_speed.filtered <= MIN_CONTROL_SPEED)
{
esc_state = ESC_STATE_FREE_RUN;
}
}
else if (sp_value >= SP_RUN_POINT)
{
if (mode_switch1 == SET) /* CCW*/
{
current.Ibus.Iref = -(sp_value * SP_TO_I_CMD) >> 15;
}
else /* CW*/
{
current.Ibus.Iref = (sp_value * SP_TO_I_CMD) >> 15;
}
}
}
else if(ctrl_source == CTRL_SOURCE_SOFTWARE)
{
if (start_stop_btn_flag == RESET)
{
esc_state = ESC_STATE_FREE_RUN;
}
else
{
esc_state = ESC_STATE_RUNNING;
}
}
}
else if(ctrl_mode == OPEN_LOOP_CTRL)
{
if(ctrl_source == CTRL_SOURCE_EXTERNAL)
{
if(sp_value <= SP_STOP_POINT)
{
esc_state = ESC_STATE_FREE_RUN;
}
else
{
closeloop_rdy = RESET;
/* 5 ms inc openloop volt & spd */
open_loop_cmd_ramp(&openloop);
}
}
else if(ctrl_source == CTRL_SOURCE_SOFTWARE)
{
if (start_stop_btn_flag == RESET)
{
esc_state = ESC_STATE_FREE_RUN;
}
else
{
closeloop_rdy = RESET;
/* 5 ms inc openloop volt & spd */
open_loop_cmd_ramp(&openloop);
}
}
}
break;
case ESC_STATE_FREE_RUN:
if (rotor_speed.filtered == 0)
{
esc_state = ESC_STATE_SAFETY_READY;
}
else
{
esc_state = ESC_STATE_FREE_RUN;
}
break;
case ESC_STATE_BRAKING:
break;
case ESC_STATE_ERROR:
if(error_code == MC_NO_ERROR)
{
esc_state = ESC_STATE_IDLE;
}
break;
case ESC_STATE_ENC_ALIGN:
break;
case ESC_STATE_I_TUNE:
if (start_stop_btn_flag == SET)
{
I_tune_manual();
}
else
{
current.Ibus.Iref = 0;
esc_state = ESC_STATE_FREE_RUN;
}
break;
case ESC_STATE_AUTO_LEARN:
break;
#ifdef MOTOR_PARAM_IDENTIFY
case ESC_STATE_WINDING_PARAM_ID:
if (motor_param_ident.state_flag == PROCESSING)
{
param_id_process(&motor_param_ident);
}
else
{
if (motor_param_ident.Ls.f <= 0 || motor_param_ident.Rs.f <= 0)
{
motor_param_ident.state_flag = FAILED;
error_code |= error_code_mask & MC_PARAM_IDENT_ERROR;
motor_param_ident.Rs.f = motor_param_ident.Rs_Old.f;
motor_param_ident.Ls.f = motor_param_ident.Ls_Old.f;
}
else
{
motor_param_ident.state_flag = SUCCEED;
motor_param_ident.Rs_Old.f = motor_param_ident.Rs.f;
motor_param_ident.Ls_Old.f = motor_param_ident.Ls.f;
}
tmr_output_enable(PWM_ADVANCE_TIMER, FALSE);
disable_pwm_timer();
tmr_pwm_init();
adc_preempt_config();
PWM_ADVANCE_TIMER->cval = 0;
enable_pwm_timer();
esc_state = ESC_STATE_FREE_RUN;
}
break;
#endif
case ESC_STATE_NONE:
break;
}
fMosTemperature = (((adc_in_tab[ADC_MOS_TEMP_IDX] * ADC_REFERENCE_VOLT / ADC_DIGITAL_SCALE_12BITS) - V0_V) / dV_dT) + T0_C;
ui_wave_param.iMosTemperature_meas = (int16_t)(fMosTemperature * 100);
ui_wave_param.iBusVoltage_meas = (int16_t)(adc_in_tab[ADC_BUS_VOLT_IDX]);
ui_wave_param.speed_meas_filter_pu = (int16_t)(rotor_speed.filtered * RPM_TO_SPEED_PU >> 15);
ui_wave_param.speed_reference_pu = (int16_t)(speed_ramp.command * RPM_TO_SPEED_PU >> 15);
adc_sample.emf.emf_half_vdc_val = (int16_t)(adc_in_tab[ADC_BUS_VOLT_IDX] * EMF_HALF_VDC_GAIN);
zcp_highspd_fall = (adc_sample.emf.emf_half_vdc_val) + (adc_sample.emf.emf_high_spd_offset_falling);
zcp_highspd_rise = (adc_sample.emf.emf_half_vdc_val) + (adc_sample.emf.emf_high_spd_offset_rising);
#if 1
/* Over/under voltage protection 屏蔽过电压*/
// if (adc_in_tab[ADC_BUS_VOLT_IDX] < UNDERVOLTAGE_THRESHOLD_d)
// {
// error_code |= error_code_mask & MC_UNDER_VOLT_ERROR;
// }
// else if (adc_in_tab[ADC_BUS_VOLT_IDX] > OVERVOLTAGE_THRESHOLD_d)
// {
// error_code |= error_code_mask & MC_OVER_VOLT_ERROR;
// }
/* MOS Temperature protection 屏蔽温度*/
// if (adc_in_tab[ADC_MOS_TEMP_IDX] > TEMPERATURE_THRESHOLD_d)
// {
// error_code |= error_code_mask & MC_OVER_TEMP_ERROR;
// }
#endif
/* Enter error state handler */
if (error_code != MC_NO_ERROR)
{
esc_state = ESC_STATE_ERROR;
}
}
- 🌿AT32官方驱动板提供的母线电压采集电路:
- 🌿AT32官方MOS管温度采集电路:
🛠调参步骤
开环控制模式下调参:(✨推荐配合可调电源,能直观的看到电机在运转时,电流大小。)
调参原则:先小,然后逐渐递增。
- 🌿在电机能转动情况下,从初始速度开始调。
- 🌿电机能平稳启动后,调节最终的速度 ,最快速度时,电机运转不卡顿为止。
- 🌿调节循环周期,电机转动流畅,电流最小。
- 📐将最终整定后的参数,修改到工程中:
- 👉需要注意,部分参数不是直接照抄,有偏置补偿的。(修改后,重新烧录程序,再连接上位机软件,可以查看到修改后的参数以及运行效果)
- 🌿其他运行模式,进行调参方式,类似操作即可。
📒其他相关控制电路
- 官方提供的外部定位器调试电路:
AT32官方提供的例程并不适合新手移植,不管是从AT32提供的开源驱动硬件还是软件实现代码,都是比较复杂的。成本比较高。
- 🌿驱动硬件,从驱动板原理图可以看出,设计的物料比较多和控制方法相对比较复杂。虽然可以针对不同mcu和驱动电机进行优化,但是按照现成的软件驱动实现的方案,成本还是高。
- 🌿软件实现,不方便移植。如果想将这套代码移植到其他品牌单片机上,几乎是行不通的。