【程序】在联控智能STM32F405RG开发板(低压板)上用方波驱动24V无刷电机(使用3个预驱芯片IR2101S和6个IRF540N N-MOS管)

16 篇文章 7 订阅
4 篇文章 1 订阅

程序和开发板原理图的下载地址:https://pan.baidu.com/s/1PNxL5kNyIj1ki7SCAZ04lQ?pwd=vmfi
本程序通过方波驱动联控智能的24V无刷电机。开发板为联控智能低压板,使用的单片机为STM32F405RG,晶振的大小为8MHz,如下图所示。程序实现了电机正转、反转、停转、速度增加和速度减小这五种功能,分别由按键1~5控制。程序为开环控制,没有实现PID速度闭环,也没有利用开发板上的各种电路保护器件(如MOS管过流保护、总电源过压保护等等)。所以,在运行程序前,一定要保证开发板供电电压为24V,而且电机没有被堵转。

以下是电机参数:

从电机参数中,我们可以直接看到什么HALL值下该换什么相,方便编写程序。

【程序代码】

main.c:

#include <stdio.h>
#include <stm32f4xx.h>
#include "common.h"
#include "Keyboard.h"
#include "Motor.h"

int main(void)
{
  int key;
  
  HAL_Init();
  
  clock_init();
  usart_init(115200);
  printf("STM32F405RG Motor\n");
  printf("SystemCoreClock=%u\n", SystemCoreClock);
  
  Keyboard_Init();
  Motor_Init();
  while (1)
  {
    if (key == -1)
    {
      // 等待按键按下
      key = Keyboard_Read();
      if (key != -1)
      {
        // 按键处理
        switch (key)
        {
          case 1:
            Motor_Start(MOTORDIRECTION_FORWARD);
            break;
          case 2:
            Motor_Start(MOTORDIRECTION_BACKWARD);
            break;
          case 3:
            Motor_Stop();
            break;
          case 4:
            Motor_SpeedUp();
            break;
          case 5:
            Motor_SpeedDown();
            break;
        }
      }
    }
    else
    {
      // 等待按键松开
      if (Keyboard_Read() == -1)
        key = -1;
    }
  }
}

Motor.c:

#include <stdio.h>
#include <stdlib.h>
#include <stm32f4xx.h>
#include "Motor.h"

// HALL信号
#define HALL_U (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6) == GPIO_PIN_SET)
#define HALL_V (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET)
#define HALL_W (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET)
// 上管控制
#define UH_0 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 0)
#define UH_1 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, abs(motor_speed))
#define VH_0 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, 0)
#define VH_1 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, abs(motor_speed))
#define WH_0 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, 0)
#define WH_1 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, abs(motor_speed))
// 下管控制
#define UL_0 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET)
#define UL_1 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET)
#define VL_0 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET)
#define VL_1 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET)
#define WL_0 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_RESET)
#define WL_1 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET)

// 霍尔值表
static const uint8_t motor_halltable[2][6] = {
  {2, 6, 4, 5, 1, 3}, // 正转
  {5, 1, 3, 2, 6, 4}  // 反转
};

TIM_HandleTypeDef htim1;
static int8_t motor_lasthall = -1;
static int16_t motor_speed;
static uint32_t motor_lasttime;

