增量式旋转编码器在STM32平台上的应用

发布时间:2023年12月23日

背景

旋钮是仪器仪表上一种常见的输入设备,它的内部是一个旋转编码器,知乎上的这篇科普文章对其工作原理做了深入浅出的介绍。
旋钮在仪器上的样子

我们公司的功率分析仪前面板也用到了该类设备,最近前面板的MCU从MSP430切换成了STM32,因此我要将编码器的驱动移植到STM32。

查看MSP430的代码,看懂其基本思路,是将旋钮的2路输出信号接到2个GPIO管脚,并让这2个管脚作为中断源,驱动在中断里检测2路输出信号的电平组合,并做一些复杂的处理,后台循环根据中断的处理结果获悉旋钮的旋转方向,以及旋转过了几个刻度。

觉得MSP430的处理逻辑太复杂了,而且经过确认,app并不需要知道旋转过了几个刻度,决定用简单的方法来实现旋钮功能。

移植思路

了解所用旋钮的信号时序

我们用的旋钮是日本帝国通信的XRE系列,其原理图:
旋钮的原理图
其时序图:
旋钮的时序图,顺时针
可以看到,因为增量式旋转编码器的2个信号的波形相差1/4周期,因此信号A在跳变时,信号B一定处于稳定的高电平或稳定的低电平。

找到信号波形与旋转方向之间的对应关系

根据时序图可以看出,当A相处于上升沿时,B相总是电平,结合时序图左下角的CLOCKWISE ROTATION注释,我们可以得出结论,只要在A相的上升沿中断里检测到B相为低电平,则说明旋钮在时针旋转。

那怎么检测逆时针旋转呢?旋钮手册里并没有COUNTER-CLOCKWISE ROTATION的内容,其实我们只要从右往左看,就是逆时针的时序图:
旋钮的时序图,逆时针
可以看到,在时针旋转场景里,当A相处于上升沿时,B相总是电平,因此我们只需要在A相的上升沿检测一次B相的电平高低,就能判断出旋转方向!

代码编写

STM32的EXTI中断

与MSP430的GPIO自带中断功能不同,STM32的GPIO是不具备中断能力的,要想当中断管脚用,需要搭配EXTI模块。

网上关于EXTI的样例代码较少,更多的是用STM32CubeMX生成的整套工程代码,我现有的工程就是前任员工基于STM32CubeMX生成的,很难将两个工程自动合并。

想到可以将EXTI工程里的有效代码摘出来,插入现有工程里,尝试了一下,可行。

GPIO和EXTI初始化代码

void encoder_init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  // GPIO init
  GPIO_InitStruct.Pin = encoder_a2_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;   //A相负责触发中断,确定观测时间点
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  GPIO_InitStruct.Pin = encoder_b2_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;       //软件读取B相的电平高低,从而确定方向
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

 /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);       //encoder_a2_Pin是连接到GPIO_PIN_0的,因此对应EXTI的0号中断
  HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}

中断回调代码

void EXTI0_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI0_IRQn 0 */

  /* USER CODE END EXTI0_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(encoder_a2_Pin);
  /* USER CODE BEGIN EXTI0_IRQn 1 */

  /* USER CODE END EXTI0_IRQn 1 */
}

void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u) // 不用担心多个GPIO共用一个EXTI中断,EXTI会做区分
  {
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Callback(GPIO_Pin);
  }
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	uint8_t pin_value;

	switch (GPIO_Pin)
	{
		case encoder_a2_Pin:
		{
			pin_value = HAL_GPIO_ReadPin(encoder_b2_GPIO_Port, encoder_b2_Pin);
			if (!pin_value) {
				encoder_msg = ENCODERA_LEFT; // 逆时针旋转
			} else {
				encoder_msg = ENCODERA_RIGH; // 顺时针旋转
			}
			break;
		}
		default:
			break;
	}
}

后台循环代码

uint8_t encoder_msg = INVALID_MSG;

void encoder_process(void)
{
	uint8_t out_data[6];

	if (encoder_msg != INVALID_MSG)
	{
		data_process(ENCODER_KEY_TYPE, 0, (uint8_t *)&encoder_msg, out_data); //将旋钮消息按约定格式封装成out_data
		spi_enque_tx_data(out_data);  // 将out_data通过SPI总线上报主控板
		encoder_msg = INVALID_MSG;  // clear msg
	}
}

总结

我的代码相比老工程,在app感知不到功能差异的前提下,逻辑大大简化,中断占用量还减少了一半,值得大家参考。

文章来源:https://blog.csdn.net/happen23/article/details/135030060
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。