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

相关推荐
小智学长 | 嵌入式39 分钟前
Arduino入门教程:4-1、代码基础-进阶
嵌入式硬件·物联网·arduino
国科安芯2 小时前
【AS32系列MCU调试教程】调试工具:Eclipse调试工具栏与窗口的深入分析
单片机·嵌入式硬件·eclipse
nuannuan2311a2 小时前
9N65-ASEMI照明系统应用专用9N65
单片机·嵌入式硬件
woshihonghonga2 小时前
高级定时器TIM1、TIM8
stm32·单片机·嵌入式硬件
腾飞的信仰2 小时前
举例说明单片机,主循环和中断资源访问冲突的案例
单片机·嵌入式硬件·mongodb
腾飞的信仰13 小时前
单片机,主循环和中断资源访问冲突的案例
单片机·嵌入式硬件
花落已飘13 小时前
STM32 Bootloader:使用文件头加载并启动应用程序
stm32·bootloader
猿来不是梦14 小时前
RT_Thread内核源码分析(五)——内存管理@小堆内存管理算法
stm32·单片机·算法·系统架构·rt_thread操作系统
JXNL@15 小时前
STM32外设学习之USB
stm32·嵌入式硬件·学习
aerror20 小时前
使用mpu6500/6050, PID,互补滤波实现一个简单的飞行自稳控制系统
单片机·飞控