STM32中断实现旋转编码器计数

系列文章目录

STM32单片机系列专栏

C语言理论和实践总结专栏


文章目录

1. 旋转编码器

2. 中断代码编写

2.1 Interrupt.c

2.2 Interrupt.h

2.3 完整工程文件


1. 旋转编码器

旋转编码器主要用于测量轴的旋转位置、速度或者是角度的变化,它能够将转动的角度或者位置转换为电信号输出。通常被用于定位和速度反馈系统,旋转编码器有多种类型,包括增量式和绝对式两大类:

增量式旋转编码器(Incremental Encoder):

  • 这种编码器在旋转时产生一系列脉冲,通常有两个输出信号,称为A相和B相,这些信号相互之间有90度的相位差(称为正交输出)。通过计算这些脉冲,你可以确定轴的旋转速度和方向。方向由A相和B相的相位关系确定,速度由脉冲的频率确定。

绝对式旋转编码器(Absolute Encoder):

  • 绝对式编码器为每个角度位置提供一个唯一的代码,即使在断电后,设备也能记住轴的具体位置。当电源再次接通时,编码器可以立即提供当前的位置信息,而无需任何移动或寻找参考点。

连接到STM32单片机

  • 当连接到STM32单片机时,增量式编码器的A和B输出可以连接到微控制器的两个输入引脚,通常会配置这些引脚为外部中断输入,或者是使用具备输入捕获功能的定时器。编码器的旋转会导致A和B输出产生脉冲,STM32通过捕获这些脉冲来计算位置和速度。
  • 速度测量:通过计算一定时间内的脉冲数量,可以测量旋转的速度。
  • 方向判定:通过比较A和B两个通道脉冲的相对时序,可以判断旋转的方向。
  • 位置确定:对于增量式编码器,通常从一个已知的参考点开始计数脉冲,来确定当前位置。对于绝对式编码器,直接读取编码器的输出即可。

硬件电路

增量式编码器通常有两个输出,称为A和B,它们通常提供相位相差90度的两个方波信号。在这个电路中:

  • R1R2 是上拉电阻,确保在开关没有闭合时,A和B输出为高电平(VCC)。
  • R3 R4 是输出限流电阻,为了防止模块引脚电流过大。
  • C1C2 是去抖动电容,用于滤除由于机械开关反跳造成的电气噪声。
  • 当编码器旋转时,A和B的输出会在两个开关的作用下生成脉冲信号。

2. 中断代码编写

中断是微控制器程序设计中的一种机制,它允许微控制器在执行常规程序时,响应异步事件(如外部设备的信号变化)。当中断发生时,微控制器会停止当前的操作,保存当前状态,然后跳转到一个特定的中断服务例程(ISR)去处理这个事件。事件处理完成后,它会返回到之前中断的位置,继续执行原来的程序。

对于中断的概念,如果不理解可以看这篇文章:

STM32中断系统详解

对于中断程序的编写,需要两个文件,Interrupt.c 和 Interrupt.h.

2.1 Interrupt.c

首先是初始化:我们需要从GPIO到NVIC这一路的外设模块都配置好,流程如下:

  • 配置RCC,将涉及的外设的时钟都打开
  • 配置GPIO,选择用到的端口作为输入模式
  • 配置AFIO,选择使用到的这一路GPIO,连接到后面的EXTI
  • 配置EXTI,选择边沿触发方式,上升沿或者下降沿或者双边沿。选择触发响应方式,中断响应或者事件响应。
  • 配置NVIC,给这个中断选择一个合适的优先级。

初始化中断源的硬件

首先需要初始化引脚或硬件模块,让它们能够产生中断信号。对于旋转编码器,通常使用两个引脚来接收编码器的两个输出通道A和B。

// 初始化GPIOB的引脚为输入上拉模式,用来接收编码器的信号
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; // 选择0和1号引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

配置中断线和中断通道

对于STM32微控制器,需要使用AFIO(Alternate Function I/O)和EXTI(External Interrupt)配置特定的GPIO引脚作为外部中断的源。

// 选择PB0和PB1作为外部中断的源
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);

配置外部中断控制器(EXTI)

接下来,配置外部中断控制器EXTI以监听特定的中断线,并设置其触发条件(上升沿、下降沿或双边沿触发)。

// 配置EXTI线0和1,设置为中断模式并且是下降沿触发
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);

配置中断向量控制器(NVIC)

中断服务程序的执行优先级是通过NVIC设置的。这里要设置抢占优先级和子优先级,并使能特定的中断。

实现中断服务程序(ISR)

编写ISR函数以响应中断。ISR的名称在STM32的启动文件中已经预定义,因此必须与启动文件中声明的名称相匹配。

// 处理来自编码器通道A的中断请求
void EXTI0_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line0) == SET) {
        // ... 处理中断 ...
        EXTI_ClearITPendingBit(EXTI_Line0); // 清除中断标志位
    }
}

// 处理来自编码器通道B的中断请求
void EXTI1_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line1) == SET) {
        // ... 处理中断 ...
        EXTI_ClearITPendingBit(EXTI_Line1); // 清除中断标志位
    }
}

 在ISR内部,通常会执行如下操作:

  • 确认中断来源:通过检查中断标志位确定是否为期望的中断源。
  • 处理中断:执行必要的处理,如读取硬件状态、更新变量、设置输出等。
  • 清除中断标志位:在处理完中断之后,必须清除相应的中断标志位,否则中断请求会一直存在,导致中断服务程序被重复调用。

