STM32定时器简单采集编码器脉冲
MCU:STM32H723ZGT6
编码器:(欧姆龙)E6B2-CWZ1X;1000P/R;8根线信号线分别为 A+ A- B+ B- Z+ Z- 以及5V和GND;
- A 脉冲输出
- B 脉冲输出
- Z 零点信号 当编码器旋转到零点时,Z信号会发出一个脉冲表示现在是零位置 表示编码器转了1圈,可用来记录编码器转了多少圈,从而知道运行距离
采用定时器1的CH1(PE9)接A+和CH2(PE11)接B+;一般是定时器的通道1和2才能作为编码器输入口,对应编码器输出的两相。
串口1TX(PA9)、RX(PA10)打印输出信息;
编码器介绍
正交编码器(又名双通道增量式编码器),用于将线性移位转换为脉冲信号。通过监控脉冲的数目和两个信号的相对相位,用户可以跟踪旋转位置、旋转方向和速度。另外,第三个通道称为索引信号,可用于对位置计数器进行复位,从而确定绝对位置。
原理:增量型编码器通过内部两个光敏接受管将编码器的转向转化为A相和B相脉冲的时序和相位关系。编码器每转还输出一个Z相脉冲以代表零位参考位。
正转时:A相在上升沿时,B相是低电平;B相上升沿时,A相是高电平;A相在下降沿时,B相是高电平;B相下降沿时,A相是低电平;
反转时:B相在上升沿时,A相是低电平;A相上升沿时,B相是高电平;B相在下降沿时,A相是高电平;A相下降沿时,B相是低电平;可见正转和反转两者对应正好相反!
使用定时器编码器接口模式就是将A相和B相的所有跳变沿作为计数器的计数时钟,出现边沿信号时,计数自增(正转)或自减(反转)!
STMCubeMX配置
时钟树
debug
串口usart1
定时器TIM1
MCU代码
tim.c代码:
相关GPIO配置为配置为上拉输入模式
/* USER CODE BEGIN Header */
/********************************************************************************* @file tim.c* @brief This file provides code for the configuration* of the TIM instances.******************************************************************************* @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 "tim.h"/* USER CODE BEGIN 0 *//* USER CODE END 0 */TIM_HandleTypeDef htim1;/* TIM1 init function */
void MX_TIM1_Init(void)
{/* USER CODE BEGIN TIM1_Init 0 *//* USER CODE END TIM1_Init 0 */TIM_Encoder_InitTypeDef sConfig = {0};TIM_MasterConfigTypeDef sMasterConfig = {0};/* USER CODE BEGIN TIM1_Init 1 *//* USER CODE END TIM1_Init 1 */htim1.Instance = TIM1;htim1.Init.Prescaler = 0;htim1.Init.CounterMode = TIM_COUNTERMODE_UP;htim1.Init.Period = 65535;htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim1.Init.RepetitionCounter = 0;htim1.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 = 0xF;sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;sConfig.IC2Prescaler = TIM_ICPSC_DIV1;sConfig.IC2Filter = 0xF;if (HAL_TIM_Encoder_Init(&htim1, &sConfig) != HAL_OK){Error_Handler();}sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK){Error_Handler();}/* USER CODE BEGIN TIM1_Init 2 *//* USER CODE END TIM1_Init 2 */}void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if(tim_encoderHandle->Instance==TIM1){/* USER CODE BEGIN TIM1_MspInit 0 *//* USER CODE END TIM1_MspInit 0 *//* TIM1 clock enable */__HAL_RCC_TIM1_CLK_ENABLE();__HAL_RCC_GPIOE_CLK_ENABLE();/**TIM1 GPIO ConfigurationPE9 ------> TIM1_CH1PE11 ------> TIM1_CH2*/GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_11;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);/* USER CODE BEGIN TIM1_MspInit 1 *//* USER CODE END TIM1_MspInit 1 */}
}void HAL_TIM_Encoder_MspDeInit(TIM_HandleTypeDef* tim_encoderHandle)
{if(tim_encoderHandle->Instance==TIM1){/* USER CODE BEGIN TIM1_MspDeInit 0 *//* USER CODE END TIM1_MspDeInit 0 *//* Peripheral clock disable */__HAL_RCC_TIM1_CLK_DISABLE();/**TIM1 GPIO ConfigurationPE9 ------> TIM1_CH1PE11 ------> TIM1_CH2*/HAL_GPIO_DeInit(GPIOE, GPIO_PIN_9|GPIO_PIN_11);/* USER CODE BEGIN TIM1_MspDeInit 1 *//* USER CODE END TIM1_MspDeInit 1 */}
}/* USER CODE BEGIN 1 */
uint16_t Encoder_Get_CNT(void)
{return __HAL_TIM_GET_COUNTER(&htim1);
}/* USER CODE END 1 */
新添加的代码
__HAL_TIM_GET_COUNTER函数获取当前的CNT计数值!
/*** @brief Get the TIM Counter Register value on runtime.* @param __HANDLE__ TIM handle.* @retval 16-bit or 32-bit value of the timer counter register (TIMx_CNT)*/
#define __HAL_TIM_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNT)
usart.c代码
/* USER CODE BEGIN Header */
/********************************************************************************* @file usart.c* @brief This file provides code for the configuration* of the USART instances.******************************************************************************* @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 "usart.h"/* USER CODE BEGIN 0 */
#include "stdio.h"
/* USER CODE END 0 */UART_HandleTypeDef huart1;/* USART1 init function */void MX_USART1_UART_Init(void)
{/* USER CODE BEGIN USART1_Init 0 *//* USER CODE END USART1_Init 0 *//* USER CODE BEGIN USART1_Init 1 *//* USER CODE END USART1_Init 1 */huart1.Instance = USART1;huart1.Init.BaudRate = 115200;huart1.Init.WordLength = UART_WORDLENGTH_8B;huart1.Init.StopBits = UART_STOPBITS_1;huart1.Init.Parity = UART_PARITY_NONE;huart1.Init.Mode = UART_MODE_TX_RX;huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart1.Init.OverSampling = UART_OVERSAMPLING_16;huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;if (HAL_UART_Init(&huart1) != HAL_OK){Error_Handler();}if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK){Error_Handler();}if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK){Error_Handler();}if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK){Error_Handler();}/* USER CODE BEGIN USART1_Init 2 *//* USER CODE END USART1_Init 2 */}void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{GPIO_InitTypeDef GPIO_InitStruct = {0};RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};if(uartHandle->Instance==USART1){/* USER CODE BEGIN USART1_MspInit 0 *//* USER CODE END USART1_MspInit 0 *//** Initializes the peripherals clock*/PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USART1;PeriphClkInitStruct.Usart16ClockSelection = RCC_USART16910CLKSOURCE_D2PCLK2;if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK){Error_Handler();}/* USART1 clock enable */__HAL_RCC_USART1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/**USART1 GPIO ConfigurationPA9 ------> USART1_TXPA10 ------> USART1_RX*/GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;GPIO_InitStruct.Alternate = GPIO_AF7_USART1;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/* USER CODE BEGIN USART1_MspInit 1 *//* USER CODE END USART1_MspInit 1 */}
}void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{if(uartHandle->Instance==USART1){/* USER CODE BEGIN USART1_MspDeInit 0 *//* USER CODE END USART1_MspDeInit 0 *//* Peripheral clock disable */__HAL_RCC_USART1_CLK_DISABLE();/**USART1 GPIO ConfigurationPA9 ------> USART1_TXPA10 ------> USART1_RX*/HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);/* USER CODE BEGIN USART1_MspDeInit 1 *//* USER CODE END USART1_MspDeInit 1 */}
}/* USER CODE BEGIN 1 */
int fputc(int ch, FILE *f)
{HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);return ch;
}/* USER CODE END 1 */
添加重定向函数,添加头文件#include "stdio.h"
勾选微库
main.c代码
int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MPU Configuration--------------------------------------------------------*/MPU_Config();/* 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_TIM1_Init();MX_USART1_UART_Init();/* USER CODE BEGIN 2 */printf("Init-ok!\r\n");/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */uint16_t speed = 0;/* 使能编码器接口 */HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */speed = Encoder_Get_CNT();printf("cnt = %d\r\n",speed);HAL_Delay(500);}/* USER CODE END 3 */
}
串口打印
顺时针旋转计数值增加,逆时针旋转计数值减少!转一圈计数4000!
采用4倍频方式,即分别在A、B相波形的上升沿和下降沿计数,分辨率可以提高4倍!
想只测速,在不分方向的情况下,只需要用单片机随意选择一个信号线就行了,然后定时器边沿触发,检测脉冲计数,然后清零!
定时器编码器模式介绍
表中的TI1FP1信号可类比上文提到的A相,TI2FP2信号可类比B相;上升对应A/B相上升沿、下降对应A/B相的下降沿;
仅在TI1处计数,指在A相的跳变沿进行计数,不考虑B相的跳变沿;仅在TI2处计数,指在B相的跳变沿进行计数,不考虑A相的跳变沿;
在TI1和TI2处均计数,类似上文提到的正转反转的情况!
正转对应递增情况:TI1FP1信号(A相)上升(上升沿)低(B相低电平)、TI1FP1信号(A相)下降(下降沿)高(B相高电平);TI2FP2信号(B相)上升(上升沿)高(A相高电平)、TI2FP2信号(B相)下降(下降沿)低(A相低电平);
类似可分析反转情况!
分析:TI1上升(上升沿),TI2低,递增;TI2上升,TI1高,递增;TI1下降,TI2高,递增;TI2下降,TI1低,递增;可以看到抖动对计数没干扰!类似可分析反向递减的情况!
对应CH1或CH2的极性选择,这里CH1或者CH2某一个选择下降沿就会变成反相,如果两个又都选为FallingEdge,则又变回正常模式(反相的反相)!