AB phase incremental encoder and STM32 timer encoder mode speed/distance measurement (HAL library)

introduce

An encoder is a device used to measure rotational motion or position. Encoders are often used together with a timer module to obtain and count the number of pulses of rotation in a microcontroller to determine the direction and distance of an object's movement. Encoders are useful in many applications, such as robot motion control, motor position feedback, and position sensing.
STM32 microcontrollers provide support for multiple types of encoders, one of the common encoder types is the incremental encoder. Incremental encoders are based on two pulse signal channels, commonly referred to as A-phase and B-phase. When rotation occurs, the phase relationship of these two signal channels changes, allowing the direction of rotation and the number of steps in the rotation to be calculated. STM32 provides hardware support that allows you to use timer modules to capture and count encoder signals. Here are some important concepts related to STM32 encoders:
计数器值(Counter Value): This is the counter in the timer module, which records the number of pulses of the encoder signal. The counter increases and decreases depending on changes in the encoder signal.
计数方向(Counter Direction): The counter can be incremented or decremented, depending on changes in the encoder signal. Counting directions can be used to determine the direction of rotation of an object.
捕获: The timer can be configured to capture the status of the encoder signal to record the counter value when the pulse signal changes. Capture allows you to measure the time interval between pulses for use in velocity and position calculations.
编码器模式(Encoder Mode): This is a setting for the timer module that specifies that it will be used as an encoder. Encoder mode configures the timer's input channel so that it can capture the encoder signal.
编码器计数器(Encoder Counter): Some STM32 models provide a special hardware encoder counter that can directly process the encoder's signal and count.

AB phase encoder

An AB phase encoder, also known as a quadrature encoder or incremental encoder, is a device commonly used to measure rotational position and direction. It is based on two output signal channels, often called phase A and phase B, which are 90 degrees out of phase and are used to determine the direction of rotation and count.

! ! Many people on the Internet cannot tell the difference between these encoders. Here is a summary! !

Incremental encoder:

Incremental encoders usually have two output channels, called Phase A and Phase B, and an index signal channel (sometimes they may not have one).
When rotational motion occurs, the number of pulses of the A-phase and B-phase signals changes. These two signals are 90 degrees out of phase, and the direction of rotation and count value can be determined by monitoring their changes.
The output of an incremental encoder is an incremental pulse, which needs to be counted and integrated to obtain position information. Speed ​​can be measured using two channels of signals.

Quadrature encoder:

The quadrature encoder also has two output channels, A-phase and B-phase. The signal pulses of these two channels are 90 degrees out of phase, just like the incremental encoder.
Unlike the incremental encoder, the output of the quadrature encoder is already integrated position information, so the rotational position can be obtained directly.
Quadrature encoders can also provide a Z-phase or index signal that marks a complete rotation cycle.

Principle introduction:
Insert image description here

Insert image description here
The direction of rotation can be determined by judging whether the other phase is high or low during the rising/falling edge of one phase. The
Insert image description here
last one is commonly used, and the content here is the judgment corresponding to the AB phase. For example, in the figure, FP1 rises and FP2 reaches high level. If 1 is phase A and 2 is phase B, that is, B is at high level when A rises, which corresponds to inversion, so it counts downward.
Insert image description here
The encoder interface determines whether it is forward or reverse, and controls CNT++ or -. ARR is set to 65535, so that when CNT=0, it will become 65535 as soon as it is decremented. It can also be converted to signed type and directly halved to become plus or minus 65535/2.

STM32 configuration

Main configuration:
Insert image description here
several important parameters:
预分配: You can set whether the encoder output pulse is frequency divided
滤波: filtering to eliminate burrs, the larger the effect, the better, depending on the needs.
The following is a sample code

#include "tim.h"

// 声明外部的 TIM_HandleTypeDef 结构体变量,该结构体变量在其他文件中定义
extern TIM_HandleTypeDef htim2;

// 初始化 TIM2 编码器
void MX_TIM2_Init(void)
{
    
    
  // 定义 TIM_Encoder_InitTypeDef 结构体变量,并初始化为 0
  TIM_Encoder_InitTypeDef sConfig = {
    
    0};
  // 定义 TIM_MasterConfigTypeDef 结构体变量,并初始化为 0
  TIM_MasterConfigTypeDef sMasterConfig = {
    
    0};

  // 设置 htim2 结构体的相关参数
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 2;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 65536;
  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 = 15;
  sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
  sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC2Filter = 15;

  // 初始化编码器
  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();
  }
}

