本章节主要介绍编码器的原理,获取编码器脉冲值以及如何计算小车速度和路程

前言

1.软件准备:STM32CubeMx、Keil5_ MDK

2.硬件准备:STM32F103C8T6核心板、带编码器电机、TB6612电机驱动模块/L298N电机驱动、18650锂电池3节/3S航模电池、杜邦线若干

编码器简介

如图 3-1 左所示是市场上常用的编码器图片,这样编码器精度较高,但价格较高,而且体积较大,我们寻迹车用到的编码器图片如图 3-1 右所示。我们可以把编码器看做是一个能够测量小车轮子所转圈数的传感器,能够测量出轮子转的圈数便可知道小车行驶的里程。我们通过计算 1秒时间内轮子转的圈数再结合寻迹车轮子的半径, 即可计算出寻迹车的速度。获取到小车行走速度和行驶里程后我们便可通过传感器获取到的行驶里程或者小车速度控制小车的行驶里程和行驶速度。

我们用到寻迹车上的编码器一般有霍尔编码器和光电编码器,两者原理差不多,都是测量小车轮子转一圈编码器输出的脉冲数,光电编码器相比霍尔编码器精度会高些。一般我们常用的编码器都是分为 AB 双相的,双相的比单相的相比分辨率更高,而且可以获取到轮子转的方向。 

编码器原理

如图 3-2 所示为编码器的原理,我们可以看到图中轮子转一圈 A 相输出 8 个跳边沿,单片机通过外部中断每个跳边沿计数器加 1 的话,轮子转一圈会计数到 8。如果用 AB 双相的话每个相都输出 8个跳边沿,单片机通过外部中断每个跳边沿计数器加 1 的话,轮子转一圈会计数到 8。如果用 AB 双相的话每个相都输出 8 个跳边沿,双相就输出 16 个跳边沿,精度也就会相应提高 2 倍。我们同时可以发现当 B 相上升沿的时候 A 相处于高电平状态,此时电机处于正转状态。相反,A 相上升沿的时候 B 相处于高电平状态,此时电机处于反转状态,我们便可由此判别电机的正转和反转状态。

编码器电路

 如图 3-3 所示为带编码器电机的接口图,也就是图 3-1 右所示的编码器电机,读取编码器的数据本质上就是读取编码器输出的脉冲数据, 就是通过单片机定时器和外部中断获取编码器输出的上升沿和下降沿。一般高级些的单片机内部都会有编码器读取的硬件资源,我们所使用的STM32F103C8T6 单片机就有,每个基本定时器和高级定时器都可以配置成编码器模式。如图 3-4所示,STM32F103C8T6 PA0 和 PA1 对应定时器 2 的通道 1 和通道 2,PA6 和 PA7 对应定时器 3 的通道 1 和通道 2,将定时器1 和定时器 2 配置成编码器模式即可读取到寻迹车左右两轮的速度和行驶里程。

编码器测速程序实现

CubeMx配置:

PWM配置

编码器模式配置 

GPIO配置

void MX_GPIO_Init(void)
{
 
  GPIO_InitTypeDef GPIO_InitStruct = {0};
 
  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
 
  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, IN4_Pin|IN3_Pin|IN2_Pin|IN1_Pin, GPIO_PIN_RESET);
 
  /*Configure GPIO pins : PBPin PBPin PBPin PBPin */
  GPIO_InitStruct.Pin = IN4_Pin|IN3_Pin|IN2_Pin|IN1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
 
}


定时器配置

