首页 > 其他分享 >STM32之两种驱动 “旋转编码器“ 方式

STM32之两种驱动 “旋转编码器“ 方式

时间:2025-01-10 12:30:40浏览次数:3  
标签:编码器 HAL 旋转 STM32 TIM InitStructure GPIO EXTI

一、 简介

             本章讲解旋转编码器驱动方式,一种为普通GPIO边沿检测方式,一种为定时器编码器方式,各有优缺点,当资源不足时可以选择第一种但是精度不如定时器编码器方式,资源充足时建议时间定时器方式较优且可靠。

二、旋转编码器原理

        2.1 介绍:

                 相信很多都知道旋转编码器这个东西,但是也不免新人见过但是没听过此名词故而不了解,因此请看下图!!! 这就是常见的旋转编码器,和电位器很像,但是原理和应用不一样,它一般用于音响系统、数控机床、机器人、自动化设备、伺服控制系统等。

        2.2  模块电路原理:

                    可见外围电路还是相当简单,SW1引出的TRIM_A/B是编码器的AB相脉冲,PUSH是按下的按键电平翻转,这三个通道空闲都是上拉。

        2.3 正反转检测原理:

                   编码器给出两相方波,可见下图的波形A/B相位差90°,我们就叫A/B通道,其中的一个通道给出与转速有关的信息,同时与另外一个通道对比,就能知道旋转的方向。

                可见下图编码器盘上有两个通道(A 和 B),每个通道在不同位置有通断的状态,可见图中间的S1标识,如果 S1向右转动那么A 通道的信号先于 B 通道发生变化,则表示顺时针旋转,如果S1向左转动 B 通道的信号先于 A 通道发生变化,则表示逆时针旋转。

三、驱动方式一

        3.1 简介:

                   此驱动方式以普通GPIO边沿检测方式,优点是不占用定时器资源,普通的GPIO配置成外部中断即可使用,缺点是响应慢、精度低、抗噪声能力差。

        3.2 原理:

                 当脉冲来临时,触发外部中断进入服务函数,通过检测旋转编码器A通道和B通道的电平变化来确定编码器的旋转方向,并相应地增加或减少计数。当中断触发时,它检查当前通道的状态与之前保存的状态是否不同,以判断是否有旋转发生。然后,通过比较A通道和B通道的相对状态(即是否相同),来决定是正转还是反转:如果两个通道状态不同则认为是正转,增加计数;如果相同则认为是反转,减少计数。最后,更新上次的状态并清除中断标志位,以便处理下一次中断。

        3.3 代码:

/**
  ************************************************************************** 
  ** -------------------------------------------------------------------- **
  ** @name          : Xz_scan
  ** @brief         : 用于初始化外部中断以及中断向量控制器
  ** @param         : None
  ** @retval        : None
  ** @author data   : 轩哥	2023-03-18
  ** @version       : v1.0
  ** @attention     : None
  ** -------------------------------------------------------------------- **
  ************************************************************************** 
**/
void Xz_scan(void)
{
    EXTI_InitTypeDef EXTI_InitStructure;													//外部中断结构体声明
    NVIC_InitTypeDef NVIC_InitStructure;													//中断向量控制器声明
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);						//打开复用时钟

		GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource6|GPIO_PinSource7);//配置GPIO对应线
    EXTI_InitStructure.EXTI_Line = XZ_A_PIN | XZ_B_PIN;						//引脚线
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;						//中断模式
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//上下边沿触发
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;											//使能
    EXTI_Init(&EXTI_InitStructure);																//初始化


    NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;					  //中断源
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;	//抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;					//子优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;								//使能
    NVIC_Init(&NVIC_InitStructure);																//初始化NVIC


}


/**
  ************************************************************************** 
  ** -------------------------------------------------------------------- **
  ** @name          : EXTI9_5_IRQHandler
  ** @brief         : 外部中断服务函数
  ** @param         : None
  ** @retval        : None
  ** @author data   : 轩哥	2023-03-18
  ** @version       : v1.0
  ** @attention     : 它是下降沿触发,结合正反转时序图观察可见复合判断且不掉步
											
											---顺时针--    ---逆时针--
											|  A |  B |    |  A |  B |
											|  1 |  1 |    |  1 |  1 |
											|  0|   1 |    |  1 |  0 |
											|  0 |  0 |    |  0 |  0 |
											|  1 |  0 |    |  0 |  1 |
											-----------    -----------
  ** -------------------------------------------------------------------- **
  ************************************************************************** 
**/

