使用STM32定时器编码器模式实现直流有刷电机测速(HAL库教程)

前言

最近在做一个单片机大作业,要用到直流有刷,在这里把学习编码器的知识记录一下,学习参考资料:

正点原子DMF407电机控制专题教程_V1.0

编码器测速原理

我所使用的编码器是市面上常见的磁电增量式编码器,其有AB两相,用于输出电机转动时的脉冲数,AB两相的先后顺序决定了电机的转动方向

这其实就是单片机的外部计数器模式,51中也带有同样的功能

信号从通道被采样后的处理过程如下

编码器的计数接口是利用脉冲的边沿来计数的,我们知道AB两相都有脉冲且相位差为90度,那么一次检测最多可以得到四个边沿,此时我们可以通过配置计数的方式来实现不同的边沿计数

由图可以看出,总共有三种计数方式供我们选择,不同的模式对应了不同的计数形式,这里我选用的是第三种,因此会产生四个边沿,相当于对信号进行了四倍频,即一个脉冲信号会计数4次,在计算时要注意

寄存器部分

在编码过程中有几个重要的寄存器要简单介绍一下

TIMx_CR1控制寄存器

 我们要关注这里的DIR位,通过判断DIR来判断计数器所处的模式是递增还是递减,流程大致为:配置好定时器的装载值为65536,不分频,那么每一次接收到65536个脉冲后定时器就会溢出进入定时器中断,此时通过判断DIR是0还是1来判断是正转还是反转,进而将得到的脉冲数累积到总脉冲数中进行速度计算

同时还要注意 CEN位,该位用于使能计数器的工作,也就是相当于选择模式,对应硬件就是内部的开关,用于判断是外部定时器模式还是内部定时器模式,当该位置1为外部定时器模式

TIMx_CCMR1捕获/比较模式寄存器1

在这里IC1F为滤波器,其实现方法为通过配置寄存器来设定滤波系数,例如在一个采样周期下,当N=2时代表要两个相同的事件(比如两个相同电位的高电平信号)才能视为一个有效边沿,这个过程基于数字滤波器实现,寄存器配置如下

IC1PSC是输入捕获预分频器,和上述原理类似,这里我和教程一样,选择一个边沿就触发一次计数

CC1S用于配置定时器通道,这里不做多要求,IC1映射到TI1就可以了

TIMx_SMCR从模式控制器

 SMS代表的三位用于配置编码器模式123,具体对应关系如下

 至此要用到的所有寄存器就介绍完了,接下来我们来进行CUBEMX工程的创建

硬件信息

主控芯片我选择的是STM32F103C8T6,电机用的是常见的自带编码器的直流有刷减速电机,编码器线数为11,电机驱动选用L298N,同时用OLED屏幕显示电机转速信息

工程创建

首先配置时钟树,直接拉满,再初始化几个IO口用于驱动电机

 剩下的流程就是选择对应的IDE生成工程即可

代码部分

初始化外设

/* USER CODE BEGIN 2 */
  run_left_motor(50,1); //左轮电机启动
  OLED_Init();  //OLED初始化
  OLED_Clear();
  HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL); //开启定时器编码器通道
  HAL_TIM_Base_Start_IT(&htim4);  //开启定时中断通道
  /* USER CODE END 2 */

 初始化电机以及编码器

#define ROTO_RATIO 44 //编码器线数11*分频系数4
#define REDUTATION_RATIO 30 //电机减速比30

//编码器结构体
typedef struct
{
    int encode_old;
    int encode_new;
    float speed;
}ENCODE_TypeDef;
//电机结构体
typedef  struct
{
    float speed;
}Motor_TypeDef;

extern ENCODE_TypeDef g_encode_left;
extern Motor_TypeDef g_motor_left;

 电机转动

void run_left_motor(uint8_t speed,unsigned char dir)
{
  uint32_t arr;
  HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);
  if(dir == 1)
  {
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET);
  }
  else
  {
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);
  }
  HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
  arr = __HAL_TIM_GET_AUTORELOAD(&htim2);
  __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,(1.0*speed/100)*arr);
}

电机速度计算

//encoder_now为定时器编码器模式下的计数值,ms代表每隔多少ms进入一次计算公式
void left_speed_compute(int32_t encode_now,uint8_t ms)
{
  uint8_t i=0,j=0;
  float temp = 0.0; //用于后续冒泡排序
  static uint8_t sp_count=0,k=0;  //用于判断隔多长时间进一次计算,静态变量,不会随着函数调用被刷新
  static float speed_array[10]={0.0}; //上面的k为数组的索引值
  if(sp_count == ms)  //当调用函数次数超过50次即每间隔50ms进入一次计数
  {
    g_encode_left.encode_new = encode_now;  //编码器值更新
    g_encode_left.speed = (g_encode_left.encode_new - g_encode_left.encode_old);  //脉冲变化数计算
    //通过脉冲计算速度,公式:(50ms内记录的脉冲数/50ms*60)/减速比/编码器线数/分频系数 = 每分钟的脉冲数/已知常量
    speed_array[k++] = (float)(g_encode_left.speed*((1000/ms)*60.0)/(REDUTATION_RATIO*ROTO_RATIO));
    g_encode_left.encode_old = g_encode_left.encode_new;  //更新编码器计数值
    //冒泡排序法
    if(k==10)
    {
      for(i=10;i>=1;i--)
      {
        for(j=0;j<(i-1);j++) {
          if (speed_array[j] > speed_array[j + 1]) {
            temp = speed_array[j];
            speed_array[j] = speed_array[j + 1];
            speed_array[j + 1] = temp;
          }
        }
      }
      temp = 0.0; //初始化temp
      //去除两边高低数据
      for(i=2;i<8;i++)
      {
        temp += speed_array[i];
      }
      temp = (float)(temp/6);
      /*
       * 一阶低通滤波 Y(n)= qX(n) + (1-q)Y(n-1)
       * 其中 X(n)为本次采样值;Y(n-1)为上次滤波输出值;Y(n)为本次滤波输出值,
       * q 为滤波系数
      */
      g_motor_left.speed = (float)((g_motor_left.speed)*(float)0.52)+((float)0.48*temp);
      //显示当前速度
      unsigned char Tx_Buf[20];
      sprintf((char *)Tx_Buf,"Left: %.2f",g_motor_left.speed);
      OLED_ShowString(1,2,Tx_Buf,5);
      k = 0;
    }
    sp_count = 0;
  }
  sp_count++;
}

溢出计数统计以及调用计算

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  //判断编码器的溢出方向
  if(htim->Instance == TIM1)
  {
    if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&htim1))
    {
      g_tim1_encode_count--;
    }
    else
    {
      g_tim1_encode_count++;
    }
  }
  //每间隔1ms更新一次脉冲数
  if(htim->Instance == TIM4)
  {
    int Left_encode_now = gtim_get_encode();
    left_speed_compute(Left_encode_now,50);
  }
}
//更新脉冲数
int gtim_get_encode(void)
{
  return ( int32_t )__HAL_TIM_GET_COUNTER(&htim1) + g_tim1_encode_count * 65536;       /* 当前计数值+之前累计编码器的值=总的编码器值 */
}

实验现象验证

转动时转速为20r/min 

不转动时转速为0

物联沃分享整理
物联沃-IOTWORD物联网 » 使用STM32定时器编码器模式实现直流有刷电机测速(HAL库教程)

发表评论