下面是Interrupt.c完成代码,和详细注释。

void Encoder_Init(void)
{
    /* 开启时钟 */
    // 对于GPIOB端口和AFIO(用于重新映射中断线的功能)需要使能它们的时钟。
    // RCC_APB2Periph_GPIOB和RCC_APB2Periph_AFIO是在库文件中定义的宏,用来指定时钟线。
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // GPIOB时钟使能
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);  // AFIO时钟使能
    
    /* GPIO初始化 */
    // 初始化GPIOB的0号和1号引脚作为输入。
    GPIO_InitTypeDef GPIO_InitStructure;  // 声明一个结构体变量,用来初始化GPIO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // 设置管脚模式为输入上拉
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; // 选择管脚0和管脚1
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置IO速度为50MHz
    GPIO_Init(GPIOB, &GPIO_InitStructure); // 应用配置到GPIOB
    
    /* AFIO选择中断引脚 */
    // 为外部中断线EXTI0和EXTI1选择PB0和PB1作为中断源。
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0); // 将PB0映射到EXTI的0线
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1); // 将PB1映射到EXTI的1线
    
    /* EXTI初始化 */
    // 设置EXTI的0线和1线,开启这两条线,并设置为中断模式和下降沿触发。
    EXTI_InitTypeDef EXTI_InitStructure;  // 声明一个结构体变量,用来初始化EXTI
    EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;  // 设置要配置的线路是EXTI的0线和1线
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;  // 使能线路
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;  // 设置为中断请求,而非事件请求
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  // 设置为下降沿触发
    EXTI_Init(&EXTI_InitStructure);  // 应用配置到EXTI
    
    /* NVIC中断分组 */
    // 设置NVIC的优先级分组。分组决定了抢占优先级和子优先级的位宽。
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  // 设置分组2,这是一个中间等级的分组设置
    
    /* NVIC配置 */
    // 配置NVIC以接收EXTI0和EXTI1的中断请求,并设置相应的优先级。
    NVIC_InitTypeDef NVIC_InitStructure;  // 声明一个结构体变量,用来初始化NVIC
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;  // 设置NVIC的中断通道为EXTI0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  // 使能这个中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  // 抢占优先级设置为1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  // 子优先级设置为1
    NVIC_Init(&NVIC_InitStructure);  // 应用配置到NVIC

    // EXTI1线路设置相似的NVIC配置
    NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;  // 设置NVIC的中断通道为EXTI1
    // 其余配置与EXTI0相同,除了子优先级为2,这意味着如果EXTI0和EXTI1同时请求,EXTI0的处理会被优先考虑
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;   //指定NVIC线路的响应优先级为2
    NVIC_Init(&NVIC_InitStructure);  // 应用配置到NVIC
}



// 函数名:Encoder_Get
// 功能:读取自上次调用此函数以来编码器的增量值
// 返回值:增量值,类型为int16_t(16位有符号整型)

// 使用Temp变量作为中继,目的是返回Encoder_Count后将其清零
// 在这里,也可以直接返回Encoder_Count,但这样就不是获取增量值的操作方法了,也可以实现功能,只是思路不一样
int16_t Encoder_Get(void)
{
    int16_t Temp; // 定义一个局部变量Temp,用于暂存编码器的增量值
    Temp = Encoder_Count; // 将全局变量Encoder_Count的值赋给Temp
    Encoder_Count = 0; // 将全局计数器清零,为下次计数做准备
    return Temp; // 返回增量值
}

// 函数名:EXTI0_IRQHandler
// 功能:外部中断0的中断服务程序
// 当外部中断0触发时,由中断控制器自动调用此函数
// 无返回值,无参数
void EXTI0_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line0) == SET) // 检查EXTI_Line0的中断是否被触发
    {
        // 为防止机械抖动误触发,再次检查GPIO_Pin_0的电平,如果出现数据乱跳的现象,可再次判断引脚电平
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) // 如果PB0的电平为低(因为是下降沿触发)
        {
            if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) // PB0的下降沿触发中断,同时检查PB1的电平,以判断旋转的方向
            {
                Encoder_Count--; // 如果PB1也为低,假定为反方向旋转,计数减一
            }
        }
        EXTI_ClearITPendingBit(EXTI_Line0); // 清除中断标志位,以防止中断再次触发
    }
}

// 函数名:EXTI1_IRQHandler
// 功能:外部中断1的中断服务程序
// 当外部中断1触发时,由中断控制器自动调用此函数
// 无返回值,无参数
void EXTI1_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line1) == SET) // 检查EXTI_Line1的中断是否被触发
    {
        // 为防止机械抖动误触发,再次检查GPIO_Pin_1的电平
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) // 如果PB1的电平为低(因为是下降沿触发)
        {
            if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) // 同时检查PB0的电平,以判断旋转的方向
            {
                Encoder_Count++; // 如果PB0也为低,假定为正方向旋转,计数加一
            }
        }
        EXTI_ClearITPendingBit(EXTI_Line1); // 清除中断标志位,以防止中断再次触发
                                            //中断标志位必须清除
											//否则中断将连续不断地触发,导致主程序卡死
    }
}

2.2 Interrupt.h

这里只需要声明一些函数就可以了

#ifndef __ENCODER_H
#define __ENCODER_H

void Encoder_Init(void);
int16_t Encoder_Get(void);

#endif

 

2.3 完整工程文件

基于STM32单片机使用中断实现旋转编码器计次

  • 32
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TENET-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值