相关推荐recommended
Stm32-使用TB6612驱动电机及编码器测速
作者:mmseoamin日期:2023-12-14

这里写目录标题

    • 起因
    • 一、电机及编码器的参数
    • 二、硬件
    • 三、接线
    • 四、驱动电机
      • 1、TB6612电机驱动
      • 2、定时器的PWM模式驱动电机
      • 五、编码器测速
        • 1、定时器的编码器接口模式
        • 2、定时器编码器模式测速的原理
        • 3、编码器模式的配置
        • 4、编码器模式相关代码
        • 5、测速方法
        • 六、相关问题以及解答
          • 1、编码器模式下的自动重装值ARR和预分频PSC应该如何设置
          • 2、如何判断正反转
          • 3、圈数如何计算
          • 4、转速如何计算
          • 5、为什么我的编码器没有输出,获取到的脉冲数是0
          • 七、测速硬件展示及测速现象
          • 八、总结
          • 九、大家可以参考
            • 参考链接1
            • 参考链接2
            • 参考链接3
            • 参考链接4

              起因

              最近在学习编码电机以及尝试使用编码电机测速。遇到了很多问题,花费了很多时间,在这里做一个记录,对自己学习到的知识进行一个总结

              找了很多资料,看了很多视频,这些太多了,以至于让我不知道究竟哪一个是正确的,今天看这个,明天看这个,导致自己的学习效率低下

              当然,有很多大佬的文章和资料给了我很大的启发

              这个电机我玩了四天,把自己觉得重要的东西和大家分享一下

              现在一般都是用编码器电机,参加比赛啥的,编码电机常用于测速,所以编码电机就成了一个必须学习的知识点

              编码器被广泛应用于电机测速,实现电机闭环控制。

              相关的知识点有:定时器的输出比较(输出PWM)、定时器的输入捕获,定时器的编码器接口、速度控制


              一、电机及编码器的参数

              编码电机其实就是一个带有编码器的电机,我的这个电机是一个增量式的带霍尔传感器的电机

              电机的型号是JGB37-520电机

              下方是电机的参数

              主要关注的就是电机的额定电压 12V

              电机的减速比 30(这个很重要)

              编码器的参数

              主要关注编码器的线数 11线 (也就是说电机转一圈会产生11个脉冲

              供电电压 5V

              输出类型 方波

              Stm32-使用TB6612驱动电机及编码器测速,在这里插入图片描述,第1张

              编码器的连接

              一般这种编码器都有六根线

              两边靠外的两根线是电机电源线

              往里两根是编码器的电源线

              中间两根是编码器的A,B相

              Stm32-使用TB6612驱动电机及编码器测速,在这里插入图片描述,第2张

              具体大概是啥意思呢?

              就是电机转动的时候编码器会通过编码电机的A相和B相输出两个正交的方波

              通过输出的两个方波就可以对电机进行测速和识别电机的方向


              二、硬件

              整体结构采用洞洞板+TB6612+Stm32C8T6+编码电机(起初采用的是这种结构)

              后面采用Stm32ZET6+TB6612+洞洞板+编码电机+12V电源(原因是C8T6烧坏了,哭😥)

              主控Stm32C8T6 or Stm32ZET6

              电机驱动 TB6612(由于上一个L298N烧了)

              520霍尔编码电机

              12V电源


              三、接线

              这里展示驱动一个编码电机的示例,毕竟先从一个电机玩起,弄懂后后面就会使用的更加得心应手啦

              主要使用到了定时器的PWM模式(输出比较)功能

              大家一定要认真接线,看清出每根线的作用,不要随便接线,一不小心电机驱动就烧了,或者是单片机烧了(在学习的时候就烧了一个单片机,人民币-15)

              注意这个是我实现的接线,大家可以根据自己单片机的片上资源合理选择,选择合适的IO口

              电机驱动

              TB6612C8T6
              STBY高电平(+3.3V)
              AIN1PB14
              AIN2PB15
              PWMAPA8 (TIM1-CH1)
              AO1电机电源+
              AO2电机电源-
              VM12V
              VCC3.3V
              GND和单片机共地

              编码器

              编码器的A、B相C8T6
              A相PA0 (TIM2-CH1)
              B相PA1 (TIM2-CH2)

              四、驱动电机

              1、TB6612电机驱动

              首先了解一下TB6612

              下图是TB6612驱动模块

              原理图

              Stm32-使用TB6612驱动电机及编码器测速,在这里插入图片描述,第3张

              STBY接高电平 清零电机全部停止

              置 1 通过 AIN1 AIN2, BIN1,BIN2 引脚来控制正反转

              PWM引脚控制占空比

              VM: 接 12V 以内电源

              VCC: 接 5V 电源

              GND: 接电源负极

              下图是驱动逻辑

              可以看出IN引脚控制正反转,PWM引脚控制速度

              Stm32-使用TB6612驱动电机及编码器测速,第4张

              2、定时器的PWM模式驱动电机

              使用定时器的PWM模式 生成一个需要的 占空比可调的 频率 符合要求的方波信号。

              方波信号的频率不宜过高或者过低,过高容易导致电机驱动的晶闸管经常处于开关状态–发热巨大;过低则容易产生噪音,对电机也低频的冲击

              这里输出PWM信号的定时器是TIM1-CH1

              设置成PWM模式,频率和占空比可调

              有关定时器PWM模式,可以看其他大佬的文章和资料,看看手册

              可以看看江科大的教学视频,比我讲的详细多了,也很好理解

              我贴出视频链接,大家学习32的时候可以跟他

              TIM输出比较,PWM模式

              下方的PWM模式的代码作为一个参考

              Motor.h

              #ifndef __MOTOR_H
              #define __MOTOR_H
              #include "sys.h"	 
              #define PWMA   TIM1->CCR1  //PA8  PWMA  TIM1_CH1
              #define AIN2   PBout(15)
              #define AIN1   PBout(14)
              void Motor_PWM_Init(u16 arr,u16 psc);
              void Motor_SetSpeed(u8 mode ,u16 speed);
              #endif
              

              Motor.c

              void Motor_Init(void)	//IN引脚初始化
              {
                  GPIO_InitTypeDef GPIO_InitStructure;
                  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能PB端口时钟
                  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14|GPIO_Pin_15;	//端口配置
                  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;      //推挽输出
                  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     //50M
                  GPIO_Init(GPIOB, &GPIO_InitStructure);				 //根据设定参数初始化GPIOB
              }
              void Motor_PWM_Init(u16 arr,u16 psc)	//PWM引脚初始化
              {
                  GPIO_InitTypeDef GPIO_InitStructure;
                  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
                  TIM_OCInitTypeDef  TIM_OCInitStructure;
                  Motor_Init();
                  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1 | RCC_APB2Periph_AFIO,ENABLE);//开启时钟
                  //输出TIM1 CH1 
                  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM_CH1 
                  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
                  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
                  GPIO_Init(GPIOA, &GPIO_InitStructure);
                  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);//初始化定时器。
                  TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
                  TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值  不分频
                  TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
                  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
                  TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
                  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
                  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
                  TIM_OCInitStructure.TIM_Pulse = 0;                            //设置待装入捕获比较寄存器的脉冲值
                  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;     //输出极性:TIM输出比较极性高
                  TIM_OC1Init(TIM1, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
                  TIM_CtrlPWMOutputs(TIM1,ENABLE);	//MOE 主输出使能		高级定时器一定要写这个语句
                  TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
                  TIM_Cmd(TIM1, ENABLE);  //使能TIM1
              }
              void Motor_SetSpeed(u8 mode ,u16 speed)		//mode 代表正反转 speed PWM占空比即速度
              {
                  PWMA = speed;
                  if(mode==1)
                  {
                      AIN1 = 1;
                      AIN2 = 0;
                  }
                  else {
                      AIN1 = 0;
                      AIN2 = 1;
                  }
              }
              

              五、编码器测速

              编码器一般应用于电机控制,使用PWM驱动电机,然后再使用编码器测量速度,再使用PID算法进行闭环控制

              记住下面这句话

              在一定的时间内,电机转动一圈,通过霍尔传感器的A、B两相输出一定数量的脉冲,我们可以根据一定时间内的脉冲数计算出电机的瞬时速度。

              1、定时器的编码器接口模式

              采用的是定时器的编码器接口模式,Stm32中的定时器只有TIM1-5和TIM8才有编码器接口功能,而且只有CH1通道和CH2通道有用。

              2、定时器编码器模式测速的原理

              原理:接收编码器的A、B相产生的正交信号,根据编码器产生的正交信号脉冲,自动控制CNT自增或自减,根据计数方向和编码器的信号关系来指示编码器的位置、旋转方向和旋转速度,利用脉冲值来计算电机的转动位移

              这个可以参考手册里定时器的编码器模式,比我讲的清楚多了

              定时器的编码器接口托管了输入捕获的前两个接口

              还有一句话记住,编码器模式下就相当于一个带有方向选择的外部时钟

              3、编码器模式的配置

              具体配置流程就是

              时钟–>GPIO–>时基单元配置–>编码器接口配置–>开启定时器–>读取一个时间段内的脉冲–>计算电机旋转轴转速

              使用这个函数把定时器设置为编码器接口模式

              TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
              

              采用的是编码器模式3,在TI1和TI2边沿都计数,也就是在一个周期内对A相和B相的上升沿下降沿都计数,一个周期内计4次,所以采用这种模式后,相应的计数值(CNT)就会变成4倍,这就是很多资料里说的四倍频计数。

              4、编码器模式相关代码

              采用的是定时器2的编码器接口模式,通道1和通道2捕获

              encoder.c

              /**
               * @brief 把TIM2初始化为编码器接口模式
               * @param psc 预分频系数
               * @param arr 自动重装载值
               * @retval None
               */
              void Encoder_Init_TIM2(uint16_t psc,uint16_t arr)	
              {
                  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
                  TIM_ICInitTypeDef TIM_ICInitStructure;
                  GPIO_InitTypeDef GPIO_InitStructure;
                  //NVIC_InitTypeDef  NVIC_InitStructure;
                  //使能定时器2的时钟
                  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
                  //使能PB端口时钟
                  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
                  //端口配置
                  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
                  //浮空输入
                  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
                  //根据设定参数初始化GPIOB
                  GPIO_Init(GPIOA, &GPIO_InitStructure);
                  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
                  // 预分频器
                  TIM_TimeBaseStructure.TIM_Prescaler = psc;
                  //设定计数器自动重装值
                  TIM_TimeBaseStructure.TIM_Period = arr;
                  //选择时钟分频:不分频
                  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
                  //TIM向上计数
                  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
                  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
                  //使用编码器模式3
                  TIM_EncoderInterfaceConfig(TIM2, \
                                             TIM_EncoderMode_TI12, \
                                             TIM_ICPolarity_Rising, \
                                             TIM_ICPolarity_Rising);
                  TIM_ICStructInit(&TIM_ICInitStructure);
                  TIM_ICInitStructure.TIM_ICFilter = 10;
                  TIM_ICInit(TIM2, &TIM_ICInitStructure);
                  //清除TIM的更新标志位
                  TIM_ClearFlag(TIM2, TIM_FLAG_Update);
                  TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
                  //Reset counter
                  TIM_SetCounter(TIM2,0);
                  TIM_Cmd(TIM2, ENABLE);
              }
              /**
               * @brief 单位时间读取编码器计数
               * @param TIMX 定时器
               * @retval 速度值	是编码器返回的脉冲
               */
              int Read_Encoder()
              {
                  	int Encoder_TIM;
                      Encoder_TIM= (short)TIM2 -> CNT;
                      //Encoder_TIM= (int)((int16_t)(TIM4->CNT));;
                      TIM2 -> CNT=0;
                 	    return Encoder_TIM;
              }
              

              encoder.h

              #ifndef __ENCODER_H
              #define __ENCODER_H
              #include "sys.h"
              	 
              void Encoder_Init_TIM2(uint16_t psc,uint16_t arr);
              int Read_Encoder();
              #endif
              

              5、测速方法

              在一个时间周期T0内,定时的读取编码器产生的脉冲,以我的编码器为例(11线,减速比30),转一圈会产生1320个脉冲(因为采用的是编码模式3)

              这个1320 = 11 * 30 * 4

              通过在固定的周期T0内,产生的脉冲就相当于路程,而这个固定的周期就相当于时间

              所以速度就等于 在T0这段时间获取到的脉冲总数/(编码器单圈产生的总脉冲数*T0)

              对于我的电机就是

              T0这段时间获取到的脉冲总数/(1320*T0)

              六、相关问题以及解答

              在看了前面之后,应该对编码器模式和编码器测速有了一个大概的认识,知道了它测速的原理,但肯定有好多疑问,我把我学习过程中遇到的问题和解决方法做一个总结,你肯定也有这些疑问,不要着急,看下去。

              1、编码器模式下的自动重装值ARR和预分频PSC应该如何设置

              ARR,自动重装值,指的是CNT计数自增或自减到ARR就会溢出,(可以产生中断),然后继续从0开始计数。

              PSC,预分频系数,(前面不是说过编码器相当于一个外部时钟吗),PSC相当于外部时钟的频率,如果分频(假设PSC=2)的话,就会(比如电机转一圈产生100个脉冲),此时编码器模式下只能计数50个脉冲。

              所以我们应该如何设置?

              PSC呢? PSC没有必要设置,因为我要计数的本来就是电机转动一圈产生的真实脉冲,所以PSC给0就好啦

              ARR呢? 目前在各种论坛和博客和资料中有两种版本。

              第一种,根据电机的线数和减速比来设置,比如我的电机是11线,减速比30,转动一圈的脉冲数是1320,这个值就可以设置为1320。

              产生的脉冲数恰好是你定时器溢出的时候,溢出一次记录一次,这个的次数就是电机的圈数(当然这种误差很大)

              也就是说电机转一圈正好是1320,当CNT计数到ARR时,计数器就会清零并且重新计数,所以这个ARR就是电机转一圈产生的脉冲数的最大值。

              第二种,直接设置成定时器ARR的最大值,也就是65535(2^16-1),这样设置的目的就是无论你电机产生多少脉冲,都可以记录,且不会溢出。

              不过使用65535的话,就要在最开始的时候初始化编码器模式提前把CNT清零,然后再开始计数。再在一个周期内定时读取脉冲数,再清零,这个脉冲也是周期内读取到的脉冲值。

              电机旋转一圈能产生脉冲,那么我们就能记录一段时间产生的脉冲数来计算速度

              2、如何判断正反转

              可以通过判断CR1寄存器中的DIR位,这个位是计数方向位

              Stm32-使用TB6612驱动电机及编码器测速,在这里插入图片描述,第5张

              正转就是CNT向上计数(DIR==0)

              反转就是CNT向下计数(DIR==1)

              3、圈数如何计算

              就是上方说的,把ARR设置为电机旋转一周产生的脉冲数

              电机转一圈,CNT达到ARR,溢出,进中断,设置一个变量++(正转),–(反转)

              4、转速如何计算

              规定某个某个时钟周期内,读取有多少脉冲,从而计算转速

              这里采用的是M法测速,测出的是电机是多少转/s

              脉冲相当于路程,某个时钟周期相当于时间

              这个上方有描述,可以往上翻翻

              5、为什么我的编码器没有输出,获取到的脉冲数是0

              检查一下接线,从硬件开始,一步一步排查,对应的引脚是否正确

              检查电源,编码器的电源是否打开,相应的PWM波是否有效

              硬件确认没有错误,检查软件,编码器接口是否打开,PWM模式是否输出,电机IN引脚是否配置

              七、测速硬件展示及测速现象

              硬件展示

              Stm32-使用TB6612驱动电机及编码器测速,在这里插入图片描述,第6张

              测速展示

              可以看到当转速为1的时候,产生的脉冲是66-68,而我设置的闸门时间是50ms,结合电机转一圈是1320个脉冲,也就是说测量的脉冲数几乎正确。

              (66*2=132) 和编码器的线数完全吻合,测速成功!!!

              Stm32-使用TB6612驱动电机及编码器测速,在这里插入图片描述,第7张

              八、总结

              这个编码器花费了我几乎四天的时间,也可能是自己比较小白,不懂得如何通过电机转一圈产生的脉冲数来计算速度,第一天就实现了读取脉冲

              但是后面几天执着于测速,没有采取正确的方法,导致自己无线内耗,浪费了大量时间。

              由于网上资料繁杂,找不到自己想要的,浪费了很多时间,很多只是一笔带过,没有系统的讲解原理和方法,我也不知道自己这篇文章是否正解

              所以将这个学习总结分享给大家

              当然,这都是在借鉴了前人的肩膀下,谢谢各位大佬和优秀的文章,我会在下方贴出自己觉得值得一看大佬们的链接,大家可以一看

              欢迎大家指错,看到了就会修改,大家一起共同进步

              九、大家可以参考

              参考链接1

              带霍尔传感器编码器的直流减速电机测速原理讲解(附源码)-OpenEdv-开源电子网

              参考链接2

              编码器分类及原理和测速应用(含代码) - 古月居

              参考链接3

              stm32平衡小车(2)-----编码器电机驱动

              参考链接4

              带编码器的直流减速电机——基于STM32F407_编码器直流减速电机磁极数目_谁还不是个程序猿的博客

              谢谢各位大佬的文章,让我受益匪浅,站在前人的肩膀下才能看的更远