void Motor_Init(void)
{
  GPIO_InitTypeDef gpio;
  TIM_OC_InitTypeDef tim_oc = {0};
  
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_TIM1_CLK_ENABLE();
  
  // IR2101S的HIN和LIN上面是没有横线的, 一定要把PDF放大了看, 不要看错了
  // HIN与HO, LIN与LO始终都是同向的
  
  // U,V,W上管 (高电平导通MOS管)
  gpio.Alternate = GPIO_AF1_TIM1;
  gpio.Mode = GPIO_MODE_AF_PP;
  gpio.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10;
  gpio.Pull = GPIO_NOPULL;
  gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOA, &gpio);
  
  // HALL信号
  gpio.Mode = GPIO_MODE_IT_RISING_FALLING;
  gpio.Pin = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8;
  gpio.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOB, &gpio);
  
  // U,V,W下管 (高电平导通MOS管)
  UL_0;
  VL_0;
  WL_0;
  gpio.Mode = GPIO_MODE_OUTPUT_PP;
  gpio.Pin = GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
  gpio.Pull = GPIO_NOPULL;
  gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOB, &gpio);
  
  htim1.Instance = TIM1;
  htim1.Init.Period = 12000;
  htim1.Init.Prescaler = 0;
  HAL_TIM_PWM_Init(&htim1);
  
  // 这里的Pulse就是寄存器中的CCRx
  // 在PWM1模式下, 若OCPolarity=TIM_OCPOLARITY_HIGH, 只要CNT<CCR, 输出的就是高电平; 反之当CNT>=CCR时输出低电平
  // 若CCR=0则一直输出低电平, 因为CNT永远不会小于CCR
  // 若CCR=ARR+1则一直输出高电平, 因为CNT永远小于CCR (ARR就是Init.Period)
  tim_oc.OCMode = TIM_OCMODE_PWM1;
  tim_oc.Pulse = 0;
  HAL_TIM_PWM_ConfigChannel(&htim1, &tim_oc, TIM_CHANNEL_1);
  HAL_TIM_PWM_ConfigChannel(&htim1, &tim_oc, TIM_CHANNEL_2);
  HAL_TIM_PWM_ConfigChannel(&htim1, &tim_oc, TIM_CHANNEL_3);
  
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
  
  HAL_NVIC_SetPriority(EXTI9_5_IRQn, 1, 0);
  HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
}

uint8_t Motor_ReadHall(void)
{
  return (HALL_W << 2) | (HALL_V << 1) | HALL_U;
}

int Motor_SpeedDown(void)
{
  int16_t new_speed;
  
  if (motor_speed == 0)
    return Motor_Start(MOTORDIRECTION_BACKWARD);
  
  new_speed = motor_speed - 1000;
  if (motor_speed > 0 && new_speed <= 0)
    return Motor_Stop();
  else if (abs(new_speed) > htim1.Init.Period)
    return -1;
  
  motor_speed = new_speed;
  printf("Motor speed: %d\n", motor_speed);
  return 0;
}

int Motor_SpeedUp(void)
{
  int16_t new_speed;
  
  if (motor_speed == 0)
    return Motor_Start(MOTORDIRECTION_FORWARD);
  
  new_speed = motor_speed + 1000;
  if (motor_speed < 0 && new_speed >= 0)
    return Motor_Stop();
  else if (abs(new_speed) > htim1.Init.Period)
    return -1;
  
  motor_speed = new_speed;
  printf("Motor speed: %d\n", motor_speed);
  return 0;
}

int Motor_Start(MotorDirection direction)
{
  if (motor_speed != 0)
    return -1;
  
  motor_speed = 1000 * direction;
  __HAL_GPIO_EXTI_GENERATE_SWIT(GPIO_PIN_6); // 软件触发HALL中断
  printf("Motor is started! speed=%d\n", motor_speed);
  return 0;
}

int Motor_Stop(void)
{
  if (motor_speed == 0)
    return -1;
  
  motor_speed = 0;
  motor_lasthall = -1;
  UH_0;
  VH_0;
  WH_0;
  UL_0;
  VL_0;
  WL_0;
  
  printf("Motor is stopping...\n");
  motor_lasttime = HAL_GetTick();
  while (HAL_GetTick() - motor_lasttime < 250);
  printf("Motor is stopped!\n");
  return 0;
}

void Motor_Switch(void)
{
  uint8_t direction, hall;
  
  hall = Motor_ReadHall();
  if (motor_speed == 0)
  {
    // 电机停转时, 手动旋转电机, 可以读取HALL值
    printf("Hall value: %u\n", hall);
    motor_lasttime = HAL_GetTick();
    return;
  }
  else if (motor_speed > 0)
    direction = 0; // 正转
  else
    direction = 1; // 反转
  
  // 忽略重复的中断
  if (hall == motor_lasthall)
    return;
  motor_lasthall = hall;
  
  // 霍尔换相
  if (hall == motor_halltable[direction][0])
  {
    // AB (A的上管导通, B的下管导通)
    // U=A, V=B, W=C
    UH_1;
    VH_0;
    WH_0;
    // 先关闭其他下管, 再开需要的下管
    UL_0;
    WL_0;
    VL_1;
  }
  else if (hall == motor_halltable[direction][1])
  {
    // AC (A的上管导通, C的下管导通)
    UH_1;
    VH_0;
    WH_0;
    UL_0;
    VL_0;
    WL_1;
  }
  else if (hall == motor_halltable[direction][2])
  {
    // BC
    UH_0;
    VH_1;
    WH_0;
    UL_0;
    VL_0;
    WL_1;
  }
  else if (hall == motor_halltable[direction][3])
  {
    // BA (B的上管导通, A的下管导通)
    UH_0;
    VH_1;
    WH_0;
    VL_0;
    WL_0;
    UL_1;
  }
  else if (hall == motor_halltable[direction][4])
  {
    // CA
    UH_0;
    VH_0;
    WH_1;
    VL_0;
    WL_0;
    UL_1;
  }
  else if (hall == motor_halltable[direction][5])
  {
    // CB
    UH_0;
    VH_0;
    WH_1;
    UL_0;
    WL_0;
    VL_1;
  }
}

