ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《DMG474电机控制专题教程V1.1 》第22章 有感方波驱动

《DMG474电机控制专题教程V1.1 》第22章 有感方波驱动

原创 嵌入式/内核开发 作者:正点原子 时间:2026-04-28 15:32:22 0 删除 编辑

第22章 有感方波驱动


       经过前面的理论分析,是时候一展身手让电机转起来了。需要注意的是关于本书中的无刷章节的代码是同时兼容直流无刷电机(BLDC)和永磁同步电机(PMSM)的。驱动无刷电机我们使用到最简单的方式就是本章的有感方波驱动。

       本章分为如下几个小节:

       22.1 硬件设计

       22.2 程序设计

       22.3 下载验证


        22.1 硬件设计


       1、例程功能

       l 本实验使用无刷电机接口一连接无刷电机驱动板。

       l 当按下KEY0一次加速旋转,按一次KEY1减速旋转,按下KEY2则停止旋转。

       l LED0作为程序运行状态指示灯,闪烁周期200ms。


       2、硬件资源

       1)LED灯:LED0 – PE0

       2)独立按键

              KEY0 – PE12

              KEY1 – PE13

              KEY2 – PE14

       3) 定时器:  

               TIM1_CH1:PA8

               TIM1_CH2:PA9

               TIM1_CH3:PA10

       4) IO:

              下桥臂IO:PB13\14\15

              霍尔IO: PA0\1\2

              使能IO:SHDN: PC13

       5)无刷电机

       6)无刷电机驱动板

       7)12-60V的DC电源(建议使用电机额定电压24V)


       3、原理图


图22.1.2 无刷电机接口1原理图


       图22.1.2就是我们DMG474电机开发板的直流无刷电机接口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)   /* PA口时钟使能 */
 
#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)  /* PA口时钟使能 */
 
#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)  /* PA口时钟使能 */
 
/* 下桥臂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)   /* PB口时钟使能 */
 
#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)   /* PB口时钟使能 */
 
#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)   /* PB口时钟使能 */
 
/* 复用到TIM1 */
#define ATIM_TIMX_PWM_CHY_GPIO_AF              GPIO_AF6_TIM1
 
#define ATIM_TIMX_PWM                          TIM1
#define ATIM_TIMX_PWM_IRQn                     TIM1_UP_TIM16_IRQn
#define ATIM_TIMX_PWM_IRQHandler               TIM1_UP_TIM16_IRQHandler
#define ATIM_TIMX_PWM_CH1                      TIM_CHANNEL_1     /* 通道1 */
#define ATIM_TIMX_PWM_CH2                      TIM_CHANNEL_2     /* 通道2 */
#define ATIM_TIMX_PWM_CH3                      TIM_CHANNEL_3     /* 通道3 */
#define ATIM_TIMX_PWM_CHY_CLK_ENABLE()         do{ __HAL_RCC_TIM1_CLK_ENABLE(); }while(0)   /* TIM1 时钟使能 */

       可以把上面的宏定义分成三部分:第一部分是定时器1输出通道1、2、3对应的IO口的宏定义,同时也是控制逆变器的三个上桥臂的IO宏定义;第二部分则是控制逆变器的三个下桥臂的IO宏定义,第三部分是关于定时器1的通道、时钟等相关宏定义。

       下面我们来看下bldc_tim.c代码:

/*
 * @brief       高级定时器TIMX PWM输出初始化函数
 * @note
 *              高级定时器的时钟来自APB2,而PCLK2 = 170MHz,我们设置PPRE2不分频,因此
 *              高级定时器时钟 = 170MHz
 *              定时器溢出时间计算方法: 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的GPIO口 */
        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的GPIO口 */
        HAL_GPIO_Init(ATIM_TIMX_PWM_CH2_GPIO_PORT, &gpio_init_struct);
 
        gpio_init_struct.Pin = ATIM_TIMX_PWM_CH3_GPIO_PIN; /* 通道y的GPIO口 */
        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_13    /* PC13 */
#define SHUTDOWN_PIN_GPIO                    GPIOC
#define SHUTDOWN_PIN_GPIO_CLK_ENABLE()      do{  __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)  /* PC口时钟使能 */
 
/* 使能半桥芯片的SD引脚 */
#define SHUTDOWN_EN                         HAL_GPIO_WritePin(SHUTDOWN_PIN_GPIO, SHUTDOWN_PIN, GPIO_PIN_SET);  
 
/* 失能半桥芯片的SD引脚 */ 
#define SHUTDOWN_OFF                        HAL_GPIO_WritePin(SHUTDOWN_PIN_GPIO, SHUTDOWN_PIN, GPIO_PIN_RESET);
 
/*************************** 霍尔传感器接口 ******************************/
 
#define HALL1_TIM_CH1_PIN                     GPIO_PIN_0      /* U */
#define HALL1_TIM_CH1_GPIO                    GPIOA
#define HALL1_U_GPIO_CLK_ENABLE()           do{  __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)     /* PA口时钟使能 */
 
#define HALL1_TIM_CH2_PIN                     GPIO_PIN_1      /* V */
#define HALL1_TIM_CH2_GPIO                    GPIOA
#define HALL1_V_GPIO_CLK_ENABLE()           do{  __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)     /* PA口时钟使能 */
 
#define HALL1_TIM_CH3_PIN                     GPIO_PIN_2      /* W */
#define HALL1_TIM_CH3_GPIO                    GPIOA
#define HALL1_W_GPIO_CLK_ENABLE()           do{  __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)        /* PA口时钟使能 */
 
