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

背景

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

我们公司的功率分析仪前面板也用到了该类设备,最近前面板的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初始化代码

c 复制代码
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);
}

中断回调代码

c 复制代码
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;
	}
}

后台循环代码

c 复制代码
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感知不到功能差异的前提下,逻辑大大简化,中断占用量还减少了一半,值得大家参考。

相关推荐
重生之我是数学王子3 小时前
单片机 STM32入门
stm32·单片机·嵌入式硬件
qq_459730036 小时前
4-3 MCU中ARM存储器的作用
arm开发·单片机·嵌入式硬件
嵌入式科普9 小时前
嵌入式科普(24)从SPI和CAN通信重新理解“全双工”
c语言·stm32·can·spi·全双工·ra6m5
重生之我是数学王子10 小时前
点亮核心板小灯 STM32U575
stm32·单片机·嵌入式硬件
end_SJ10 小时前
初学stm32 --- 定时器中断
stm32·单片机·嵌入式硬件
南城花随雪。10 小时前
单片机:实现数码管动态显示(0~99999999)74hc138驱动(附带源码)
单片机·嵌入式硬件
南城花随雪。12 小时前
单片机:实现信号发生器(附带源码)
单片机·嵌入式硬件
灵槐梦14 小时前
【速成51单片机】2.点亮LED
c语言·开发语言·经验分享·笔记·单片机·51单片机
三月七(爱看动漫的程序员)14 小时前
HiQA: A Hierarchical Contextual Augmentation RAG for Multi-Documents QA---附录
人工智能·单片机·嵌入式硬件·物联网·机器学习·语言模型·自然语言处理
新晨单片机设计15 小时前
【087】基于51单片机智能宠物喂食器【Proteus仿真+Keil程序+报告+原理图】
嵌入式硬件·51单片机·proteus·宠物·ad原理图