void EXTI9_5_IRQHandler(void)
{
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6);
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_7);
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8);
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  switch (GPIO_Pin)
  {
    case GPIO_PIN_6:
    case GPIO_PIN_7:
    case GPIO_PIN_8:
      Motor_Switch();
      break;
  }
}

Motor.h:

#ifndef _MOTOR_H
#define _MOTOR_H

typedef enum
{
  MOTORDIRECTION_FORWARD = 1,
  MOTORDIRECTION_BACKWARD = -1
} MotorDirection;

void Motor_Init(void);
uint8_t Motor_ReadHall(void);
int Motor_SpeedDown(void);
int Motor_SpeedUp(void);
int Motor_Start(MotorDirection direction);
int Motor_Stop(void);
void Motor_Switch(void);

#endif

【程序详解】

关于无刷电机的驱动原理请参阅:直流无刷电机是什么?在机器人、电动汽车、航模/无人机等方面有何应用? - 知乎

程序使用motor_speed全局变量保存电机的目标速度,电机的实际速度为HALL中断的触发频率。motor_speed=0为停转,motor_speed>0为正转,motor_speed<0为反转。motor_speed的取值范围为-12000~12000。

电机的驱动电路由6个MOS管和3个预驱芯片IR2101S构成。MOS管的上管为Q1、Q3和Q5,MOS管的下管为Q2、Q4和Q6。每对上下管由一个IR2101S预驱动。
Q1和Q2控制A相(也叫U相),Q3和Q4控制B相(也叫V相),Q5和Q6控制C相(也叫W相)。A、B、C相线的颜色分别为黄、绿、蓝。

其中Q1和Q2的电路如下图所示(其他两路类似):

由于MOS管栅极的导通电压过高,超过了单片机的电平输出能力(3.3V),所以需要使用预驱芯片IR2101S进行升压,升压后再接到MOS管的栅极上。

值得注意的是,IR2101S芯片手册里面的图片,缩小了看,好像HIN和LIN上面有横线,但其实放大了之后根本没有:

所以HIN和HO,LIN和LO是同相的。单片机给HIN(或LIN)输入什么电平,HO(或LO)就输出什么电平。上下管采用的是同型号MOS管,都是N-MOS管。由于采用了同型号管,当下管截止的时候,上管源极的电压就由芯片的VB和VS来提供,这两个电压分别叫做High side floating supply和High side floating supply return,具体不用深究。电路的接法就是参考的IR2101S手册上的Typical Connection,VB和VS上面接有二极管和电容。

我们只需要知道,只要单片机给高电平,相应的MOS管就导通就行了。并且要注意,同一路的上管和下管绝不允许同时导通,否则会发生短路,烧毁MOS管

我们用“AB”表示A相的上管通,B相的下管通。也就是左边字母的上管通,右边字母的下管通。“BA”就表示B相的上管通,A相的下管通。

HALL值我们用WVU的顺序表示,U,V,W=0,0,1表示为二进制的100也就是十进制的4。十进制的3转换为二进制是011,所以表示的是U,V,W=1,1,0。

根据前面的电机参数图,可以表示出每个HALL值的换向顺序:

HALL4(HA=0,HB=0,HC=1,倒过来是二进制的100,等于十进制的4)=>BC(B的上管通B相就是24V高电平,C的下管通C相就接地了,对应到表中就是V输出1,W输出0,而U的上下管都不通,悬空)

HALL5=>BA(这回是U接地了,不再是W接地,所以C的下管要截止,A的下管要导通)

HALL1=>CA

