|
本帖最后由 正点原子 于 2022-8-19 11:03 编辑
1)实验平台:正点原子DMF407电机开发板
2)平台购买地址: https://detail.tmall.com/item.htm?&id=677230699323
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/stm32dj/ATK-DMF407.html
4)对正点原子电机开发板感兴趣的同学可以加群讨论: 592929122
第22章 有感方波驱动
经过前面的理论分析,是时候一展身手让电机转起来了。需要注意的是关于本书中的无刷章节的代码是同时兼容直流无刷电机(BLDC)和永磁同步电机(PMSM)的。驱动无刷电机我们使用到最简单的方式就是本章的有感方波驱动。
本章分为如下几个小节:
22.1 硬件设计
22.2 程序设计
22.3 下载验证
22.1 硬件设计
1、例程功能
本实验使用无刷电机接口一连接无刷电机驱动板
当按下KEY0一次加速旋转,按一次KEY1减速旋转,按下KEY2则停止旋转。
LED0作为程序运行状态指示灯,闪烁周期200ms。
2、硬件资源
1)LED灯:LED0 – PE0
2)独立按键
KEY0 – PE2
KEY1 – PE3
KEY2 – PE4
3) 定时器:
TIM1_CH1:PA8
TIM1_CH2:PA9
TIM1_CH3:PA10
IO:PB13\14\15
使能引脚:SHDN: PF10
4)无刷电机
5)无刷电机驱动板
6)12-60V的DC电源(建议使用电机额定电压24V)
3、原理图
图22.1.2 无刷电机接口1原理图
图22.1.2就是我们DMF407电机开发板的直流无刷电机接口1原理图,本实验所涉及的IO如下:
表22.1.1 无刷相关IO口说明
电机与驱动板之间的连接请查看21.4.2小节。
驱动板和开发板连接只需要使用排线连接即可,电机与驱动板通过接线端子进行连接,注意:由于本实验使用霍尔传感器读取转子位置,所以需将无刷驱动板的JP3跳帽把H&Z与HALL短接在一起。实物连接如下:
图22.1.3 开发板&驱动板连接图
22.2 程序设计
22.2.1 无刷电机基本控制的配置步骤
1)初始化定时器以及相关IO
初始化三相上下桥臂六路IO、霍尔状态读取三路IO、SHUTDOWN引脚以及定时器通道IO,设置ARR、PSC,计数方式以及脉冲输出模式等
2)霍尔状态读取
编写霍尔传感器状态读取函数
3)BLDC相关函数实现
包含电机启停函数,6步换相控制函数等
4)控制转速
设置旋转方向以及脉冲占空比
5)编写中断服务函数
在中断里边读取霍尔状态,根据方向以及霍尔状态依次导通上下桥臂
22.2.2 程序流程图
图22.2.2.1 无刷电机基本控制流程图
22.2.3 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。无刷方波有感的控制源码主要有:定时器驱动源码和无刷电机驱动源码四个文件:bldc_tim.c和bldc_tim.h以及bldc.c和bldc.h。源码中都有明确的注释。
首先来看下bldc_tim.h头文件的内容:
/* 高级定时器 定义 */
/* TIM_CHx通道 上桥臂IO定义 */
#define ATIM_TIMX_PWM_CH1_GPIO_PORT GPIOA
#define ATIM_TIMX_PWM_CH1_GPIO_PIN GPIO_PIN_8
#define ATIM_TIMX_PWM_CH1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define ATIM_TIMX_PWM_CH2_GPIO_PORT GPIOA
#define ATIM_TIMX_PWM_CH2_GPIO_PIN GPIO_PIN_9
#define ATIM_TIMX_PWM_CH2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define ATIM_TIMX_PWM_CH3_GPIO_PORT GPIOA
#define ATIM_TIMX_PWM_CH3_GPIO_PIN GPIO_PIN_10
#define ATIM_TIMX_PWM_CH3_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
/* 下桥臂IO定义 */
#define M1_LOW_SIDE_U_PORT GPIOB
#define M1_LOW_SIDE_U_PIN GPIO_PIN_13
#define M1_LOW_SIDE_U_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define M1_LOW_SIDE_V_PORT GPIOB
#define M1_LOW_SIDE_V_PIN GPIO_PIN_14
#define M1_LOW_SIDE_V_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define M1_LOW_SIDE_W_PORT GPIOB
#define M1_LOW_SIDE_W_PIN GPIO_PIN_15
#define M1_LOW_SIDE_W_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
/* 复用到TIM1 */
#define ATIM_TIMX_PWM_CHY_GPIO_AF GPIO_AF1_TIM1
#define ATIM_TIMX_PWM TIM1
#define ATIM_TIMX_PWM_IRQn TIM1_UP_TIM10_IRQn
#define ATIM_TIMX_PWM_IRQHandler TIM1_UP_TIM10_IRQHandler
#define ATIM_TIMX_PWM_CH1 TIM_CHANNEL_1 /* 通道Y, 1<= Y <=4 */
#define ATIM_TIMX_PWM_CH2 TIM_CHANNEL_2 /* 通道Y, 1<= Y <=4 */
#define ATIM_TIMX_PWM_CH3 TIM_CHANNEL_3 /* 通道Y, 1<= Y <=4 */
#define ATIM_TIMX_PWM_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM1_CLK_ENABLE(); }while(0) /* TIM8 时钟使能 */
可以把上面的宏定义分成三部分:第一部分是定时器1输出通道1、2、3对应的IO口的宏定义,同时也是控制逆变器的三个上桥臂的IO宏定义;第二部分则是控制逆变器的三个下桥臂的IO宏定义,第三部分是关于定时器1的通道、时钟等相关宏定义。
下面我们来看下bldc_tim.c代码。
/*
* @brief 高级定时器TIMX PWM输出初始化函数
* @note
* 高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
* 高级定时器时钟 = 168Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值
* @param psc: 时钟预分频数
* @retval 无
*/
void atim_timx_oc_chy_init(uint16_t arr, uint16_t psc)
{
ATIM_TIMX_PWM_CHY_CLK_ENABLE(); /* TIMX 时钟使能 */
g_atimx_handle.Instance = ATIM_TIMX_PWM; /* 定时器x */
g_atimx_handle.Init.Prescaler = psc; /* 定时器分频 */
g_atimx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */
g_atimx_handle.Init.Period = arr; /* 自动重装载值 */
g_atimx_handle.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; /* 分频因子 */
g_atimx_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
g_atimx_handle.Init.RepetitionCounter = 0; /* 开始时不计数*/
HAL_TIM_PWM_Init(&g_atimx_handle); /* 初始化PWM */
g_atimx_oc_chy_handle.OCMode = TIM_OCMODE_PWM1; /* 模式选择PWM1*/
g_atimx_oc_chy_handle.Pulse = 0;
g_atimx_oc_chy_handle.OCPolarity = TIM_OCPOLARITY_HIGH; /* 比较极性为低 */
g_atimx_oc_chy_handle.OCNPolarity = TIM_OCNPOLARITY_HIGH;
g_atimx_oc_chy_handle.OCFastMode = TIM_OCFAST_DISABLE;
g_atimx_oc_chy_handle.OCIdleState = TIM_OCIDLESTATE_RESET;
g_atimx_oc_chy_handle.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle,
ATIM_TIMX_PWM_CH1); /* 配置通道1 */
HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle,
ATIM_TIMX_PWM_CH2); /* 配置通道2 */
HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle,
ATIM_TIMX_PWM_CH3); /* 配置通道3 */
/* 开启定时器通道1输出PWM */
HAL_TIM_PWM_Start(&g_atimx_handle,TIM_CHANNEL_1);
/* 开启定时器通道2输出PWM */
HAL_TIM_PWM_Start(&g_atimx_handle,TIM_CHANNEL_2);
/* 开启定时器通道3输出PWM */
HAL_TIM_PWM_Start(&g_atimx_handle,TIM_CHANNEL_3);
}
函数atim_timx_oc_chy_init的主要功能,就是初始化定时器1的ARR和PSC等参数,然后通过调用函数HAL_TIM_PWM_ConfigChannel设置TIM1_CH1、2、3的PWM模式以及比较值等参数,最后通过调用函数HAL_TIM_PWM_Start来使能TIM1以及使能PWM通道TIM1_CH1、2、3输出。定时器通道的GPIO口初始化和定时器使能等程序放到回调函数HAL_TIM_PWM_MspInit中,注意:我们为了程序易读,将下桥臂的IO初始化一起放在该回调函数里实现了,如下:
/**
* @brief 定时器底层驱动,时钟使能,引脚配置
此函数会被HAL_TIM_PWM_Init()调用
* @param htim:定时器句柄
* @retval 无
*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == ATIM_TIMX_PWM)
{
GPIO_InitTypeDef gpio_init_struct;
ATIM_TIMX_PWM_CHY_CLK_ENABLE(); /* 开启通道y的CPIO时钟 */
ATIM_TIMX_PWM_CH1_GPIO_CLK_ENABLE(); /* IO时钟使能 */
ATIM_TIMX_PWM_CH2_GPIO_CLK_ENABLE(); /* IO时钟使能 */
ATIM_TIMX_PWM_CH3_GPIO_CLK_ENABLE(); /* IO时钟使能 */
M1_LOW_SIDE_U_GPIO_CLK_ENABLE(); /* IO时钟使能 */
M1_LOW_SIDE_V_GPIO_CLK_ENABLE(); /* IO时钟使能 */
M1_LOW_SIDE_W_GPIO_CLK_ENABLE(); /* IO时钟使能 */
/* 下桥臂的IO初始化 */
gpio_init_struct.Pin = M1_LOW_SIDE_U_PIN;
gpio_init_struct.Pull = GPIO_NOPULL;
gpio_init_struct.Speed = GPIO_SPEED_HIGH;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出模式 */
HAL_GPIO_Init(M1_LOW_SIDE_U_PORT, &gpio_init_struct);
gpio_init_struct.Pin = M1_LOW_SIDE_V_PIN;
HAL_GPIO_Init(M1_LOW_SIDE_V_PORT, &gpio_init_struct);
gpio_init_struct.Pin = M1_LOW_SIDE_W_PIN;
HAL_GPIO_Init(M1_LOW_SIDE_W_PORT, &gpio_init_struct);
/* 上桥臂即定时器IO初始化 */
gpio_init_struct.Pin = ATIM_TIMX_PWM_CH1_GPIO_PIN; /* 通道y的CPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推完输出 */
gpio_init_struct.Pull = GPIO_NOPULL; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
gpio_init_struct.Alternate = ATIM_TIMX_PWM_CHY_GPIO_AF;/* 端口复用 */
HAL_GPIO_Init(ATIM_TIMX_PWM_CH1_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = ATIM_TIMX_PWM_CH2_GPIO_PIN; /* 通道y的CPIO口 */
HAL_GPIO_Init(ATIM_TIMX_PWM_CH2_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = ATIM_TIMX_PWM_CH3_GPIO_PIN; /* 通道y的CPIO口 */
HAL_GPIO_Init(ATIM_TIMX_PWM_CH3_GPIO_PORT, &gpio_init_struct);
HAL_NVIC_SetPriority(TIM1_UP_TIM10_IRQn, 2, 2);
HAL_NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn);
}
}
程序使用H_PWM - L_ON方式驱动,因此只有3路上桥臂使用PWM通道的初始化,三路下桥臂使用的普通IO输出。相应的IO也在头文件进行了定义。
接着看下bldc.h的内容:
/* 半桥芯片的刹车引脚 */
#define SHUTDOWN_PIN GPIO_PIN_10 /* PF10 */
#define SHUTDOWN_PIN_GPIO GPIOF
#define SHUTDOWN_PIN_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0) /* PF口时钟使能 */
#define SHUTDOWN_EN HAL_GPIO_WritePin(SHUTDOWN_PIN_GPIO,SHUTDOWN_PIN,GPIO_PIN_SET);
#define SHUTDOWN_OFF HAL_GPIO_WritePin(SHUTDOWN_PIN_GPIO,SHUTDOWN_PIN,GPIO_PIN_RESET);
/*************************************************************************/
/* 霍尔传感器接口 */
#define HALL1_TIM_CH1_PIN GPIO_PIN_10 /* U */
#define HALL1_TIM_CH1_GPIO GPIOH
#define HALL1_U_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOH_CLK_ENABLE(); }while(0) /* PH口时钟使能 */
#define HALL1_TIM_CH2_PIN GPIO_PIN_11 /* V */
#define HALL1_TIM_CH2_GPIO GPIOH
#define HALL1_V_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOH_CLK_ENABLE(); }while(0) /* PH口时钟使能 */
#define HALL1_TIM_CH3_PIN GPIO_PIN_12 /* W */
#define HALL1_TIM_CH3_GPIO GPIOH
#define HALL1_W_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOH_CLK_ENABLE(); }while(0) /* PH口时钟使能 */
/************************************************************************/
#define MAX_PWM_DUTY (((10000) - 1)*0.96) /* 最大占空比限制 */
上述代码主要分为三部分:第一部分为逆变电路的半桥芯片刹车IO的宏定义;第二部分为读取三路霍尔输出状态的IO宏定义;第三部分为PWM占空比的最大限制宏定义,用于对速度进行限制。
接着看下bldc.c文件,该文件主要包含了BLDC初始化函数、霍尔传感器状态读取函数、无刷电机的启停函数、以及基础的六步换向控制函数。函数如下:
/**
* @brief 初始化BLDC相关IO口, 并使能时钟
* @param 无
* @retval 无
*/
void bldc_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct;
SHUTDOWN_PIN_GPIO_CLK_ENABLE();
gpio_init_struct.Pin = SHUTDOWN_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init_struct.Pull = GPIO_NOPULL;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(SHUTDOWN_PIN_GPIO, &gpio_init_struct);
hall_gpio_init(); /* 霍尔接口初始化 */
atim_timx_oc_chy_init(arr, psc); /* 定时器初始化 */
}
该函数主要实现刹车引脚和霍尔接口以及定时器的初始化。内容比较简单,就不多说了,接着来看下霍尔状态读取函数:
/**
* @brief 获取霍尔传感器引脚状态
* @param 无
* @retval 霍尔传感器引脚状态
*/
uint32_t hallsensor_get_state(uint8_t motor_id)
{
__IO static uint32_t State ;
State = 0;
if(motor_id == MOTOR_1)
{
if(HAL_GPIO_ReadPin(HALL1_TIM_CH1_GPIO,HALL1_TIM_CH1_PIN) !=
GPIO_PIN_RESET) /* 霍尔1传感器状态获取 */
{
State |= 0x01U;
}
if(HAL_GPIO_ReadPin(HALL1_TIM_CH2_GPIO,HALL1_TIM_CH2_PIN) !=
GPIO_PIN_RESET) /* 霍尔2传感器状态获取 */
{
State |= 0x02U;
}
if(HAL_GPIO_ReadPin(HALL1_TIM_CH3_GPIO,HALL1_TIM_CH3_PIN) !=
GPIO_PIN_RESET) /* 霍尔3传感器状态获取 */
{
State |= 0x04U;
}
}
return State;
}
该函数有1个入口参数,该参数代表当前控制哪个电机接口,方便大家控制多个电机时使用。接着是霍尔状态读取,将霍尔1、2、3各自的读取值,组合在一起。注意霍尔3最高位,霍尔2次之,霍尔1最低位。然后返回该霍尔组合值。
接着看下电机的启停函数
/* 关闭电机运转 */
void stop_motor1(void)
{
/* 关闭半桥芯片输出 */
SHUTDOWN_OFF;
/* 关闭PWM输出 */
HAL_TIM_PWM_Stop(&g_atimx_handle,TIM_CHANNEL_1);
HAL_TIM_PWM_Stop(&g_atimx_handle,TIM_CHANNEL_2);
HAL_TIM_PWM_Stop(&g_atimx_handle,TIM_CHANNEL_3);
/* 上下桥臂全部关断 */
g_atimx_handle.Instance->CCR2 = 0;
g_atimx_handle.Instance->CCR1 = 0;
g_atimx_handle.Instance->CCR3 = 0;
HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_RESET);
HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_RESET);
HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_RESET);
}
/* 开启电机运转 */
void start_motor1(void)
{
SHUTDOWN_EN;
/* 使能PWM输出 */
HAL_TIM_PWM_Start(&g_atimx_handle,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&g_atimx_handle,TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&g_atimx_handle,TIM_CHANNEL_3);
}
电机开启函数,开启半桥芯片正常输出,即使能shutdown引脚,然后开启TIM1的三个通道的PWM输出。
电机关闭函数,关闭半桥芯片正常输出,即失能shutdown引脚,然后关闭TIM1通道的PWM输出,并关断所有上下桥臂。
接着是我们的六步换相控制函数:
/* 上下桥臂的导通情况,共6种,也称为6步换向 */
void m1_uhvl(void)
{
g_atimx_handle.Instance->CCR2 = 0;
g_atimx_handle.Instance->CCR1 = g_bldc_motor1.pwm_duty; /* U相上桥臂PWM */
g_atimx_handle.Instance->CCR3 = 0;
/* V相下桥臂导通 */
HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_SET);
/* U相下桥臂关闭 */
HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_RESET);
/* W相下桥臂关闭 */
HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_RESET);
}
void m1_uhwl(void)
{
g_atimx_handle.Instance->CCR2 = 0;
g_atimx_handle.Instance->CCR1 = g_bldc_motor1.pwm_duty;
g_atimx_handle.Instance->CCR3 = 0;
HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_SET);
HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_RESET);
HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_RESET);
}
void m1_vhwl(void)
{
g_atimx_handle.Instance->CCR1=0;
g_atimx_handle.Instance->CCR2 = g_bldc_motor1.pwm_duty;
g_atimx_handle.Instance->CCR3=0;
HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_SET);
HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_RESET);
HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_RESET);
}
void m1_vhul(void)
{
g_atimx_handle.Instance->CCR1 = 0;
g_atimx_handle.Instance->CCR2 = g_bldc_motor1.pwm_duty;
g_atimx_handle.Instance->CCR3 = 0;
HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_SET);
HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_RESET);
HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_RESET);
}
void m1_whul(void)
{
g_atimx_handle.Instance->CCR2 = 0;
g_atimx_handle.Instance->CCR3 = g_bldc_motor1.pwm_duty;
g_atimx_handle.Instance->CCR1 = 0;
HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_SET);
HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_RESET);
HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_RESET);
}
void m1_whvl(void)
{
g_atimx_handle.Instance->CCR2 = 0;
g_atimx_handle.Instance->CCR3 = g_bldc_motor1.pwm_duty;
g_atimx_handle.Instance->CCR1 = 0;
HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_SET);
HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_RESET);
HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_RESET);
}
上述代码为6步换相控制代码,由于我们使用的驱动方式为H_PWM – L_ON,即上桥臂由定时器通道输出的PWM控制,下桥臂使用的普通IO直接控制,所以上桥臂通过修改对应定时器通道的比较值,进而修改占空比,既可以控制MOS的开关也可以控制电机速度;下桥臂直接通过IO拉高拉低控制开关。
讲到这里,bldc.c文件里的函数就讲得差不多了,接着来看下我们的核心内容,定时器的中断回调函数:
/**
* @brief 定时器中断回调
* @param 无
* @retval 无
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
uint8_t bldc_dir=0;
if(htim->Instance == ATIM_TIMX_PWM) /* 55us */
{
#ifdef H_PWM_L_ON
if(g_bldc_motor1.run_flag == RUN)
{
if(g_bldc_motor1.dir == CW) /* 正转 */
{
/* 顺序6,2,3,1,5,4 */
g_bldc_motor1.step_sta = hallsensor_get_state(MOTOR_1);
}
else /* 反转 */
{
/* 顺序5,1,3,2,6,4 */
g_bldc_motor1.step_sta = 7 - hallsensor_get_state(MOTOR_1);
}
if((g_bldc_motor1.step_sta <= 6)&&(g_bldc_motor1.step_sta >= 1))
{
pfunclist_m1[g_bldc_motor1.step_sta-1]();
}
else /* 霍尔传感器错误、接触不良、断开等情况 */
{
stop_motor1();
g_bldc_motor1.run_flag = STOP;
}
/* 检测到霍尔值变换,代表换向一次 */
if(g_bldc_motor1.step_last != g_bldc_motor1.step_sta)
{
g_bldc_motor1.hall_keep_t=0;
bldc_dir = check_hall_dir(&g_bldc_motor1);
if(bldc_dir == CCW)
{
g_bldc_motor1.pos -=1;
}
else if(bldc_dir == CW)
{
g_bldc_motor1.pos +=1;
}
g_bldc_motor1.step_last = g_bldc_motor1.step_sta;
}
else if(g_bldc_motor1.run_flag == RUN) /* 运行且霍尔保持时 */
{
g_bldc_motor1.hall_keep_t++ /* 换向一次所需计数值(时间) 单位1/18k */
}
}
#endif
}
}
在中断回调函数里,主要根据霍尔状态调用不同的换相函数,霍尔值与绕组的导通真值表可以查看表21.3.2,我们也是依据这个表来实现该代码的。通过读取到的霍尔状态组合值,导通对应的上下桥臂,我们这里使用函数指针的方式,指向函数列表pfunclist_m1的成员,这样可以使代码更加简洁,当然大家也可以使用switch语句实现。
有了上述的逻辑,操作起来无刷电机就比较简单了。最后来看下main函数。
int main(void)
{
uint8_t key,t;
char buf[32];
int16_t pwm_duty_temp=0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
lcd_init(); /* 初始化LCD */
bldc_init(10000-1,0);
bldc_ctrl(MOTOR_1,CCW,0); /* 初始无刷电机接口1速度 */
HAL_TIM_Base_Start_IT(&g_atimx_handle); /* 启动高级定时器1*/
g_point_color = WHITE;
g_back_color = BLACK;
lcd_show_string(10, 10, 200, 16, 16, "BLDC Motor Test",g_point_color);
lcd_show_string(10, 30, 200, 16, 16, "KEY0:Start forward", g_point_color);
lcd_show_string(10, 50, 200, 16, 16, "KEY1:Start backward", g_point_color);
lcd_show_string(10, 70, 200, 16, 16, "KEY2:Stop", g_point_color);
while (1)
{
t++;
if(t % 20 == 0)
{
sprintf(buf,"PWM_Duty:%.1f%%",(float)((g_bldc_motor1.pwm_duty/
MAX_PWM_DUTY)*100));/* 显示控制PWM占空比 */
lcd_show_string(10,110,200,16,16,buf,g_point_color);
LED0_TOGGLE(); /* LED0(红灯) 翻转 */
}
key = key_scan(0);
if(key == KEY0_PRES)
{
pwm_duty_temp+=500;
if(pwm_duty_temp>=MAX_PWM_DUTY/2)
pwm_duty_temp=MAX_PWM_DUTY/2;
if(pwm_duty_temp>0)
{
g_bldc_motor1.pwm_duty= pwm_duty_temp;
g_bldc_motor1.dir=CCW;
}
else
{
g_bldc_motor1.pwm_duty=-pwm_duty_temp;
g_bldc_motor1.dir=CW;
}
g_bldc_motor1.run_flag=RUN; /* 开启运行 */
start_motor1(); /* 开启运行 */
}
if(key == KEY1_PRES) /* 按下KEY1开启电机 */
{
pwm_duty_temp-=500;
if(pwm_duty_temp<=-MAX_PWM_DUTY/2)
pwm_duty_temp=-MAX_PWM_DUTY/2;
if(pwm_duty_temp < 0)
{
g_bldc_motor1.pwm_duty=-pwm_duty_temp;
g_bldc_motor1.dir=CW;
}
else
{
g_bldc_motor1.pwm_duty=pwm_duty_temp;
g_bldc_motor1.dir=CCW;
}
g_bldc_motor1.run_flag=RUN; /* 开启运行 */
start_motor1(); /* 运行电机 */
}
if(key == KEY2_PRES) /* 按下KEY2关闭电机 */
{
stop_motor1(); /* 停机 */
g_bldc_motor1.run_flag=STOP; /* 标记停机 */
pwm_duty_temp=0;
g_bldc_motor1.pwm_duty=0;
}
delay_ms(10);
}
}
主函数首先调用bldc_init函数初始化定时器的PWM参数,然后调用bldc_ctrl函数控制无刷电机的初始化速度和方向,接着启动定时器,接下来就可以使用无刷电机了。主函数循环通过检测按键来控制无刷电机的旋转和停止,当按下KEY0增大占空比,按下KEY1减小占空比,按下KEY2关闭电机。
22.3 下载验证
硬件连接好之后下载程序到开发板,液晶屏和串口都会显示提示信息,点击按键KEY0一次可以看到定时器比较值递增500(加速),电机开始旋转,点击一次按键KEY1则可以看到定时器比较值递减500(减速),点击按键KEY2则可以随时停止无刷电机。大家可以自己下载程序并点击按键测试效果。
|
|