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_CTRLvolt_cmd = pid_controller(&pid_spd_volt, (speed_ramp.command - rotor_speed.filtered));
#elsecurrent.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;elsespeed_ramp.cmd_final = -MIN_CONTROL_SPEED;}command_ramp(&speed_ramp);#if defined WITHOUT_CURRENT_CTRLvolt_cmd = pid_controller(&pid_spd_volt, (speed_ramp.command - rotor_speed.filtered));
#elsecurrent.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_IDENTIFYcase 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;
#endifcase 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和驱动电机进行优化,但是按照现成的软件驱动实现的方案,成本还是高。
- 🌿软件实现,不方便移植。如果想将这套代码移植到其他品牌单片机上,几乎是行不通的。