HALL3=>CB

HALL2=>AB

HALL6=>AC

这就是正转的换相顺序。在换相函数中,我们是从AB开始判断,所以正转的霍尔顺序就是264513,用motor_halltable全局数组保存:

// 霍尔值表
static const uint8_t motor_halltable[2][6] = {
  {2, 6, 4, 5, 1, 3}, // 正转
  {5, 1, 3, 2, 6, 4}  // 反转
};

反转就是把右边的字母左右对调,AB换成BA,AC换成CA等等,于是:

HALL4=>CB

HALL5=>AB

HALL1=>AC

HALL3=>BC

HALL2=>BA

HALL6=>CA

字母对调就相当于把电压反了过来,本来AB是A相24V,B相0V,反过来BA就是A相0V,B相24V,电机就可以反转了。

于是反转的霍尔值表就是513264。(从AB开始判断,第一个HALL值就是5)

从表中还可以发现,同一列正转的HALL值和翻转的HALL值相加,刚好等于7。比如2+5=7、6+1=7等等。

在电机停转的情况下,手动旋转电机,可以在串口中看到每转一定角度HALL值就变化一次,正转和反转的变化序列刚好相反。我们在程序中要做的,正是电机在转起来的情况下,遇到每个HALL值该怎么换相。

HALL信号接的单片机I/O口为PB6~8,我们在这三个引脚上打开EXTI外部中断,任何一个引脚上的电平发生变化,都将触发中断,读取HALL值并换相。上管采用定时器1的PWM波输出,控制电机的转速。下管直接输出高低电平。

上管控制:TIM1_CH1~3(高电平导通)
下管控制:PB13~15(高电平导通)

定时器选择的是PWM1模式,CCR(tim_oc.Pulse以及__HAL_TIM_SET_COMPARE的第三个参数)决定了占空比。

在PWM1模式下,若OCPolarity=TIM_OCPOLARITY_HIGH:
(1)只要CNT<CCR,输出的就是高电平。反之当CNT>=CCR时输出低电平。
(2)若CCR=0则一直输出低电平,因为CNT永远不会小于CCR。
(3)若CCR=ARR+1则一直输出高电平,因为CNT永远小于CCR (ARR就是Init.Period)。

所以,要想关闭上管,只需要将对应通道的CCR的值设为0就可以了。

启动电机时,先设置motor_speed的值,然后用__HAL_GPIO_EXTI_GENERATE_SWIT(GPIO_PIN_6)这条语句强制触发外部中断,根据当前HALL值开启MOS管,电机就开始转起来了。

关闭电机时,先关闭所有的MOS管,然后用while语句等待HALL值不再变化,电机就停止了。更改电机转速只需要修改motor_speed全局变量就可以,但是如果要改变电机转向,则一定要先让电机停下来,再进行反向转动。也就是说motor_speed一定不能从正值直接改为负值,或者从负值直接改为正值。电机在转动过程中,motor_speed也不允许直接清零,以免HALL中断停止工作后某些MOS管一直导通。要想停转必须调用Motor_Stop函数。

电机在高速转动下可以立即停下来:

Motor is started! speed=1000
Motor speed: 2000
Motor speed: 3000
Motor speed: 4000
Motor speed: 5000
Motor speed: 6000
Motor speed: 7000
Motor speed: 8000
Motor speed: 9000
Motor speed: 10000
Motor speed: 11000
Motor speed: 12000
Motor is stopping...
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Hall value: 1
Hall value: 5
Hall value: 4
Hall value: 6
Hall value: 2
Hall value: 3
Motor is stopped!

更换了轮子之后,HALL值表可能需要进行修改。比如下面这个大轮子,霍尔值表就是:

// 霍尔值表
static const uint8_t motor_halltable[2][6] = {
  {1, 3, 2, 6, 4, 5}, // 正转
  {6, 4, 5, 1, 3, 2}  // 反转
};

这个大轮子的霍尔值表仍然满足每列相加等于7的规律。

----------------------------------------------------------------------------------------------------------------------------------

后来笔者根据联控智能的电路图自己画了一个板子,发现同样的程序有时候启不动电机。仔细看了下是因为板子上的预驱芯片有一个引脚没有上锡,没有和板子的焊盘接触。重新焊了一下就好了,上电后每次都能成功启动电机。程序本身没有问题。

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值