void EXTI9_5_IRQHandler(void) {

    static uint8_t lastEncoderState = 0;  							// 关于A通道的上一状态
    static uint8_t lastEncoderState2 = 0; 						  // 关于B通道的上一状态

    if (EXTI_GetITStatus(EXTI_Line6) != RESET) 					//当边沿来了
    {
        uint8_t currEncoderState = GPIO_ReadInputDataBit(XZ_A_PORT,XZ_A_PIN); //将PA6电平保存
			
			if (currEncoderState != lastEncoderState)				//PA6当前电平状态与上一个电平状态比较 不相等代表被旋转
        {
            if (GPIO_ReadInputDataBit(XZ_B_PORT,XZ_B_PIN) != currEncoderState) //读取B通道电平和当前A通道状态
            {
                encoderCount++;													//不同电平认为在正转
            }
            else {
                encoderCount--;													//相同认为在反转
					
            }

            lastEncoderState = currEncoderState;				 //将当前A通道电平保存为上一个电平
        }

        EXTI_ClearITPendingBit(EXTI_Line6);							//清除标作位
    }

    if (EXTI_GetITStatus(EXTI_Line7) != RESET)         //当边沿来了
    {
        uint8_t currEncoderState = GPIO_ReadInputDataBit(XZ_B_PORT,XZ_B_PIN); //读取B通道电平
		
        if (currEncoderState != lastEncoderState2) 		//与上一次保存的电平不一致认为在转动
        {
            if (GPIO_ReadInputDataBit(XZ_A_PORT,XZ_A_PIN) != currEncoderState) //读取A通道的电平和B通道刚刚的电平比较
            {
                encoderCount--;												//不相同认为在反转
            }
            else {
                encoderCount++;												//相同认为在正转
            }
            lastEncoderState2 = currEncoderState;		 //保存上一次电平状态
        }

        EXTI_ClearITPendingBit(EXTI_Line7);					//清除标志位
    }

}

四、驱动方式二

        4.1 简介:

                   此驱动方式以定时器编码器检测方式,优点是能够更快速、更准确地响应编码器的脉冲,减少了因处理器忙碌或其他中断造成的延迟,抗噪声能力更强。缺点是占用片上外设资源。

        4.2 原理:

                    通过监测连接到定时器输入捕获通道的旋转编码器A相和B相的信号变化,利用这两个信号之间的相位差来确定旋转方向,并根据每个通道的状态变化以四倍频的方式更新计数,从而精确地跟踪编码器的位置和旋转速度。定时器能够响应每个通道的上升沿和下降沿,提供高分辨率的位置测量,并且可以通过读取定时器的计数值来获取旋转信息。

        4.3 代码:

uint8_t Encoder_sensi=1;//分频因子
  TIM_HandleTypeDef  TIM_Handle1;  //输入结构体	
