本文记录博主学习BLDC控制软件的过程。
本文是博主第一次做BLDC控制实验,内容只是写代码让BLDC转起来,而暂时不考虑其他复杂的需求。
控制的方法是比较简单的六步换相法。利用电机内部的霍尔传感器,通过参考电机厂家给出的二二导通换相表,生成方波来控制电机。第一次实验为了简化需求,只考虑开环、定速、正转的情况:
所以,实验目标为,通过按下开发板上的Key1按键使BLDC低速运转,按下Key2按键使BLDC停止运转。
本实验需要BLDC六步换相的理论作为知识储备。
博主根据自己的工作习惯和经验,制订出实验的软件架构如下:
其中,各个板块的含义如下:
对于架构中不同的板块,博主也根据经验选择合适的工具链进行开发。
本章节会结合原理图,研究配置CubeMX生成底层代码。
博主用的开发板是STM32F405RGT6,在CubeMX中将时钟源设置为外部晶振。
时钟数配置如下图。
由于后续的低频任务会运行在系统定时器中断里,所以这里需要留意一下系统定时器的频率为168MHz,也就是1秒钟计数168000000次。
在本实验中,开发板上的按键是用于告诉单片机启动或停止电机的请求。开发板上焊接了5个按键模块,如下图。本实验只需要用SW1和SW2两个。
以SW1为例,它的左端接地,右端连接到单片机的PC13引脚,如下图。
所以在CubeMX中配置PC13引脚的模式为输入模式,并配置为上拉。
这样的配置表示,PC13引脚接收到低电平时,按键SW1被按下,接收到高电平时,按键SW1没有被按下。SW2的按键引脚配置类似。
串口通信可以将单片机运行过程中的全局变量发送到PC上,然后通过串口调试助手读取信息,是一种验证问题的常规方式。
从原理图可以看出,PB10和PB11引脚是串口3发送和接收的引脚。
首先将串口模式设置为Asynchronous,也就是异步通信。
然后是一些参数设置,包括波特率、字长度等。这里的设置将和PC上的串口调试助手相对应。
最后是NVIC中断优先级设置,这里暂时设置为如下图。
六步换相法控制BLDC需要根据霍尔传感器信号输入来判断导通哪些MOS管,因此需要配置3个引脚来分别接收A相,B相,C相三个霍尔信号。
从原理图中可以看出,PB6,PB7,PB8这三个引脚分别对应着A相,B相,C相,所以接下来需要在CubeMX中配置这三个引脚。以PB6为例,在CubeMX中如下。
首先,GPIO_EXTI表示用于GPIO的外部中断。GPIO mode配置成了上升或下降沿触发的外部中断,也就是说,当电机转动导致了霍尔信号变化的时候,会触发外部中断。这一点很重要,因为在该中断回调函数里,会执行换相逻辑。
STM32通过控制引脚输出PWM或GPIO电平,来导通MOS管,从而实现了BLDC的换相控制。由于采用了H PWM-L ON的控制方式,上桥由PWM驱动,下桥由高低电平驱动。
以A相为例,原理图如下。左边的TIM1_CH是PWM,TIM1_CHN是高低电平。
然后顺藤摸瓜找到对应的引脚,TIM1_CH对应PA8,TIM1_CHN对应PB13。
接着就可以在CubeMX分别配置PWM输出和GPIO输出。
这里博主将上桥PWM配置为Up向上计数,重装载值为12000。也就是说后面进行输出比较的时候,范围是0~12000。
下桥的GPIO配置为推挽输出,初始为低电平。
以上配置完成后,就可以生成Keil工程了。
本章节会在Keil中手写一些代码,作为两方面用处。一方面是为应用层软件配置好输入输出接口函数,另一方面是调用Hal库函数对底层进一步配置。
在main文件的初始化代码段加上如下函数:
HAL_SYSTICK_Config(168000U);
该函数用于配置系统定时器重装载值为168000。结合前文将系统时钟配置为168MHz,重装载值配置为168000就意味着通过系统定时器产生1ms中断。在中断函数SysTick_Handler中将执行低频任务。
在main文件中配置如下按键输入接口函数,调用该函数可以返回按键状态(1或0)。
// Get Key State
uint8_t Get_Key1State(void)
{
return((uint8_t)(!HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_13)));
}
uint8_t Get_Key2State(void)
{
return((uint8_t)(!HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_0)));
}
博主习惯于将输入的接口函数以Get_开头,其实相当于封装了一层统一的命名,并简化传参。这里的Get函数中调用了Hal库的读取GPIO pin脚的函数,并对返回值取反,使得返回1代表按键按下,0代表按键没有按下。
在main文件中重新定义fputc 函数:
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xFFFF);
return (ch);
}
通过这种方式,就可以用printf函数将全局变量打印到PC上。本文的实验比较简单,没有做UI上位机,就用串口调试助手查看电机运行的实时数据了。
在main文件中配置霍尔信号状态接收函数,调用该函数可以返回霍尔传感器状态(1或0)。
// Get Hall State
uint8_t Get_HallAState(void)
{
return HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6);
}
uint8_t Get_HallBState(void)
{
return HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7);
}
uint8_t Get_HallCState(void)
{
return HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8);
}
应用层调用霍尔信号状态函数可以获取当前的A,B,C三相的霍尔传感器状态,然后查阅换相表得到导通的MOS管。
在main文件中配置MOS管导通控制函数,调用该函数可以设置MOS管的开关或者PWM。
// Mos
void Set_T1PWM(uint32_t PWMValue)
{
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,PWMValue);
}
void Set_T3PWM(uint32_t PWMValue)
{
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_2,PWMValue);
}
void Set_T5PWM(uint32_t PWMValue)
{
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_3,PWMValue);
}
void Set_T2Status(uint8_t State)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13,(GPIO_PinState)State);
}
void Set_T4Status(uint8_t State)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14,(GPIO_PinState)State);
}
void Set_T6Status(uint8_t State)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15,(GPIO_PinState)State);
}
其中,T1,T3,T5是上桥,在CubeMX配置为定时器PWM,因此传入的是输出比较值;T2,T4,T6是下桥,配置为GPIO输出,因此传入0或1的状态,来控制导通与断开。这里传入的PWMValue是0~12000,因为CubeMX中配置重装载值为12000。
在main文件中配置电机启停函数,调用该函数可以启动或停止定时器通道,从而达到启停电机的效果。
// Set_StartStopMotor
void Set_StartStopMotor(uint8_t State)
{
if(State == 0) //Stop Motor
{
HAL_TIM_PWM_Stop(&htim1,TIM_CHANNEL_1);
HAL_TIM_PWM_Stop(&htim1,TIM_CHANNEL_2);
HAL_TIM_PWM_Stop(&htim1,TIM_CHANNEL_3);
}
else
{
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_3);
}
}
Set函数会根据传入的State参数来判断调用Stop或是Start的Hal库函数。
本实验的电机控制应用层软件采用Simulink建模并生成代码。
Matlab/Simulink软件的灵活性很强,可以定制化生成各式各样的代码。博主按照自己的建模习惯,制定了本小节的MBD方案,但不是Matlab/Simulink的唯一用法。
首先,将所有控制策略做在一个模型文件和数据字典中,命名为MotorControl,如下图所示。
数据字典中定义了枚举量、Signal对象等,并根据需要设定Storage Class和头文件。
模型的顶层调用了两个function-call子系统,分别对应系统定时器中断中运行的低频任务,和霍尔传感器外部中断中运行的换相任务。在代码生成的时候会生成SystickTask和HallExitTask两个函数。
下面博主会叙述自己设计的BLDC控制策略,如有不当之处,希望能够交流探讨。
本实验中的低频任务只做两件事,电机状态机建模、启动停止控制。输入为按键状态、霍尔传感器状态,输出为电机状态。
1)首先是按键输入监测,调用了Get_Key1State()接口函数,并通过延时两个周期判断产生了上升沿。
这部分做了两个延时,也就是,第一个周期Key1State为0,然后连续两个周期为1的情况时,视为一次启动电机的请求(StartMotorReq置为1)。
2)电机状态机,Stateflow中共有4个状态:Init,Start,Run,Stop。
电机状态机的输入为启动或停止电机的请求。默认进入Init初始化状态,如果产生了启动电机请求(StartMotorReq)则跳转到Start状态。如果在Start状态停留了10个周期(StartCount == 10),则进入Run状态。当产生了停止电机请求(StopMotorReq),无论当前为Start状态还是Run状态都会跳入Stop状态。
进入Start或是Stop状态的时候,会产生一个事件(Event)用于触发外面的子系统。
每个状态进入时,都会输出MotorState,作为状态机外面的逻辑判断的依据。
3)电机启动的时候还未进入霍尔传感器引起的中断,在该状态中也需要进行启动时的换相函数。首先通过Get_HallXState函数获取三个霍尔传感器的状态,再通过移位进行组装合成。
A相左移2位,B相左移1位,C相不移位。例如A,B,C三相分别是1,0,1时,
HallState = (A << 2) + (B << 1) + C = 4 + 0 + 1 = 5
后面的模型会通过HallState 输入到查表模块(1D Lookup Table)中来判断哪些MOS管需要导通。然后通过调用Set函数导通Mos管。
PWMValue的值在数据字典中定义为2000。这是因为本实验做开环定速旋转,设的过高会引起启动时的抖动。
霍尔中断子系统中的模型中没有电机状态机的部分,其余的和5.2 低频任务子系统相似,也是根据霍尔传感器的状态判断导通MOS管。但是有一点区别在于该子系统需要输出HallExtiFlag,也就是霍尔激活标志位。
将模型MotorControl.slx配置好Embedded Coder后,Ctrl + B生成代码。
顶层的两个子系统分别生成了HallExtiTask.c和SystickTask.c两个c文件,并且其中包含了同名的函数。这两个函数就是上文两个子系统对应的代码。
MotorControl.c中生成了一个初始化函数MotorControl_initialize(),用于将模型中定义的全局变量初始化为0。
将所有的C文件和头文件拷贝到Keil工程中,然后在三个地方分别调用。
1)在main函数中调用初始化函数MotorControl_initialize();
2)在系统定时器中断函数中调用SystickTask();
3)在霍尔传感器中断回调函数中调用HallExtiTask();
另外,调用函数需要加上对应的头文件,否则编译无法通过。
最后在Keil中编译软件,并通过ST-Link下载到单片机中。尝试按下按键1和按键2,验证是否可以启动和停止。
博主第一次做电机控制实验,感觉还是很有意思的。后面会按照本文的方法论去继续研究BLDC的控制。
>>返回个人博客总目录