打印
[应用相关]

STM32驱动旋转编码器(标准库)

[复制链接]
33|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2025-2-11 09:28 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
一、机械式增量式旋转编码器工作原理
KY-040是一种机械式增量编码器,通过CLK和DT两个相位差90°的方波信号判断旋转方向:

顺时针旋转:CLK引脚先变化,DT随后变化

逆时针旋转:DT引脚先变化,CLK随后变化

它有5个引脚:CLK、DT、SW、VCC、GND



编程思想:通过中断I/O不断轮询的方式:取一个周期2个引脚的波形图,TI1对应CLK,TI2对应DT,把两个脚配置为上升沿触发,当CLK引脚上升沿到来触发中断,此时进入中断服务函数判断DT引脚电平是否为低电平,若为则编码器正转,反之,几乎同时,当DT引脚上升沿到来触发中断,进入中断服务函数判断CLK引脚是否为低电平,若为则编码器反转。(下面提供的代码会有详细的注释,没看懂可以翻到下面源码部分)我这里通过数据的加减来模拟旋转编码器的正反转,并输出到串口上。

源码下载链接:https://share.weiyun.com/zlszrbGj

下面图片参考作者@百里与司空https://blog.csdn.net/hjlkklk/ar ... ult-2-142960567.142^v101^pc_search_result_base5&spm=1018.2226.3001.4187



驱动源码encoder.c:

#include "encoder.h"
#include "stm32f10x.h"
#include "Delay.h"

/*通过I/O口的中断不断检测来驱动旋转编码器*/

/*********************************

   @brief:    旋转编码器的初始化
   @param:    void
   @retval:   void

*********************************/

int16_t Encoder_Count; //定义一个变量的值用来模拟旋转编码器的正反转

void Encoder_Init(void)
{

    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(Encoder_GPIO_CLK, ENABLE);                    //开启GPIOB的时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);                //开启AFIO的时钟,外部中断必须开启AFIO的时钟


    //CLK和DT和SW三个引脚对应GPIOA的引脚的配置
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;    //上拉输入,因为旋转编码器空闲时为高电平
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(Encoder_GPIO_PORT, &GPIO_InitStructure);

    /*AFIO选择中断引脚*/
    GPIO_EXTILineConfig(Encoder_GPIO_PORT_Source, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择CLK的PA0为外部中断引脚
    GPIO_EXTILineConfig(Encoder_GPIO_PORT_Source, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择DT的PA1为外部中断引脚

    /*EXTI的初始化*/

    EXTI_InitTypeDef EXTI_InitStructure;
    EXTI_InitStructure.EXTI_Line=EXTI_Line0|EXTI_Line1;  //选择配置外部中断的0号线和1号线
    EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;    //指定外部中断线为中断模式
    EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; //指定外部中断线为上升沿触发
    EXTI_InitStructure.EXTI_LineCmd=ENABLE;              //指定外部中断线使能
    EXTI_Init(&EXTI_InitStructure);

    /*NVIC中断分组*/
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);          //0~4都可以

    /*NVIC配置*/
    //EXTI0线的配置
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel=EXTI0_IRQn;              //选择配置NVIC的EXTI0线
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;     //指定NVIC线路的抢占优先级为1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;            //指定NVIC线路的响应优先级为1
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;               //NVIC使能
    NVIC_Init(&NVIC_InitStructure);                             //将配置好的结构体变量的置赋给NVIC_Init,配置NVIC外设


    //EXTI1线的配置
    NVIC_InitStructure.NVIC_IRQChannel=EXTI1_IRQn;              //选择配置NVIC的EXTI1线
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;     //指定NVIC线路的抢占优先级为1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;            //指定NVIC线路的响应优先级为2
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;               //NVIC使能
    NVIC_Init(&NVIC_InitStructure);                             //将配置好的结构体变量的置赋给NVIC_Init,配置NVIC外设

}


/*编写EXTI0的中断服务函数*/
void EXTI0_IRQHandler(void)
{
  if(EXTI_GetITStatus(EXTI_Line0) == SET)   //先判断中断是否发生,即使不需要这步,但也要保持逻辑的严密性
  {
      if(GPIO_ReadInputDataBit(Encoder_GPIO_PORT, Encoder_GPIO_PIN_CLK) == 1)  //校验CLK引脚上升沿对应引脚的电平
      {
         if(GPIO_ReadInputDataBit(Encoder_GPIO_PORT, Encoder_GPIO_PIN_DT) == 0)  //在判断DT的引脚是否为低电平
         {

           Encoder_Count++;  //自加来模拟正转
           EXTI_ClearITPendingBit(EXTI_Line0); //注意:自加完后一定清楚中断标志位
         }

      }
  }
}


/*编写EXTI1的中断服务函数*/  //与上面的逻辑是一样的
void EXTI1_IRQHandler(void)
{

   if(EXTI_GetITStatus(EXTI_Line1) == SET)   //先判断中断是否发生,即使不需要这步,但也要保持逻辑的严密性
  {
      if(GPIO_ReadInputDataBit(Encoder_GPIO_PORT, Encoder_GPIO_PIN_DT) == 1)  //校验DT引脚上升沿对应引脚的电平
      {
         if(GPIO_ReadInputDataBit(Encoder_GPIO_PORT, Encoder_GPIO_PIN_CLK) == 0)  //在判断CLK的引脚是否为低电平
         {

           Encoder_Count--;  //自减来模拟反转
           EXTI_ClearITPendingBit(EXTI_Line1);  //注意:自加完后一定清楚中断标志位
         }

      }
  }   

}


/*获取Encoder_Count的值,和按键清0*/

int16_t Encoder_Get_Count(void)
{

   int16_t temp;
   temp=Encoder_Count;
   if(GPIO_ReadInputDataBit(Encoder_GPIO_PORT, Encoder_GPIO_PIN_CLK) == 1)   //检测SW引脚按下,则对Encoder_Count进行清0
   {
        Delay_us(10);  //延时按键消抖
        Encoder_Count=0;
   }
   return temp;

}


encoder.h头文件:

#ifndef __ENCODER_H
#define __ENCODER_H

#include "stm32f10x.h"

//宏定义部分,方便用户修改引脚
#define Encoder_GPIO_CLK              RCC_APB2Periph_GPIOA
#define Encoder_GPIO_PORT             GPIOA
#define Encoder_GPIO_PIN_CLK          GPIO_Pin_0
#define Encoder_GPIO_PIN_DT           GPIO_Pin_1
#define Encoder_GPIO_PIN_SW           GPIO_Pin_2
#define Encoder_GPIO_PORT_Source      GPIO_PortSourceGPIOA

void Encoder_Init(void);
int16_t Encoder_Get_Count(void);


#endif
主函数main:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "usart.h"
#include "encoder.h"
#include <stdio.h>

int16_t Value;

int main(void)
{

    USART1_Config();
    Encoder_Init();

        while(1)
        {
      printf("\r\ntest\n");
      Value=Encoder_Get_Count();   //获取Encoder_Count的加减来模拟旋转编码器正反转
      printf("\r\nValue=%d\n",Value);   //打印在串口上
        }

}
最后看效果:

​​

————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/id2658/article/details/145460918

使用特权

评论回复
沙发
gejigeji521| | 2025-2-11 10:08 | 只看该作者
使用2个IO的中断函数。如果定义一个全局变量,还可以累积转动的位置。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2108

主题

16232

帖子

16

粉丝