/************************************************************************** 
** -------------------------------------------------------------------- **
** @name          : Encoder_Init
** @brief         : 这是一个用于初始化旋转编码器GPIO的函数
** @param         : None
** @retval        : None
** @author data   : 轩哥	2023-07-21
** @attention     : None
** -------------------------------------------------------------------- **
**************************************************************************/
void Encoder_Init(void)
{
__HAL_RCC_TIM1_CLK_ENABLE();				  //定时器1时钟打开
__HAL_RCC_GPIOA_CLK_ENABLE();                 //打开GPIOA时钟打开
__HAL_RCC_GPIOE_CLK_ENABLE();                 //打开GPIOE时钟打开
__HAL_RCC_GPIOC_CLK_ENABLE();                 //打开GPIOC时钟打开
		     	                                                                      
	GPIO_InitTypeDef GPIO_InitStructure;                                  //GPIO结构体声明
	GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;                         //设置内部上拉
	GPIO_InitStructure.Pin = XZ_A_PIN ;                
	GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;                     //设置翻转速度
		GPIO_InitStructure.Alternate=GPIO_AF1_TIM1;
	HAL_GPIO_Init(XZ_A_PORT, &GPIO_InitStructure);                                //初始化
		
	GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;                         //设置内部上拉
	GPIO_InitStructure.Pin = XZ_B_PIN ;                
	GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;                     //设置翻转速度
	GPIO_InitStructure.Alternate=GPIO_AF1_TIM1;
	HAL_GPIO_Init(XZ_B_PORT, &GPIO_InitStructure);                                //初始化
	

	TIM_Encoder_InitTypeDef  	TIM_EncoderInitStructure;			//定时器输入结构体              //定时器结构体声明
  TIM_MasterConfigTypeDef   TIM_MasterStructure;

	TIM_Handle1.Instance=TIM1;
  TIM_Handle1.Init.Prescaler = 0;
  TIM_Handle1.Init.CounterMode = TIM_COUNTERMODE_UP;
  TIM_Handle1.Init.Period = 65535;
  TIM_Handle1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  TIM_Handle1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
	
  TIM_EncoderInitStructure.EncoderMode = TIM_ENCODERMODE_TI2;	// 四倍频模式
  TIM_EncoderInitStructure.IC1Polarity = TIM_ICPOLARITY_RISING;
  TIM_EncoderInitStructure.IC1Selection = TIM_ICSELECTION_DIRECTTI;
  TIM_EncoderInitStructure.IC1Prescaler = TIM_ICPSC_DIV1;
  TIM_EncoderInitStructure.IC1Filter = 0xF;
	  if (HAL_TIM_Encoder_Init(&TIM_Handle1, &TIM_EncoderInitStructure) != HAL_OK)
  {
      	printf("TIM_EncoderInitStructure1初始化失败\r\n");
  }
	
  TIM_EncoderInitStructure.IC2Polarity = TIM_ICPOLARITY_RISING;
  TIM_EncoderInitStructure.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  TIM_EncoderInitStructure.IC2Prescaler = TIM_ICPSC_DIV1;
  TIM_EncoderInitStructure.IC2Filter = 0xF;
  if (HAL_TIM_Encoder_Init(&TIM_Handle1, &TIM_EncoderInitStructure) != HAL_OK)
  {
    			printf("TIM_EncoderInitStructure2初始化失败\r\n");
  }
  TIM_MasterStructure.MasterOutputTrigger = TIM_TRGO_RESET;
  TIM_MasterStructure.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&TIM_Handle1, &TIM_MasterStructure) != HAL_OK)
  {
			printf("TIM_MasterStructure初始化失败\r\n");
  }
	    HAL_TIM_Encoder_Start(&TIM_Handle1, TIM_CHANNEL_ALL);
}

/************************************************************************** 
** -------------------------------------------------------------------- **
** @name          : Encoder_Get
** @brief         : 这是一个获取编码器计数值的函数 (增加滤值)
** @param         : None
** @retval        : None
** @author data   : 轩哥	2023-07-23
** @attention     : None
** -------------------------------------------------------------------- **
**************************************************************************/
int8_t d0;// 定义一个全局变量 d0,用于存储上一次的编码器计数值(经过 Encoder_sensi 调整后的值)
int16_t Encoder_Get_Div4(void)
{
	if(__HAL_TIM_GET_COUNTER(&TIM_Handle1)/Encoder_sensi - d0)// 检查当前计数值与上一次计数值之间的差值
	{
		int16_t temp = (__HAL_TIM_GET_COUNTER(&TIM_Handle1)/Encoder_sensi - d0);// 计算当前计数值与上一次计数值之间的差值
		
		d0 = __HAL_TIM_GET_COUNTER(&TIM_Handle1)/Encoder_sensi;// 更新 d0 为当前计数值(经过 Encoder_sensi 调整后的值)
		return temp*-1;  //*-1方向调转
	}
	return 0;// 如果没有变化,则返回 0
	
}

五、实验示例

        5.1 示例驱动方式一:

        5.2 示例驱动方式二:

六、总结

           通过以上学习我们完成了传感器驱动方法,可见方式一的精度、响应等都不如方式二,这是因为方式二这种模式下,硬件会自动处理编码器的A相和B相信号,并根据相位关系来确定旋转方向,同时以更高的分辨率(通常是四倍于编码器的基本分辨率)进行计数,因此有更快的响应与更加可靠。

标签:编码器,HAL,旋转,STM32,TIM,InitStructure,GPIO,EXTI
From: https://blog.csdn.net/qq_39411256/article/details/145010154