void MX_TIM2_Init(void)
{
 
  /* USER CODE BEGIN TIM2_Init 0 */
 
  /* USER CODE END TIM2_Init 0 */
 
  TIM_Encoder_InitTypeDef sConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
 
  /* USER CODE BEGIN TIM2_Init 1 */
 
  /* USER CODE END TIM2_Init 1 */
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 0;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 65535;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
  sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
  sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC1Filter = 0;
  sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
  sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC2Filter = 0;
  if (HAL_TIM_Encoder_Init(&htim2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM2_Init 2 */
 
  /* USER CODE END TIM2_Init 2 */
 
}
/* TIM4 init function */
void MX_TIM4_Init(void)
{
 
  /* USER CODE BEGIN TIM4_Init 0 */
 
  /* USER CODE END TIM4_Init 0 */
 
  TIM_Encoder_InitTypeDef sConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
 
  /* USER CODE BEGIN TIM4_Init 1 */
 
  /* USER CODE END TIM4_Init 1 */
  htim4.Instance = TIM4;
  htim4.Init.Prescaler = 0;
  htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim4.Init.Period = 65535;
  htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
  sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
  sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC1Filter = 0;
  sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
  sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC2Filter = 0;
  if (HAL_TIM_Encoder_Init(&htim4, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM4_Init 2 */
 
  /* USER CODE END TIM4_Init 2 */
 
}


编码器程序

电机选取加速比为1:30。编码器线数为11。

轮胎转一圈产生30x11个脉冲,单片机编码器计数器计数为30x11x4。

电机速度为电机脉冲值x单片机编码器计数器计数。

/* USER CODE BEGIN PTD */
#define ABS(X) (X>0) ? (X=X) : (X=-X)
 
#define IN4(H) 	if(H)\
	                  HAL_GPIO_WritePin(IN4_GPIO_Port,IN4_Pin,GPIO_PIN_SET);\
                 else HAL_GPIO_WritePin(IN4_GPIO_Port,IN4_Pin, GPIO_PIN_RESET);
#define IN3(H)  if(H)\
	                   HAL_GPIO_WritePin(IN3_GPIO_Port,IN3_Pin,GPIO_PIN_SET);\
                 else HAL_GPIO_WritePin(IN3_GPIO_Port,IN3_Pin, GPIO_PIN_RESET);
#define IN2(H)  if(H)\
	                   HAL_GPIO_WritePin(IN2_GPIO_Port,IN2_Pin,GPIO_PIN_SET);\
                 else HAL_GPIO_WritePin(IN2_GPIO_Port,IN2_Pin, GPIO_PIN_RESET);
#define IN1(H)  if(H)\
	                   HAL_GPIO_WritePin(IN1_GPIO_Port,IN1_Pin,GPIO_PIN_SET);\
                 else HAL_GPIO_WritePin(IN1_GPIO_Port,IN1_Pin, GPIO_PIN_RESET);
/* USER CODE END PTD */
 
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
 
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
 
int32_t Encoder1Count = 0;
int32_t Encoder2Count = 0;
 
int64_t total_EncCount1 = 0;
int64_t total_EncCount2 = 0;
 
float Motor1Speed = 0.0;
float Motor2Speed = 0.0;
 
uint32_t uwtick_8ms_delay = 0;
 
/* USER CODE END PM */
/***********************************************************************************************
 函 数 名:void Get_Encoder(void)
 功    能:
 说    明:获取编码器反馈 8ms采样 
************************************************************************************************/
 
void Get_Encoder(void) //获取编码器反馈
{
	Encoder1Count =  - (short)__HAL_TIM_GetCounter(&htim2);//获取电机脉冲值
	Encoder2Count =  - (short)__HAL_TIM_GetCounter(&htim4);//获取电机脉冲值
	
	total_EncCount1 += Encoder1Count;	//位置累加
  total_EncCount2 += Encoder2Count;
	
	__HAL_TIM_SetCounter(&htim2,0);			//清除编码器值
	__HAL_TIM_SetCounter(&htim4,0);			//清除编码器值
	
	
	Motor1Speed = (float)Encoder1Count*100/30.0/11/4;//计算速度
	Motor2Speed = (float)Encoder2Count*100/30.0/11/4;
 
}
/**************************************************************************
函 数 名:void set_speed(int16_t left_speed,int16_t right_speed)
功    能:速度设定函数
入口参数:左右轮速度
**************************************************************************/
void set_speed(int16_t left_speed,int16_t right_speed)//设定电机转速
{
	if(left_speed>0)   {IN1(1);IN2(0);}
	else 						   {IN1(0);IN2(1);}
	if(right_speed>0)  {IN3(0);IN4(1);}
	else 						   {IN3(1);IN4(0);}
	
	__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_1, ABS(left_speed));
	__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_4, ABS(right_speed));
}


主程序

int main(void)
{
  /* USER CODE BEGIN 1 */
 
  /* USER CODE END 1 */
 
  /* MCU Configuration--------------------------------------------------------*/
 
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
 
  /* USER CODE BEGIN Init */
 
  /* USER CODE END Init */
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* USER CODE BEGIN SysInit */
 
  /* USER CODE END SysInit */
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_TIM4_Init();
  MX_TIM1_Init();
  /* USER CODE BEGIN 2 */
	//开启PWM
	HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
	HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
 
	//开启Encoder
	HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
    HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);
	
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		set_speed(500,500);
		if(uwTick - uwtick_8ms_delay > 8)
		{
			Get_Encoder();
			uwtick_8ms_delay = uwTick;
		}
 
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}


具体实现 

正向转动轮子,编码器脉冲读取正常,速度转换正常。

编码器演示视频