/***************************** 电机相关系数 *******************************/
 
#define MAX_PWM_DUTY    (((170000 / 18) - 1) * 0.96)    /* 最大占空比限制 */
#define CCW                         (1)                 /* 逆时针 */
#define CW                          (2)                 /* 顺时针 */
#define HALL_ERROR                  (0xF0)              /* 霍尔错误标志 */
#define RUN                         (1)                 /* 电机运动标志 */
#define STOP                        (0)                 /* 电机停机标志 */

       上述代码主要分为三部分:第一部分为逆变电路的半桥芯片刹车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输出,并关断所有上下桥臂。

       接着是我们的六步换相控制函数:

/*  六步换向函数指针数组 */
pctr pfunclist_m1[6] =
{
    &m1_uhwl, &m1_vhul, &m1_vhwl,
    &m1_whvl, &m1_uhvl, &m1_whul
};
 
/**
  * @brief  U相上桥臂导通,V相下桥臂导通
  * @param  无
  * @retval 无
  */
void m1_uhvl(void)
{
g_atimx_handle.Instance->CCR1 = g_bldc_motor1.pwm_duty;                     
/* U相上桥臂PWM */
    g_atimx_handle.Instance->CCR2 = 0;
    g_atimx_handle.Instance->CCR3 = 0;
    
HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT, M1_LOW_SIDE_V_PIN, GPIO_PIN_SET);     
/* V相下桥臂导通 */
HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT, M1_LOW_SIDE_U_PIN, GPIO_PIN_RESET);   
/* U相下桥臂关闭 */
HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT, M1_LOW_SIDE_W_PIN, GPIO_PIN_RESET);   
/* W相下桥臂关闭 */
}
 
/**
  * @brief  U相上桥臂导通,W相下桥臂导通
  * @param  无
  * @retval 无
  */
void m1_uhwl(void)
{
    g_atimx_handle.Instance->CCR1 = g_bldc_motor1.pwm_duty;
    g_atimx_handle.Instance->CCR2 = 0;
    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);
}
 
/**
  * @brief  V相上桥臂导通,W相下桥臂导通
  * @param  无
  * @retval 无
  */
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);
}
 
/**
  * @brief  V相上桥臂导通,U相下桥臂导通
  * @param  无
  * @retval 无
  */
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);
}
 
/**
  * @brief  W相上桥臂导通,U相下桥臂导通
  * @param  无
  * @retval 无
  */
void m1_whul(void)
{
    g_atimx_handle.Instance->CCR1 = 0;
    g_atimx_handle.Instance->CCR2 = 0;
    g_atimx_handle.Instance->CCR3 = g_bldc_motor1.pwm_duty;
 
    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);
}
 
/**
  * @brief  W相上桥臂导通,V相下桥臂导通
  * @param  无
  * @retval 无
  */
void m1_whvl(void)
{
    g_atimx_handle.Instance->CCR1 = 0;
    g_atimx_handle.Instance->CCR2 = 0;
    g_atimx_handle.Instance->CCR3 = g_bldc_motor1.pwm_duty;
 
    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拉高拉低控制开关。并且我们还定义了一个函数指针数组pfunclist_m1,指向这六个函数,方便后续我们换相时调用。

       讲到这里,bldc.c文件里的函数就讲得差不多了,接着来看下我们的核心内容,定时器的中断回调函数:

/**
 * @brief       定时器中断回调
 * @param       htim:定时器句柄
 * @retval      无
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    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                                /* 反转 */
            {
                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;
            }
        }
 
#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(85, 2, 2, 4, 8);       /* 设置时钟,170Mhz */
    delay_init(170);                            /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
    led_init();                                 /* 初始化LED */
    key_init();                                 /* 初始化按键 */
lcd_init();                                 /* 初始化LCD */
/* 中断频率18K,约55us, 频率不能过慢或过快,否则都会导致电机堵转 */
    bldc_init((170000 / 18) - 1, 0);            
    bldc_ctrl(MOTOR_1, CW, 0);                  /* 初始无刷电机接口1速度 */
   
    /* 显示提示信息 */    
    lcd_show_string(10, 10, 239, 24, 24, "BLDC Motor Test", RED);
    lcd_show_string(10, 40, 239, 24, 24, "KEY0:Start forward", RED);
    lcd_show_string(10, 70, 239, 24, 24, "KEY1:Start backward", RED);
    lcd_show_string(10, 100, 239, 24, 24, "KEY2:Stop", RED);
    printf("按下KEY0 开始正转加速\r\n");
    printf("按下KEY1 开始反转加速\r\n");
    printf("按下KEY2 停止电机\r\n");
    
    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, 130, 239, 24, 24, buf, BLUE);
            LED0_TOGGLE();                   /* LED0(红灯) 翻转 */
        }
 
        key = key_scan(0);
        if(key == KEY0_PRES)                   /* 按下KEY0设置比较值+500 */
        {
            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();                       /* 开启运行 */
        }
        else if(key == KEY1_PRES)                /* 按下KEY1设置比较值-500 */
        {
            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();                     /* 运行电机 */
        }
        else if(key == KEY2_PRES)               /* 按下KEY2关闭电机 */
        {
            stop_motor1();                      /* 停机 */
            g_bldc_motor1.run_flag = STOP;      /* 标记停机 */
            pwm_duty_temp = 0;                    /* 数据清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则可以随时停止无刷电机。大家可以自己下载程序并点击按键测试效果。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3116875/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    763916