相关文章

  • STM32MP157AAA开发板通过GPIO实现模拟I2C驱动获取光照,红外,接近传感器的数据
    实验目标:硬件:STM32MP157AAA开发板+拓展板管脚:拓展板光照,红外,接近传感器(AP3216C)I2C1_SCL对应核心板PF14,I2C1_SDA对应核心板PF15,可知从机地址为0X1E。梳理:I2C各信号下SCL与SDA的机制可查看STM32MP157AAA开发板通过GPIO实现模拟I2C驱动获取温湿度传感器数据-CSDN博客光照,红......
  • STM32MP157AAA开发板通过GPIO模拟SPI驱动通信控制数码管
    实验目标:硬件:STM32MP157AAA开发板+拓展板管脚:拓展板上数码管(M74HC595),由下图可知,数码管通过SPI与核心板通信,时钟(S)CK对应PE12、主机输出从机输入(MOSI)对应PE14、主机输入从机输出(MISO)对应PE13、从设备选择(CSN)对应PE11,由于当前电路SPI只有一个从设备,因此在硬件层面上将PE11常置为......
  • NRF24L01模块STM32-调试心得:报错 1E
    前言环境:芯片:STM32F103C8T6Keil:V5.24.2.0调试时我们会尝试读取STATUS寄存器状态来了解模块目前的状态,但是我们在读取时至为0x1E,这就很纳闷,根据寄存器描述0x1E:对应寄存器4:1,关系有:达到最大重发次数        RXFIFO为空,第四位很好理解也......
  • 【芳心科技】E. 基于STM32的便携式电脉冲理疗仪
    实物效果图:实现功能:采用STM32单片机作为控制核心,其采用先进的ARM内核。采用XL6007升压电路产生高压,采用高压三极管控制多种电脉冲产生,频率1HZ-400HZ之间,脉冲电压小于100V,安全可靠。采用LCD12864显示屏作为人机交互,能够直观显示理疗仪当前工作状态和相关参数。可以通过按键......
  • STM32标准库学习笔记(六)ADC
    前言学习永无止境!本篇是嵌入式开发之片上外设ADC,从硬件原理到实际应用,深入了解ADC模数转换原理以及相关应用。注:本文章为学习笔记,部分图片与文字来源于网络/江协科技课程/手册,如侵权请联系!谢谢!一、ADC概述1.1ADC简介 定义:ADC(Analog-DigitalConverter)模拟数字转换器,......
  • STM32 I2C总线
    一、什么是I2C总线1、SCL、和SDA线的作用I2C总线是主机与从机之间通过SCL、SDA连接,达到一个主机对应多个从机的作用。其中SCL叫做串行时钟线,负责传输时钟信号,SDA叫做串行数据线,负责传输数据。2、为什么SCL和SDA都是开漏输出,并且要外接一个上拉电阻什么开漏输出?只有低电......
  • STM32CUBEMX时钟树配置
    以下以stm32f103c8t6为例打开STM32CUBEMX,点击CLockConfiguration这就是我们的时钟数配置了;默认情况下是呈以下配置:一、了解一下为什么叫做时钟树?下图拥有两棵树,一棵树是树干比较小的叫做低速树,树干比较大的叫做高速树。而时钟的产生离不开晶振,所以时钟树的根一般都为晶......
  • STM32 拓展 RTC案例1:使用闹钟唤醒待机模式 (HAL库)
    需求描述执行完毕正常代码之后,让MCU进入待机模式,设置闹钟,自动让MCU从待机模式中被唤醒。可以用led点亮熄灭显示是否唤醒。应用场景:比如设计一个野外温度自动采集的设备,规定每小时采集一次温度,就可以定义一个1小时的闹钟,定时唤醒,采集温度,采集完进入待机模式,可以大大降低设备功......
  • 基于STM32的温室自主灌溉系统(论文+源码)
    1.总体方案设计本课题为基于STM32的温室自主灌溉系统,通过查阅多种文献和的设计了如图2.1所示的温室自主灌溉系统总体架构,整个系统在器件上包括了主控制器STM32F103,温度传感器DS18B20,土壤湿度传感器YL-69,光敏电阻,显示器LCD1602,继电器,LED灯,蜂鸣器,水泵,按键等等。在功能上,实现了......
  • Transformer、编码器、解码器、全连接FFN、自注意力机制、嵌入向量、残差连接层归一化
    一.提出背景Transformer最早是Google在2017年的AttentionIsAllYouNeed论文中提出,用于解决解决传统的序列到序列(Seq2Seq)模型在处理可变长序列时遇到的问题。(序列到序列:指的是模型的输入是一段序列,模型输出也是序列;比如语音识别中给模型一段中文语音序列,让模型给出中文文字序列......