各位,最近我做的项目要用到无刷电机,所以,我要写一个无刷电机的驱动程序。开始,我以为很简单,以我的水平和能力,2个多星期就能搞定,结果发现很难,从9月初开始搞,一直到最近才把电机转起来,真是不容易。
其间,我也学习了不少人的BLDC驱动的文章,也请教了不少人,但是,很抱歉,没有一篇文章和一个人真正的能够提供帮助,还是得自己来摸索和理解。正是因为发现没有一遍文章能够真正的提供帮助,所以,我打算来写个保姆级教程,教大家怎么样来驱动无刷电机。
1.这中间我看过的一些文章和请教过的人,在此对他们表示感谢。
【电机控制】六步法驱动BLDC电机,使用硬件COM事件,STM32+CUBEMX(HAL库)配置_st电机库 六步算法-CSDN博客
三相直流无刷电器驱动器(BLDC)_三相无刷电机驱动程序-CSDN博客
STM32 电机教程 13 - BLDC 电机转速计算_bldc转速检测stm32代码-CSDN博客
[科普] 无刷直流电机驱动控制原理图解_直流无刷电机驱动控制-CSDN博客
【2022项目复盘】无位置传感器的无刷直流电机驱动设计_eg3112-CSDN博客
STM32 电机教程 33 - 无刷电机无感控制快速实现_stm32 bldc 张十三-CSDN博客
【FOC无刷电机控制】六步换向、FOC,STM32cubemx从零开始搭建BLDC六步换相代码、FOC代码(基于霍尔传感器)_stm32接霍尔传感器foc-CSDN博客
https://github.com/kingxin/6Step
三相直流无刷电机驱动设计: 基于STM32的三相无刷直流电机驱动设计
(还有几套代码,我暂时找不到链接了,等找到再补上)
(对EG公司的李卫伟工表示感谢,对我的朋友杨峰表示感谢,对安富利的邓工和Cavin表示感谢,尤其是Cavin,真的是电机控制的专家,对王大可表示感谢,没错,就是上面开源了的王大可,感谢电机公司的罗工和zyj工)
2. 好了,直接上教程
2.1 用STM32CubeMX进行配置。
我用的是timer8产生PWM波,用Timer3做hall信号的采样
Timer3就是这么简单。
Timer8,就是这么简单。
时钟配成170MHz,其他没有啥了。
这里有个重中之重,要说明一下,就是,我的PWM的互补输出是配置成反的。因为我的预驱芯片用的EG2131,这块片子的低边驱动是低有效。【这是我花费了很多时间,才搞对,而请教的所有人都不得要领,没有一个人觉得这里重要,我问预驱逻辑,他们都很奇怪的看着我,说,跟预驱逻辑有啥关系?结果还真跟这里的逻辑有关系。当然,EG公司的李工给了我正确的解答,说要反过来,但是他不用stm32, 不懂怎么配,所以,也不得要领。最后,还是我自己摸对了】
2.2 生成代码后就是要编程了。我是用vscode来编辑的,所以,生成代码时,我选的是CMake。
重点程序片段是:
/* start 6channels PWM */
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_3);
/* start hallsensor timer interrupt */
__HAL_TIM_ENABLE_IT(&htim3, TIM_IT_UPDATE);
HAL_TIMEx_HallSensor_Start_IT(&htim3);
uint8_t get_hall_pos(void)
{
uint8_t hallpos = 0;
uint8_t hallpos_u = 0;
uint8_t hallpos_v = 0;
uint8_t hallpos_w = 0;
hallpos_u = HAL_GPIO_ReadPin(M2_H1_GPIO_Port, M2_H1_Pin);
hallpos_v = HAL_GPIO_ReadPin(M2_H2_GPIO_Port, M2_H2_Pin);
hallpos_w = HAL_GPIO_ReadPin(M2_H3_GPIO_Port, M2_H3_Pin);
hallpos = (hallpos_u<<2) | (hallpos_v<<1) | hallpos_w;
return hallpos;
}
下面是一半的换相逻辑,基本参考kingxin的代码,然后我摸索出来的。
void motor_change(uint8_t hallpos, uint8_t direction)
{
/* direction 0 cw, direciton 1 ccw */
if(direction==0) // direction 0 cw
{
switch (hallpos)
{
case 5/* constant-expression */:
/* UH + VL */
/* channel1 UH pwm, UL off */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH1N);
/* channel2 VH off, VL on */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_ACTIVE);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH2N);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH2);
/* channel3 WH OFF, WL OFF */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_INACTIVE);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH3);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH3N);
break;
case 4:
/* UH + WL */
/* channel1 UH pwm, UL off */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH1N);
/* channel2 VH off, VL off */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_INACTIVE);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH2);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH2N);
/* channel3 WH OFF, WL ON */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_ACTIVE);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH3N);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH3);
break;
case 6:
/* VH + WL */
/* channel1 UH off, UL off */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_INACTIVE);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1N);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH1);
/* channel2 VH pwm, VL off */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_PWM1);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH2);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH2N);
/* channel3 WH OFF, WL ON */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_ACTIVE);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH3N);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH3);
break;
case 2:
/* VH + UL */ // 6
/* channel1 UH off, UL on */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_INACTIVE);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1N);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1);
/* channel2 VH pwm, VL off */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_PWM1);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH2);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH2N);
/* channel3 WH off, WL off */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_INACTIVE);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH3);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH3N);
break;
case 3:
/* WH + UL */
/* channel1 UH off, UL on */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_INACTIVE);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1N);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1);
/* channel2 VH off, VL off */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_INACTIVE);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH2);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH2N);
/* channel3 WH pwm, WL off */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_PWM1);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH3);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH3N);
break;
case 1:
/* WH + VL */
/* channel1 UH off, UL off */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_INACTIVE);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH1N);
/* channel2 VH off, VL on */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_ACTIVE);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH2N);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH2);
/* channel3 WH pwm, WL off */
LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_PWM1);
LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH3);
LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH3N);
break;
default:
usb_printf("===== hall pos error =====\r\n");
break;
}
}
下面是中断服务函数,换相和速度计算都是在这里完成。换相的思路很简单,就是timer3会通过xor在hall信号的每一个跳变产生一个计时的update中断,在这个中断服务函数里进行换相和改变占空比。timer8专门产生PWM波,跟换相没什么关系。 速度计算很容易,我的电机是5对极,转一圈是30个hall跳变,也就是30个换相。我计300个换相也就是10圈的时间,然后换算成rpm就行了。跟电机实际转的情况对了一下,计算的转速,基本还是准的。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
uint8_t my_hall_pos = 0;
uint8_t my_pre_hall_pos = 0;
if(htim->Instance==TIM3)
{
if(motor_status==2) // motor is running
{
my_hall_pos = get_hall_pos();
if(my_pre_hall_pos!=my_hall_pos)
{
my_pre_hall_pos=my_hall_pos;
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, (uint32_t)(period/1.2));
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, (uint32_t)(period/1.8));
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, (uint32_t)(period/2.5));
motor_change(my_hall_pos, motor_direction);
if(motor_speed_cnt==0)
{
systick1 = HAL_GetTick();
}
motor_speed_cnt++;
if(motor_speed_cnt==300)
{
systick2 = HAL_GetTick();
motor_speed_cnt = 0;
usb_printf("motor speed is %d rpm\r\n", (int)( ((float)10/(systick2-systick1))*1000*60) );
}
}
else
{
/* do nothing */
}
}
else
{
usb_printf(" Error!!! motor is not running\r\n");
}
}
}
基本就是这些重要的内容了,但是,无刷电机的驱动,还是比较复杂的,我上面给出的重要内容,还是需要小伙伴们边做边思考才能最终做出来的。但是,我相信,大家一定能够做出来。
好了,大功告成,亲个嘴儿。