// 初始化 TIM2 编码器的外设
void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle)
{
    
    
  GPIO_InitTypeDef GPIO_InitStruct = {
    
    0};
  if(tim_encoderHandle->Instance==TIM2)
  {
    
    
    // 使能 TIM2 外设时钟
    __HAL_RCC_TIM2_CLK_ENABLE();

    // 使能 GPIOA 外设时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    // 配置 GPIOA 引脚 0 和 1 为输入模式
    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    // 配置 TIM2 中断优先级并使能中断
    HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
  }
}

// 反初始化 TIM2 编码器的外设
void HAL_TIM_Encoder_MspDeInit(TIM_HandleTypeDef* tim_encoderHandle)
{
    
    
  if(tim_encoderHandle->Instance==TIM2)
  {
    
    
    // 关闭 TIM2 外设时钟
    __HAL_RCC_TIM2_CLK_DISABLE();
    // 复位 GPIOA 引脚的配置
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0|GPIO_PIN_1);
    // 关闭 TIM2 中断
    HAL_NVIC_DisableIRQ(TIM2_IRQn);
  }
}

Main function:

// 清除 TIM2 更新中断标志位
HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);
// 启动 TIM2 编码器模式,捕获编码器信号
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
// 使能 TIM2 更新中断
__HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);

Commonly used functions:

HAL_TIM_Encoder_GetState():
该函数用于获取编码器的状态,例如运行状态、停止状态等。
HAL_TIM_Encoder_GetValue():
获取当前编码器的计数值。这对于获得实时位置信息很有用。
HAL_TIM_Encoder_Stop():
停止编码器模式。这可以用于暂停编码器信号的捕获和处理。
HAL_TIM_Encoder_Start_DMA():
在编码器模式下,使用DMA(直接内存访问)来捕获编码器信号,从而减少CPU的负担。
HAL_TIM_IC_CaptureCallback():
如果您使用输入捕获模式来处理编码器信号,该回调函数将在捕获事件发生时被调用。

Measure distance

Method 1: one interrupt at a time

Set CNT to 1. If it is full, an overflow will occur. Write the following code in the interrupt and then
add or subtract after judging the direction.

extern long long xtt;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    
    
	if(htim == &htim2)
	{
    
    
		//判断正反转;
		if((uint32_t)(__HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2)))
		{
    
    
			xtt++;
		}
		else
		{
    
    
			xtt--;
		}
	}
}

Disadvantages of writing like this:
Speed ​​and accuracy: If the encoder rotates very quickly, this method of directly judging forward and reverse rotation in the timer interrupt may lead to misjudgment. In addition, due to the way timer interrupts are used, the accuracy of counter updates may be affected by the time of interrupt processing.
Interrupt frequency and calculation: You judge and update the counter value in each timer interrupt, which may cause frequent interrupts to occur, thus affecting system performance.

Method 2: The main function quickly polls to obtain the value and then clears it.

Set CNT to 65536 and force it to short type after each acquisition to have positive and negative values. In addition, the main function polls quickly, accumulates the acquired values ​​and clears them.
polling:

#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

void SystemClock_Config(void);
extern TIM_HandleTypeDef htim2;
	
int main(void)
{
    
    
  
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_USART1_UART_Init();
	long long longs = 0;
	__HAL_TIM_CLEAR_IT (&htim2 ,TIM_IT_UPDATE );
	HAL_TIM_Base_Start_IT (&htim2);
	if (HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL) != HAL_OK)
  {
    
    
    Error_Handler(); // 错误处理:编码器启动失败
  }
  while (1)
  {
    
    
		longs += Get_Count();  //累加当前的值 可能为正也可能为负
		__HAL_TIM_SET_COUNTER(&htim2,0); //读取完成清空
		printf("当前值:%lld\n\r",longs);
  }
}

Get_Count function:

short Get_Count(void)
{
    
    
    return (short)(__HAL_TIM_GET_COUNTER(&htim2));
}

Advantages:
Simple and direct: This method directly reads the value of the encoder counter, then accumulates and clears it. The code is relatively simple and easy to understand and implement.
Real-time: Thanks to fast polling in the main loop, you are able to get the value of the encoder counter in almost real-time.
Not relying on interrupts: You do not use timer interrupts to process encoder signals, which can reduce the overhead caused by interrupt processing and is especially suitable for some scenarios that require high real-time performance.
Disadvantages:
Real-time issues: Although you can obtain the counter value in almost real-time, due to polling in the main loop, if other operations in the main loop take a long time, obtaining the counter value may be affected. Especially when dealing with other tasks or peripherals, the counter value may be lost.
Accuracy issues: The way you poll in the main loop may be affected by time changes in the loop iterations, affecting the encoder count. If the main loop iteration time is unstable, the counter values ​​may become inaccurate.
To sum up, this is suitable for some application scenarios with high real-time requirements, simple and direct requirements.

In the method three interrupt, obtain the value in the CNT register and determine the direction.

