这里写目录标题
前言
在阅读野火的《电机应用开发实战指南—基于STM32》无刷直流电机章节时,突然想基于ST官方手册高级定时器章节相关内容进行BLDC相关实验。在此做一个开发调试记录。
由于本文大量引用ST官方手册内容,篇幅较长,有不尽或不对之处敬请指正。
本试验开发环境:
1、电路板:野火407骄阳电机控制板+BLDC驱动板;
2、IDE:STM32CubeMX 6.4.0 + KEIL5.36.0.0;
3、电机:时代超群(57BL75S10-230TF9,不带编码器)。
BLDC驱动电路及换相等基础知识在此不再赘述,推荐下面两篇博文供参考。
1、【零基础玩转BLDC系列】基于霍尔传感器的无刷直流电机控制原理
2、有霍尔传感器BLDC换向原理
1、HALL接口定时器配置
1.1、ST手册定时器相关内容及其理解
HALL接口定时器涉及《STM32F4中文手册》14.3.18 连接霍尔传感器、14.3.5 捕获/比较通道以及“STM32定时器总纲”——高级定时器框图。
1.1.1、连接霍尔传感器
“可通过用于生成电机驱动 PWM 信号的高级控制定时器(TIM1 或 TIM8)以及图 114中称为 “接口定时器”的另一个定时器 TIMx(TIM2、TIM3、TIM4 或 TIM5),实现与霍尔传感器 的连接。3 个定时器输入引脚(TIMx_CH1、TIMx_CH2 和 TIMx_CH3)通过异或门连接到 TI1 输入通道(通过将 TIMx_CR2 寄存器中的 TI1S 位置 1 来选择),并由“接口定时器” 进行捕获。”
a、HALL_A、HALL_B、HALL_C分别接入某个通用定时器(TIM2、TIM3、TIM4或TIM5)的TIMx_CH1、TIMx_CH2 和 TIMx_CH3,该通用定时器称作HALL的“接口定时器”。
b、将“接口定时器”的TIMx_CR2 寄存器中的 TI1S 位置 1 :TIMx_CH1、CH2 和 CH3 引脚连接到 TI1 输入(异或组合)。
c、“接口定时器”将对TI1通道进行捕获。详情见本文1.1.2、捕获/比较通道小节。
“从模式控制器配置为复位模式;从输入为 TI1F_ED。这样,每当 3 个输入中有一个输入发生 切换时,计数器会从 0 开始重新计数。这样将产生由霍尔输入的任何变化而触发的时基。”
a、“接口定时器”的从模式控制器配置为复位模式。
b、“接口定时器”的从模式控制器的触发源配置为TI1F_ED。
“在“接口定时器”上,捕获/比较通道 1 配置为捕获模式,捕获信号为 TRC(请参见第 344 页的图 97:捕获/比较通道(例如:通道 1 输入阶段))。捕获值对应于输入上两次变化的 间隔时间,可提供与电机转速相关的信息。”
a、“接口定时器”的TIMx_CH1 配置为捕获模式,捕获信号为 TRC。详情见本文1.1.2、捕获/比较通道小节。
b、“捕获值对应于输入上两次变化的 间隔时间”的意思是,捕获值是6步换相中每一相持续的时间,以便计算电机转速。
““接口定时器”可用于在输出模式下产生脉冲,以通过触发 COM 事件更改高级控制定时器(TIM1 或 TIM8)各个通道的配置。TIM1 定时器用于生成电机驱动 PWM 信号。为此,必 须对接口定时器通道进行编程,以便在编程的延迟过后产生正脉冲(在输出比较或 PWM 模 式中)。该脉冲通过 TRGO 输出发送到高级控制定时器(TIM1 或 TIM8)。”
a、“接口定时器”通过配置成主输出模式,产生脉冲TRGO,来触发高级定时器的COM 事件。也可以不配置成输出模式,采用软件方式触发高级定时器的COM事件。 高级定时器的COM 事件同时刻更改其各个通道的配置。
b、当“接口定时器”通过配置成主输出模式,产生脉冲TRGO时,可以通过设置脉冲宽度参数"编程的延迟过后产生正脉冲"。
c、产生的脉冲TRGO输出到高级定时器(TIM1 或 TIM8)。
1.1.2、捕获/比较通道
上图97是定时器最全的捕获/比较通道内部框图,红框区域代表的手册中图71中“输入滤波器和边沿检测器”。上图中手动绘制的TI1P2(指TI1FP2)边沿信号进入TIMX_CH2通道中。
结合1.1.1节内容,可以知道,HALL_A、HALL_B、HALL_C异或后接入TIMX_CH1通道,成为图97中的TI1。TI1经过滤波器和边沿检测器后,通过或门变成TI1F_ED信号,去触发“接口定时器”的从模式控制器,进而使“接口定时器”产生TRGO输出信号。以上就是HALL触发的信号路径。
有关捕获/比较通道的更为详细的内容,推荐下面博文仅供参考。
TIM输入捕获。
1.1.3、高级控制定时器框图
结合1.1.1和1.1.2得到上图所示的“接口定时器”内部信号路径图。
a、红色是HALL触发“接口定时器”的从模式控制器路径,使“接口定时器”复位;
b、蓝色是TRC触发“接口定时器”的捕获功能,获取BLDC单相持续的时间。此时间也可从其TIMX_CH3、TIMX_CH4通道获取。但是HAL库默认配置为了TIMX_CH1。
c、绿色是“接口定时器”触发输出信号TRGO产生的路径。
以上路径均在HAL库调用时配置好,无需开发人员再次配置。
1.2、STM32CubeMX配置HALL接口
本实验接口定时器使用TIM5。具体配置如下图。注意事项图中已标明。
参数配置
中断配置。
引脚配置。
1.3、STM32CubeMX生成HALL接口代码
生成代码后,在main.c文件主函数中可以看到MX_TIM5_Init()函数,进入后可以看到调用了下面的函数。
HAL_TIMEx_HallSensor_Init(TIM_HandleTypeDef *htim, TIM_HallSensor_InitTypeDef *sConfig)
此函数将STM32CubeMX中配置的参数配置完毕。也完成了ST手册中下段表述的配置。
感兴趣的可以进入HAL_TIMEx_HallSensor_Init()函数体自行查阅。
此外,主函数中初始化“接口定时器”后,需要加入下面代码,开启比较输出中断并开启定时器HALL功能。
__HAL_TIM_ENABLE_IT(&htim5, TIM_IT_CC2); //开启比较输出中断
HAL_TIMEx_HallSensor_Start(&htim5); //开启接口定时器
比较输出中断回调函数(或PWM完成中断回调函数)用于获取HALL状态,并根据HALL状态配置高级定时器通道进行换相。
PWM完成中断回调函数定义如下:
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
uint8_t step = 0;
if(htim == &htim5)
{
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
{
step = Hall_Get_State(); //获取当前时刻HALL状态值
PWM_CW_Control(step, 1); //根据HALL状态值配置高级定时器各通道参数。
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}
}
}
2、电机驱动 PWM 信号的高级控制定时器配置
本实验采用TIM8作为生成电机驱动 PWM 信号的高级控制定时器。采用互补通道输出PWM,三相全桥采用H-PWM_L-ON控制方式。
2.1STM32CubeMX配置高级定时器
从模式控制器、时钟源、通道、时基配置以及刹车寄存器配置如下图,触发输出及PWM通道采用默认。
PWM引脚配置为高速。
2.2STM32CubeMX生成高级定时代码
生成代码后,在MX_TIM8_Init()函数后面添加并配置如下函数:
HAL_TIMEx_ConfigCommutEvent(&htim8, TIM_TS_ITR3, TIM_COMMUTATION_TRGI);
该函数配置 TIMx_CR2 寄存器的CCPC=1, CCUS=1。对应ST手册中下面一段话。
3、6步换相代码的实现
3.1、6步换相理论参考
1、ST手册中14.3.14章节 生成 6 步 PWM
2、参考博文:STM32 - 定时器的设定 - 基础- 07 - 6-step PWM generation - 6步长PWM的产生 - COM Event的解释
3、ST手册中14.3.18章节 连接霍尔传感器
3.2、6步换相代码实现
1、BLDC变相时序如下图
上表中,正转/反转的定义:俯视电机输出轴逆时针为正转,顺时针为反转。
顺时针HALL值:264513,便于记忆:“二流子上武夷山”;
逆时针HALL值与顺时针顺序相反:315462。
由上表可知,顺时针与逆时针在三相全桥通断状态相同的情况下,两种转向的HALL值相加等于7。
2、代码生成后加入如下代码
在main.c的main()函数初始化定时器后加入下面代码
PWM_Pulse_Set(speed); //设置PWM占空比
MOTOR_ENABLE; //是能MOS驱动电路
PWM_CW_Control(Hall_Get_State(), 0); //获取HALL值并设置PWM通道
Hall_Time_Enable(); //开启接口定时器,产生一次TRGO信号触发COM事件
//将PWM占空比及其他配置更新到高级定时器中,启动电机。
代码采用强制模式及PWM1模式,具体如下:
/**
* @description: 定时器通道模式设置
* @param {TIM_HandleTypeDef } htim 定时器句柄指针
* @param {uint32_t } Channel 定时器通道
* @param {uint32_t } Channel 通道模式
* @return {*}
*/
static void TIM_OCMode_Set(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t OCMode)
{
uint32_t tmpccmrx;
switch(Channel)
{
case TIM_CHANNEL_1:
tmpccmrx = htim->Instance->CCMR1;
tmpccmrx &= ~TIM_CCMR1_OC1M;
tmpccmrx |= OCMode;
htim->Instance->CCMR1 = tmpccmrx;
break;
case TIM_CHANNEL_2:
tmpccmrx = htim->Instance->CCMR1;
tmpccmrx &= ~TIM_CCMR1_OC2M;
tmpccmrx |= OCMode << 8;
htim->Instance->CCMR1 = tmpccmrx;
break;
case TIM_CHANNEL_3:
tmpccmrx = htim->Instance->CCMR2;
tmpccmrx &= ~TIM_CCMR2_OC3M;
tmpccmrx |= OCMode;
htim->Instance->CCMR2 = tmpccmrx;
break;
case TIM_CHANNEL_4:
tmpccmrx = htim->Instance->CCMR2;
tmpccmrx &= ~TIM_CCMR2_OC4M;
tmpccmrx |= OCMode << 8;
htim->Instance->CCMR2 = tmpccmrx;
break;
default:
break;
}
}
/**
* @description: 顺时针运行
* @param {uint8_t} Step 当前HALL位置
* @param {uint8_t} Trgl_en COM事件硬触发 1-硬触发,0-软触发
* @return {*}
*/
void PWM_CW_Control(uint8_t Step, uint8_t Trgl_en)
{
if (Trgl_en == 1)
{
if(Step == 2) Step = 6;
else if(Step == 6) Step = 4;
else if(Step == 4) Step = 5;
else if(Step == 5) Step = 1;
else if(Step == 1) Step = 3;
else if(Step == 3) Step = 2;
}
switch(Step)
{
case 1: //W+U-
TIM_OCMode_Set(&htim8, TIM_CHANNEL_1, TIM_OCMODE_FORCED_ACTIVE);
HAL_TIM_PWM_Stop(&htim8,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_1);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_2, TIM_OCMODE_FORCED_INACTIVE);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_2);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_3, TIM_OCMODE_PWM1);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_3);
break;
case 2: //U+V-
TIM_OCMode_Set(&htim8, TIM_CHANNEL_1, TIM_OCMODE_PWM1);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_1);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_2, TIM_OCMODE_FORCED_ACTIVE);
HAL_TIM_PWM_Stop(&htim8, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_2);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_3, TIM_OCMODE_FORCED_INACTIVE);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_3);
break;
case 3: //W+V-
TIM_OCMode_Set(&htim8, TIM_CHANNEL_1, TIM_OCMODE_FORCED_INACTIVE);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_1);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_2, TIM_OCMODE_FORCED_ACTIVE);
HAL_TIM_PWM_Stop(&htim8, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_2);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_3, TIM_OCMODE_PWM1);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_3);
break;
case 4: //V+W-
TIM_OCMode_Set(&htim8, TIM_CHANNEL_1, TIM_OCMODE_FORCED_INACTIVE);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_1);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_2, TIM_OCMODE_PWM1);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_2);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_3, TIM_OCMODE_FORCED_ACTIVE);
HAL_TIM_PWM_Stop(&htim8,TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_3);
break;
case 5: //V+U-
TIM_OCMode_Set(&htim8, TIM_CHANNEL_1, TIM_OCMODE_FORCED_ACTIVE);
HAL_TIM_PWM_Stop(&htim8,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_1);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_2, TIM_OCMODE_PWM1);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_2);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_3, TIM_OCMODE_FORCED_INACTIVE);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_3);
break;
case 6: //U+W-
TIM_OCMode_Set(&htim8, TIM_CHANNEL_1, TIM_OCMODE_PWM1);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_1);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_2, TIM_OCMODE_FORCED_INACTIVE);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_2);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_3, TIM_OCMODE_FORCED_ACTIVE);
HAL_TIM_PWM_Stop(&htim8,TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_3);
break;
default:
break;
}
}
/**
* @description: 逆时针运行
* @param {uint8_t} Step 当前HALL位置
* @param {uint8_t} Trgl_en COM事件硬触发 1-硬触发,0-软触发
* @return {*}
*/
void PWM_CCW_Control(uint8_t Step, uint8_t Trgl_en)
{
if (Trgl_en == 1)
{
if(Step == 3) Step = 1;
else if(Step == 1) Step = 5;
else if(Step == 5) Step = 4;
else if(Step == 4) Step = 6;
else if(Step == 6) Step = 2;
else if(Step == 2) Step = 3;
}
switch(Step)
{
case 1: //U+W-
TIM_OCMode_Set(&htim8, TIM_CHANNEL_1, TIM_OCMODE_PWM1);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_1);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_2, TIM_OCMODE_FORCED_INACTIVE);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_2);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_3, TIM_OCMODE_FORCED_ACTIVE);
HAL_TIM_PWM_Stop(&htim8,TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_3);
break;
case 2: //V+U-
TIM_OCMode_Set(&htim8, TIM_CHANNEL_1, TIM_OCMODE_FORCED_ACTIVE);
HAL_TIM_PWM_Stop(&htim8,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_1);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_2, TIM_OCMODE_PWM1);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_2);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_3, TIM_OCMODE_FORCED_INACTIVE);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_3);
break;
case 3: //V+W-
TIM_OCMode_Set(&htim8, TIM_CHANNEL_1, TIM_OCMODE_FORCED_INACTIVE);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_1);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_2, TIM_OCMODE_PWM1);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_2);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_3, TIM_OCMODE_FORCED_ACTIVE);
HAL_TIM_PWM_Stop(&htim8,TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_3);
break;
case 4: //W+V-
TIM_OCMode_Set(&htim8, TIM_CHANNEL_1, TIM_OCMODE_FORCED_INACTIVE);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_1);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_2, TIM_OCMODE_FORCED_ACTIVE);
HAL_TIM_PWM_Stop(&htim8, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_2);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_3, TIM_OCMODE_PWM1);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_3);
break;
case 5: //U+V-
TIM_OCMode_Set(&htim8, TIM_CHANNEL_1, TIM_OCMODE_PWM1);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_1);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_2, TIM_OCMODE_FORCED_ACTIVE);
HAL_TIM_PWM_Stop(&htim8, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_2);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_3, TIM_OCMODE_FORCED_INACTIVE);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_3);
break;
case 6: //W+U-
TIM_OCMode_Set(&htim8, TIM_CHANNEL_1, TIM_OCMODE_FORCED_ACTIVE);
HAL_TIM_PWM_Stop(&htim8,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_1);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_2, TIM_OCMODE_FORCED_INACTIVE);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_2);
TIM_OCMode_Set(&htim8, TIM_CHANNEL_3, TIM_OCMODE_PWM1);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Stop(&htim8, TIM_CHANNEL_3);
break;
default:
break;
}
}
3.3、6步换相硬触发注意事项
1、启动电机前获取HALL当前值,并根据当前HALL值对应的MOS导通表配置互补的PWM;
2、启动电机后,在“接口定时器”PWM完成中断回调函数中再次获取当前HALL值,并根据当前HALL值的下一步值对应的MOS导通表配置互补的PWM。
另外,如果是软件触发COM事件,则无需注意以上两点,直接在main()函数初始化后调用软件触发COM事件函数即可启动电机,同时在换相函数中也调用软件触发COM事件函数。软件触发COM事件函数如下:
HAL_TIM_GenerateEvent(&htim8, TIM_EVENTSOURCE_COM);
3、PWM完成中断回调函数定义如下
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
uint8_t step = 0;
if(htim == &htim5)
{
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
{
step = Hall_Get_State();
#if(MOTOR_DIR == 0)
PWM_CW_Control(step, 1);
#else
PWM_CCW_Control(step, 1);
#endif
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}
}
}
uint8_t Hall_Get_State(void)
{
uint8_t temp = 0;
temp = (GPIOH->IDR >> 10) & 0x07; //bit0: hall_U-PH10, bit1: hall_V-PH11, bit2: hall_W-PH12. //TIM5
return temp;
}