STM32和STM32CubeMX 驱动无刷电机 保姆级教程

各位,最近我做的项目要用到无刷电机,所以,我要写一个无刷电机的驱动程序。开始,我以为很简单,以我的水平和能力,2个多星期就能搞定,结果发现很难,从9月初开始搞,一直到最近才把电机转起来,真是不容易。

其间,我也学习了不少人的BLDC驱动的文章,也请教了不少人,但是,很抱歉,没有一篇文章和一个人真正的能够提供帮助,还是得自己来摸索和理解。正是因为发现没有一遍文章能够真正的提供帮助,所以,我打算来写个保姆级教程,教大家怎么样来驱动无刷电机。

1.这中间我看过的一些文章和请教过的人,在此对他们表示感谢。

【电机控制】六步法驱动BLDC电机,使用硬件COM事件,STM32+CUBEMX(HAL库)配置_st电机库 六步算法-CSDN博客

三相直流无刷电器驱动器(BLDC)_三相无刷电机驱动程序-CSDN博客  

STM32 电机教程 13 - BLDC 电机转速计算_bldc转速检测stm32代码-CSDN博客

[科普] 无刷直流电机驱动控制原理图解_直流无刷电机驱动控制-CSDN博客

BLDC驱动学习-CSDN博客

【2022项目复盘】无位置传感器的无刷直流电机驱动设计_eg3112-CSDN博客

STM32 电机教程 33 - 无刷电机无感控制快速实现_stm32 bldc 张十三-CSDN博客

【FOC无刷电机控制】六步换向、FOC,STM32cubemx从零开始搭建BLDC六步换相代码、FOC代码(基于霍尔传感器)_stm32接霍尔传感器foc-CSDN博客

https://github.com/kingxin/6Step

三相直流无刷电机驱动设计: 基于STM32的三相无刷直流电机驱动设计

(还有几套代码,我暂时找不到链接了,等找到再补上)

(对EG公司的李卫伟工表示感谢,对我的朋友杨峰表示感谢,对安富利的邓工和Cavin表示感谢,尤其是Cavin,真的是电机控制的专家,对王大可表示感谢,没错,就是上面开源了的王大可,感谢电机公司的罗工和zyj工)

2. 好了,直接上教程

2.1 用STM32CubeMX进行配置。

我用的是timer8产生PWM波,用Timer3做hall信号的采样

 

Timer3就是这么简单。 

 

Timer8,就是这么简单。

时钟配成170MHz,其他没有啥了。

 这里有个重中之重,要说明一下,就是,我的PWM的互补输出是配置成反的。因为我的预驱芯片用的EG2131,这块片子的低边驱动是低有效。【这是我花费了很多时间,才搞对,而请教的所有人都不得要领,没有一个人觉得这里重要,我问预驱逻辑,他们都很奇怪的看着我,说,跟预驱逻辑有啥关系?结果还真跟这里的逻辑有关系。当然,EG公司的李工给了我正确的解答,说要反过来,但是他不用stm32, 不懂怎么配,所以,也不得要领。最后,还是我自己摸对了】

2.2 生成代码后就是要编程了。我是用vscode来编辑的,所以,生成代码时,我选的是CMake。

重点程序片段是:

  /* start 6channels PWM */
  HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);
  HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_3);
  HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_1);
  HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_2);
  HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_3);
/* start hallsensor timer interrupt */
__HAL_TIM_ENABLE_IT(&htim3, TIM_IT_UPDATE);
HAL_TIMEx_HallSensor_Start_IT(&htim3);
uint8_t get_hall_pos(void)
{
  uint8_t hallpos  = 0;
  uint8_t hallpos_u = 0;
  uint8_t hallpos_v = 0;
  uint8_t hallpos_w = 0;

  hallpos_u = HAL_GPIO_ReadPin(M2_H1_GPIO_Port, M2_H1_Pin);
  hallpos_v = HAL_GPIO_ReadPin(M2_H2_GPIO_Port, M2_H2_Pin);
  hallpos_w = HAL_GPIO_ReadPin(M2_H3_GPIO_Port, M2_H3_Pin);

  hallpos = (hallpos_u<<2) | (hallpos_v<<1) | hallpos_w;

  return hallpos;

}

 下面是一半的换相逻辑,基本参考kingxin的代码,然后我摸索出来的。

