【自学嵌入式:stm32单片机】编码器接口测速

编码器接口测速

接线图

image
image
PA6和PA7是TIM3的CH1和CH2
我们计划用TIM3接编码器

代码实现

标准库实现

已开源到:https://gitee.com/qin-ruiqian/jiangkeda-stm32

Encoder.h

#ifndef __ENCODER_H
#define __ENCODER_H

void Encoder_Init(void);
int16_t Encoder_Get(void);

#endif

Encoder.c

#include "stm32f10x.h"                  // Device header

//初始化编码器
void Encoder_Init(void)
{
	//IC初始化是每个通道共用一个初始化函数
	//初始化PA0
	//打开APB2总线的GPIOA外设并开启时钟
	//打开TIM3的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	//开启TIM3的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
	//上拉和下拉的选择,可以看接在这个引脚的外部模块输出的默认电平
	//外部模块空闲默认输出高电平,我们就选择上拉输入
	//外部模块空闲默认输出低电平,选择下拉输入,默认输入低电平
	//一般高电平是一个默认状态,如果不确定外部模块输出的默认状态,就尽量选择浮空输入
	
	//普通推挽/开漏输出,引脚控制权是来自于输出数据寄存器的
	//如果想用定时器来控制引脚,就需要使用复用开漏/推挽输出的模式
	//在这里输出数据寄存器将被断开,输出控制权将转移给片上外设
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //TIM3的CH1和CH2
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	//不需开启定时器时钟配置,因为编码器接口会托管时钟
	//RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	//选择时基单元的时钟,选择内部时钟
	TIM_InternalClockConfig(TIM3); //TIM3的时钟单元由内部时钟来驱动
	// 初始化时基单元用的结构体
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //选择滤波器1分频
	//TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式没有用,计数方向被编码器托管
	//定时频率=72MHz/(PSC+1)/(ARR+1)
	//定时1s,也就是定时频率为1Hz
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //ARR,16位计数器满量程计数,这样计数范围最大,方便换算为负数
	//如果想要更高分辨率,ARR调成1000-1,频率72M/预分频/1000
	//占空比就是CCR/1000
	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //预分频器不分频,编码器的时钟直接驱动计数器
	//72M/预分频,就是计数器自增的频率,就是计数标准频率,根据信号频率的分布范围来调整
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级计数器才有的,置0就行,TIM2-TIM4是通用计数器
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
	
	//初始化输入捕获单元
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICStructInit(&TIM_ICInitStructure); //先初始化结构体,防止结构体不完整
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //TIM3的通道1
	TIM_ICInitStructure.TIM_ICFilter = 0xF; //滤波器滤波强度
	//TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //后面TIM_EncoderInterfaceConfig还会再配置一遍
	//上升沿触发,编码器接口始终都是上升沿、下降沿都有效,这里的上升沿参数代表的是高低电平极性不反转
	//编码器接口的输入捕获单元并没有全部使用到,编码器接口只使用了通道1和通道2的滤波器和极性选择
	//所以只配置这两个参数即可
	//TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //每次触发都有效,不分频
	//TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //只测频率,直连输入
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; //TIM3的通道2
	TIM_ICInitStructure.TIM_ICFilter = 0xF; //滤波器滤波强度 
	//TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //后面TIM_EncoderInterfaceConfig还会再配置一遍
	//上升沿触发,编码器接口始终都是上升沿、下降沿都有效,这里的上升沿参数代表的是高低电平极性不反转
	//编码器接口的输入捕获单元并没有全部使用到,编码器接口只使用了通道1和通道2的滤波器和极性选择
	//所以只配置这两个参数即可
	//TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //每次触发都有效,不分频
	//TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //只测频率,直连输入
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
	//配置编码器接口,要在ICInit函数下面,否则配置会被ICInit覆盖
	//模式T1和T2都启用,然后都先按上升沿(通道不反向)
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
	//开启定时3
	TIM_Cmd(TIM3, ENABLE);
}

//获取CNT的值
//读完后清零CNT
int16_t Encoder_Get(void)
{
	int16_t Temp = 0;
	Temp = TIM_GetCounter(TIM3);
	TIM_SetCounter(TIM3, 0);
	return Temp;
}

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "MYOLED.h"
#include "Encoder.h"
#include "Timer.h"

int16_t Speed; //计时用的

int main(void)
{
	MYOLED_Init();
	Timer_Init();
	Encoder_Init();
	MYOLED_ShowString(0, 0, "Speed:");
	while(1)
	{
		MYOLED_ShowSignedNum(6, 0, Speed, 5);
		//Delay_ms(1000); //人手转的慢,闸门时间给1s,不要给太长时间阻塞延迟,用定时器
		//CNT的值代表速度,单位是脉冲个数/s
	}
}

//用TIM2定时器中断每隔一秒读取一下速度,存到Speed变量中
//这里定时器初始化的设定是1s中断一次
void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		Speed = Encoder_Get();
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

HAL库实现

已开源到:https://gitee.com/qin-ruiqian/jiangkeda-stm32-hal
Tim3编码器模式的配置:
image

Encoder.h

/*
 * Encoder.h
 *
 *  Created on: Aug 14, 2025
 *      Author: Administrator
 */

#ifndef HARDWARE_ENCODER_H_
#define HARDWARE_ENCODER_H_

typedef struct Encoder
{
	TIM_HandleTypeDef htim; //当前用的是哪个定时器
}Encoder;

void Encoder_Init(Encoder* encoder, TIM_HandleTypeDef htim);
int16_t Encoder_Get(Encoder* encoder);

#endif /* HARDWARE_ENCODER_H_ */

Encoder.c

/*
 * Encoder.c
 *
 *  Created on: Aug 14, 2025
 *      Author: Administrator
 */


#include "stm32f1xx_hal.h"
#include "Encoder.h"

//初始化Encoder结构体
void Encoder_Init(Encoder* encoder, TIM_HandleTypeDef htim)
{
	encoder->htim = htim;
}

//获取CNT的值
//读完后清零CNT
int16_t Encoder_Get(Encoder* encoder)
{
	int16_t Temp = 0;
	Temp = __HAL_TIM_GET_COUNTER(&(encoder->htim));
	__HAL_TIM_SET_COUNTER(&(encoder->htim), 0);
	return Temp;
}

main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "Encoder.h"
#include "MYOLED.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
int16_t Speed; //计时用的
Encoder encoder; //写成全局变量,方便中断用
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
//回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	//如果是定时器TIM2回调
	if (htim->Instance == TIM2)
	{
		Speed = Encoder_Get(&encoder);
	}
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
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_TIM3_Init();
  /* USER CODE BEGIN 2 */
  Encoder_Init(&encoder, htim3);
  HAL_TIM_Base_Start_IT(&htim2); //启动定时器TIM2开关
  HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL); //定时器3的打开编码器
  MYOLED_Init();
  MYOLED_ShowString(0, 0, "Speed:");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  MYOLED_ShowSignedNum(6, 0, Speed, 5);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

posted @ 2025-08-14 19:55  秦瑞迁  阅读(32)  评论(0)    收藏  举报