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

发布时间:2026/7/18 10:30:11
STM32之两种驱动 “旋转编码器“ 方式 一、 简介本章讲解旋转编码器驱动方式一种为普通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_sensi1;//分频因子 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.AlternateGPIO_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.AlternateGPIO_AF1_TIM1; HAL_GPIO_Init(XZ_B_PORT, GPIO_InitStructure); //初始化 TIM_Encoder_InitTypeDef TIM_EncoderInitStructure; //定时器输入结构体 //定时器结构体声明 TIM_MasterConfigTypeDef TIM_MasterStructure; TIM_Handle1.InstanceTIM1; 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相信号并根据相位关系来确定旋转方向同时以更高的分辨率通常是四倍于编码器的基本分辨率进行计数因此有更快的响应与更加可靠。