void motor_change(uint8_t hallpos, uint8_t direction)
{
  /* direction 0 cw, direciton 1 ccw */
  if(direction==0)    // direction 0 cw
  {
    switch (hallpos)
    {
    case 5/* constant-expression */:
      /* UH + VL */ 
      /* channel1 UH pwm, UL off */
      LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1);     
      LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1);
      LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH1N);

      /* channel2 VH off, VL on */
      LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_ACTIVE);
	  LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH2N);
      LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH2);
		  
      /*  channel3 WH OFF, WL OFF */
      LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_INACTIVE);
	  LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH3);
	  LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH3N);

      break;

    case 4:
      /* UH + WL */  
      /* channel1 UH pwm, UL off */
      LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1);     
      LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1);
      LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH1N);

      /* channel2 VH off, VL off */
	  LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_INACTIVE);
      LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH2);
	  LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH2N);
		  
      /*  channel3 WH OFF, WL ON */
      LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_ACTIVE);
	  LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH3N);
      LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH3);

      break;

    case 6:
      /* VH + WL */  
      /* channel1 UH off, UL off */
      LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_INACTIVE);     
      LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1N);
      LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH1);

      /* channel2 VH pwm, VL off */
	  LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_PWM1);
      LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH2);
	  LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH2N);
		  
      /*  channel3 WH OFF, WL ON */
      LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_ACTIVE);
	  LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH3N);
      LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH3);

      break;

    case 2:
      /* VH + UL */   // 6
      /* channel1 UH off, UL on */
      LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_INACTIVE);     
      LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1N);
      LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1);

      /* channel2 VH pwm, VL off */
	  LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_PWM1);
      LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH2);
	  LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH2N);
		  
      /*  channel3 WH off, WL off */
      LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_INACTIVE);
	  LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH3);
	  LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH3N);  

      break;

    case 3:
      /* WH + UL */    
      /* channel1 UH off, UL on */
      LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_INACTIVE);     
      LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1N);
      LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1);

      /* channel2 VH off, VL off */
	  LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_INACTIVE);
      LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH2);
	  LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH2N);
		  
      /*  channel3 WH pwm, WL off */
      LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_PWM1);
	  LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH3);
	  LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH3N);  

      break;

    case 1:
      /* WH + VL */   
      /* channel1 UH off, UL off */
      LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_INACTIVE);     
      LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH1);
      LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH1N);

      /* channel2 VH off, VL on */
	  LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_ACTIVE);
      LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH2N);
      LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH2);
		  
      /*  channel3 WH pwm, WL off */
      LL_TIM_OC_SetMode(TIM8, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_PWM1);
	  LL_TIM_CC_EnableChannel(TIM8, LL_TIM_CHANNEL_CH3);
	  LL_TIM_CC_DisableChannel(TIM8, LL_TIM_CHANNEL_CH3N);  

      break;

    default:
        usb_printf("===== hall pos error =====\r\n");
      break;
    }
  }

下面是中断服务函数,换相和速度计算都是在这里完成。换相的思路很简单,就是timer3会通过xor在hall信号的每一个跳变产生一个计时的update中断,在这个中断服务函数里进行换相和改变占空比。timer8专门产生PWM波,跟换相没什么关系。 速度计算很容易,我的电机是5对极,转一圈是30个hall跳变,也就是30个换相。我计300个换相也就是10圈的时间,然后换算成rpm就行了。跟电机实际转的情况对了一下,计算的转速,基本还是准的。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  uint8_t my_hall_pos = 0;
  uint8_t my_pre_hall_pos = 0;

  if(htim->Instance==TIM3)
  {

    if(motor_status==2) // motor is running
    {
      my_hall_pos = get_hall_pos();
      if(my_pre_hall_pos!=my_hall_pos)
      {
        my_pre_hall_pos=my_hall_pos;
        __HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, (uint32_t)(period/1.2));
        __HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, (uint32_t)(period/1.8));
        __HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, (uint32_t)(period/2.5));

        motor_change(my_hall_pos, motor_direction);

        if(motor_speed_cnt==0)
        {
          systick1 = HAL_GetTick();
        }
        motor_speed_cnt++;
        if(motor_speed_cnt==300)
        {
          systick2 = HAL_GetTick();
          motor_speed_cnt = 0;

          usb_printf("motor speed is %d rpm\r\n",  (int)( ((float)10/(systick2-systick1))*1000*60)  );
        }

      }
      else
      {
        /* do nothing */
      }
    }
    else
    {
      usb_printf(" Error!!! motor is not running\r\n");
    }

  }
}

基本就是这些重要的内容了,但是,无刷电机的驱动,还是比较复杂的,我上面给出的重要内容,还是需要小伙伴们边做边思考才能最终做出来的。但是,我相信,大家一定能够做出来。

好了,大功告成,亲个嘴儿。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值