【平衡小车制作】(三)编码器讲解(超详解)

  大家好,我是小政。本篇文章我将针对平衡小车电机上的编码器进行讲解。让每位小伙伴能够对编码器的硬件结构和软件编程有更加清晰的理解。

一、硬件结构

1.什么是编码器?

  编码器是一种将角位移或者角速度转换成一串电数字脉冲的旋转式传感器。编码器又分为光电编码器和霍尔编码器。

2.编码器的工作原理

  这里以霍尔编码器为主介绍(光电编码器精度比霍尔高38倍)。霍尔编码器是有霍尔马盘和霍尔元件组成。霍尔马盘是在一定直径的圆板上等分的布置有不同的磁极。霍尔马盘与电动机同轴,电动机旋转时,霍尔元件检测输出若干脉冲信号,为判断转向,一般输出两组存在一定相位差的方波信号。示意图如下:
【平衡小车制作】(三)编码器讲解(超详解)

图1 霍尔编码器

3.编码器接口引脚
  我用的电机是GM25-370直流减速电机(带霍尔编码器),其对应编码器引脚图如下:
【平衡小车制作】(三)编码器讲解(超详解)

图2 霍尔编码器接口引脚

4.编码器的测速原理
  单位时间内,根据脉冲走过的距离计算电机实际速度,这里采用10ms定时器中断。

5.采集数据方式
  通常有两种方式,第一种软件技术直接采用外部中断进行采集,根据AB相位差的不同可以判断正负。第二种硬件技术直接使用定时器的编码器模式,这里采用第二种。也是大家常说的四倍频,提高测量精度的方法。其实就是把AB相的上升沿和下降沿都采集而已,所以1变4。自己使用外部中断方式实现就比较占用资源了,这里建议使用定时器的编码器模式来测量脉冲变化值。

二、软件编程

1.编码器函数——Encoder.c
1)编码器1和编码器2初始化函数
 入口参数:
  ① GPIO初始化
   ② 定时器基本结构体初始化
   ③ 编码器配置函数
   ④ 定时器输入捕获函数
   ⑤ 清除定时器更新标志位
   ⑥ 运行更新中断
   ⑦ 定时数据清零
   ⑧ 定时器使能

这里具体讲解一下编码器配置函数

// 编码器配置函数: 定时器2,模式3,上升沿
TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);  

① 第一个参数是定时器选择。
② 第二个参数是编码器启动模式;TIM_EncoderMode_TI12:在T1和T2的每个跳变沿均计数。
【模式】:
Tl1模式:在T1的所有边沿计数。
Tl2模式:在T2的所有边沿计数。
Tl1l2模式:在T1和T2的所有边沿计数。
简单举个例子:Tl1处于上升沿时,Tl2处在低电平,即对应Tl1FP1信号向上计数。依次类推,下图我圈起来的就是根据编码器计数操作推导出的计数模式。
【平衡小车制作】(三)编码器讲解(超详解)

图3 T1与T2极性

【平衡小车制作】(三)编码器讲解(超详解)

图4 对应计数模式

③ 第三和第四个参数是极性配置;这里分为上升沿和下降沿,为什么说设置极性变成设置上升沿和下降沿呢?通过进入stm32f10x.tim.c函数中可以查找到,设置极性实际上与TIM_CCER_CC1P这个寄存器是有关联的。我们查找STM32中文参考手册可以了解到。
  这里可以看到,我们设置为不反相,对应IC1上升沿
【平衡小车制作】(三)编码器讲解(超详解)

图5 IC极性查找

编码器函数代码

#include "encoder.h"
#include "sys.h"

void Encoder_TIM2_Init(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	GPIO_InitTypeDef GPIO_InitStruct;
	TIM_ICInitTypeDef TIM_ICInitStruct;
  
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 ,ENABLE);  // 开启定时器时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO,ENABLE); // 开启GPIO时钟
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;    // 浮空输入
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;   // 编码器1:PA0/PA1
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA ,&GPIO_InitStruct);	
  
	TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;        // 不分频
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;    // 向上计数
	TIM_TimeBaseInitStruct.TIM_Period = 65535;                      // 重装载值65535
	TIM_TimeBaseInitStruct.TIM_Prescaler =0;                        // 分频系数0
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
  
	// 编码器配置函数: 定时器2,模式3,上升沿
	TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);  
  
	TIM_ICStructInit(&TIM_ICInitStruct);
	TIM_ICInitStruct.TIM_ICFilter = 10;       // 滤波器设置为10
	TIM_ICInit(TIM2,&TIM_ICInitStruct);
  
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);      // 清除定时器标志位
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);  // 定时器2,溢出更新,使能
	TIM_SetCounter(TIM2,0);                   // 定时数据清零
	TIM_Cmd(TIM2,ENABLE);                     // 定时器2使能
}