It is to obtain the value in the CNT register in the interrupt and determine the direction, that is, to distinguish the overflow and underflow and record the overflow plus one and the overflow minus one. When obtaining the value, use the previous value multiplied by the reloaded value plus the current counter. value.
This method seems perfect, so let’s implement it.
The test found that the interruption was very stable and there were no sudden changes.

long long xtt; 
long long xxt = -1;  //这里设置为-1消除第一次产生的中断
void TIM2_IRQHandler(void)
{
    
    
	if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) == SET) //发生重置
	{
    
    
			uint32_t currentCounter = __HAL_TIM_GET_COUNTER(&htim2); //获取装载的上一次值
			//下溢出
			if(currentCounter == 0)
			{
    
    
				xxt++; 
			}
			//上溢出
			if(currentCounter == 65535)		
			{
    
    
				xxt--;
			}					
	}
	__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);
}

long long Get_Value(void)
{
    
    
	return xxt*65535 + (__HAL_TIM_GET_COUNTER(&htim2));
}

Insert image description here

Method 4: Use a timer to obtain the data regularly

Similar to method 2 but with an additional timer to periodically obtain and clear the counter. The disadvantage of writing this way is that the scheduled interrupt time set by the timer is difficult to control, and it needs to be directly considered between performance and speed.

Measure speed

It is OK to add a timer to __HAL_TIM_GET_COUNTER(&htim2) obtain the speed = distance/time, which will not be demonstrated here.

at last

1. Summary

Adjust the filter to suit yourself: Since the encoder signal may be interfered by noise, you can configure the timer's filter to smooth the input signal to avoid misjudgments. The index signal is not used here because the distance measuring car may move back and forth: Some encoders support an index signal (Z-phase signal), which is used to mark a complete cycle of rotation. The index signal can be used to calibrate and reset position. DMA follow-up projects can add: In some cases, you may want to use DMA to process the capture of encoder signals to reduce the burden on the CPU and improve efficiency.

2. Precautions for use of Omron E6B2-CWZ6C

The encoder I use is the Omron E6B2-CWZ6C encoder. Note that the ABZ phase needs to add a pull-up resistor. I only need to test 1K here.

3. The difference between __HAL_TIM_CLEAR_IT and __HAL_TIM_CLEAR_FLAG

Both are used to clear timer interrupts or flag bits, but their usage scenarios and functions are slightly different.

  1. __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);
    This macro is used to clear the interrupt flag bit of the timer. In HAL_TIM_PeriodElapsedCallbacka function, when the timer generates a periodic interrupt (such as a timer overflow interrupt), in order to avoid repeated calls, you usually use this macro at the beginning of the interrupt callback function to clear the interrupt flag bit. This way, on the next cycle, if an interrupt occurs again, you can ensure that the interrupt callback function is called only once.
  2. __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
    This macro is used to directly clear specific flag bits of the timer, including interrupt flag bits and other flag bits. You can use this macro to manually clear the timer flag without relying on the interrupt callback function. This is useful if you need to clear a flag bit in another piece of code.
    In summary, __HAL_TIM_CLEAR_ITit is mainly used to clear the interrupt flag bit in the interrupt callback function to avoid repeated calls. And __HAL_TIM_CLEAR_FLAGyou can manually clear the timer flag wherever needed, not limited to the interrupt callback function.

4.The difference between HAL_TIM_PeriodElapsedCallback and TIMx_IRQHandler

Both are related to timer-related interrupt processing, but there are some differences in their use.

  1. HAL_TIM_PeriodElapsedCallback:
    HAL_TIM_PeriodElapsedCallback is a callback function provided by the HAL library, which is called when the timer's periodic interrupt occurs. It is implemented through the encapsulation of the HAL library and is suitable for STM32 HAL library users. You can define this function in your application code and handle the actions when the timer interrupt occurs. This function is called on every periodic interrupt of the timer.
  2. TIMx_IRQHandler:
    TIMx_IRQHandler It is the underlying interrupt processing function of the timer. For each timer, CMSIS (Cortex Microcontroller Software Interface Standard) provides corresponding interrupt processing functions, such as TIM1_IRQHandler, TIM2_IRQHandleretc. These functions are located in device-specific startup files or libraries and are responsible for handling low-level hardware interrupts. You can perform some low-level interrupt processing in these functions, such as clearing interrupt flag bits, etc. This method is closer to the hardware and usually requires mapping of interrupt vectors in the startup file.

To sum up, HAL_TIM_PeriodElapsedCallbackit is an abstract high-level package, suitable for the STM32 HAL library, and it TIMx_IRQHandleris the underlying hardware interrupt processing function, suitable for lower-level interrupt operations. Choosing which function to use depends on your development needs and the libraries you use.

Guess you like

Origin blog.csdn.net/Systemmax20/article/details/132261043