void Encoder_TIM4_Init(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	GPIO_InitTypeDef GPIO_InitStruct;
	TIM_ICInitTypeDef TIM_ICInitStruct;
  
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4 ,ENABLE);  // 开启定时器时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE); // 开启GPIO时钟
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;    // 浮空输入
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;   // 编码器2:PB6/PB7
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB ,&GPIO_InitStruct);	
  
	TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;      // 不分频
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;  // 向上计数
	TIM_TimeBaseInitStruct.TIM_Period = 65535;                    // 重装载值65535
	TIM_TimeBaseInitStruct.TIM_Prescaler = 0;                     // 分频系数0
	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStruct);
  
	// 编码器配置函数:定时器4,模式3,上升沿
	TIM_EncoderInterfaceConfig(TIM4,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);  
  
	TIM_ICStructInit(&TIM_ICInitStruct);
	TIM_ICInitStruct.TIM_ICFilter = 10;       // 滤波器设置为10
	TIM_ICInit(TIM4,&TIM_ICInitStruct);
  
	TIM_ClearFlag(TIM4,TIM_FLAG_Update);      // 清除定时器溢出更新标志位
	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);  // 定时器4,溢出更新,使能
	TIM_SetCounter(TIM4,0);                   // 定时数据清零
	TIM_Cmd(TIM4,ENABLE);                     // 定时器2使能
}

2)编码器数据读取函数
 入口参数:定时器x
  ① 选择定时器x
   ② 采集编码器的计数值并保存
   ③ 将定时器的计数值清零
   ④ 返回定时器计数值
注:因为TIM_GetCounter得到的是短整型(short int),所以这里我们要进行一个强制转换。

int Read_Speed(int TIMx)
{
  int value_1;
  switch(TIMx)
  {
    case 2:
      value_1 = (short)TIM_GetCounter(TIM2);  // 采集编码器的计数值并保存
      TIM_SetCounter(TIM2,0);   // 将定时器的计数值清零
      break;
    case 4:
      value_1 = (short)TIM_GetCounter(TIM4);  // 采集编码器的计数值并保存
      TIM_SetCounter(TIM4,0);   // 将定时器的计数值清零
      break; 
    default: value_1 = 0;
  }
  return value_1;
}

3)定时器中断服务函数

// 定时器2中断服务函数
void TIM2_IRQHandler()
{
  if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)  // 中断标志位置1
  {
    TIM_ClearITPendingBit(TIM2,TIM_IT_Update);  // 清楚中断标志位
  }
}

// 定时器4中断服务函数
void TIM4_IRQHandler()
{
  if(TIM_GetITStatus(TIM4,TIM_IT_Update)==SET)  // 中断标志位置1
  {
    TIM_ClearITPendingBit(TIM4,TIM_IT_Update);  // 清楚中断标志位
  }
}

2.编码器函数头文件——Encoder.h

#ifdef  _ENCODER_H
#define _ENCODER_H

void Encoder_TIM2_Init(void);   // 编码器1初始化函数
void Encoder_TIM4_Init(void);   // 编码器2初始化函数
int Read_Speed(int TIMx);       // 编码器速度读取函数
void TIM2_IRQHandler(void);     // 定时器2中断服务函数
void TIM4_IRQHandler(void);     // 定时器4中断服务函数
#endif

  以上就是平衡小车系列文章第三讲——编码器,包括硬件结构讲解和STM32软件编程的讲解,文章中出现错误或者小伙伴对以上内容有所疑问,欢迎大家在评论区留言,小政看到后会尽快回复大家!

上一篇:FreeModbus从站设计(7)-如何让RTU的定时器正常工作起来


下一篇:四个定时器(TIM2 TIM3 TIM4 TIM5)编码器输入模式