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

news2024/10/7 6:43:17

系列文章目录

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单片机使用中断实现旋转编码器计次

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1626035.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode57. 插入区间

LeetCode57.插入区间 题目思路: 代码 /* 前置知识&#xff1a; vector<vector<int>> a,b; 二维vector数组是可以将二维中的一维vector数组给push_back的&#xff0c; 不是只有单个元素才可以&#xff0c;整个一维的vector数组也可以 b[0] {1,2,3},b[1] {4,5,6}…

积极应对半导体测试挑战 加速科技助力行业“芯”升级

在全球半导体产业高速发展的今天&#xff0c;中国“芯”正迎来前所未有的发展机遇。AI、5G、物联网、自动驾驶、元宇宙、智慧城市等终端应用方兴未艾&#xff0c;为测试行业带来新的市场规模突破点&#xff0c;成为测试设备未来重要的增量市场。新兴领域芯片产品性能不断提升、…

如何解决IntelliJ IDEA 2024打开项目时频繁闪退问题

&#x1f42f; 如何解决IntelliJ IDEA 2024打开项目时频繁闪退问题 &#x1f43e; 文章目录 &#x1f42f; 如何解决IntelliJ IDEA 2024打开项目时频繁闪退问题 &#x1f43e;摘要引言正文&#x1f4d8; 识别问题&#x1f4d9; 内存配置调整步骤1: 定位vmoptions文件步骤2: 修改…

企业年度规划:你的未来,我们帮你“画”出来!

亲爱的朋友们&#xff0c;您是不是常常觉得企业运营就像一场没有剧本的戏&#xff0c;时而高歌猛进&#xff0c;时而摸黑前行&#xff1f;别慌&#xff0c;今天我们就来科普一下&#xff0c;如何给企业来一场精心策划的“年度大戏”——年度规划&#xff01; 首先&#xff0c;…

qt实现方框调整

效果 在四周调整 代码 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QWidget>class MainWindow : public QWidget {Q_OBJECT public:explicit MainWindow(QWidget *parent 0);~MainWindow();void paintEvent(QPaintEvent *event);void updateRect();void re…

每年首版次测试报告的要求有哪些?

每年首版次测试报告的要求可能因不同的地区、行业或产品而有所差异&#xff0c;但一般而言&#xff0c;它们通常遵循一些基本的标准和原则。以下是一些常见的首版次测试报告要求&#xff1a; 完整性&#xff1a;测试报告应包含所有必要的测试内容&#xff0c;包括但不限于测试…

git merge 和 git rebese的区别

git merge 和 git rebese的区别 拉取分支和合并代码会涉及两种选择&#xff0c;git merge 和 git rebase&#xff1a; rebase&#xff1a;变基&#xff0c;会有一个干净的分支&#xff0c;但是对于记录来源不够清楚merge&#xff1a;合并&#xff0c;git 分支看起来比较混乱&…

Linux 调度优先级

Linux中的每个任务都有其优先级。这个优先级的范围从-20到19。优先级越低&#xff08;-20&#xff09;&#xff0c;分配 给任务的CPU时间就越多。默认的优先级是0。 并非所有的任务都需要使用相同的优先级。交互式应用要求快速响应&#xff0c;通过 crontab 运行的后台…

半导体行业的隐形翅膀:国产RFID技术突破封锁,助力生产

半导体行业的隐形翅膀&#xff1a;国产RFID技术突破封锁&#xff0c;助力生产 RFID技术&#xff0c;简单来说&#xff0c;就是一种自动识别技术&#xff0c;通过无线电波实现对标签信息的读取和写入。而这些标签&#xff0c;就像给物品贴上的小标签&#xff0c;上面存储着它们…

Windows主机入侵检测与防御内核技术深入解析

第2章 模块防御的设计思想 2.1 执行与模块执行 本章内容为介绍模块执行防御。在此我将先介绍“执行”分类&#xff0c;以及“模块执行”在“执行”中的位置和重要性。 2.1.1 初次执行 恶意代码&#xff08;或者行为&#xff09;要在被攻击的机器上执行起来&#xff0c;看起…

测试架构师必备技能-Nginx安装部署实战

Nginx(“engine x”)是一款是由俄罗斯的程序设计师Igor Sysoev所开发高性能的免费开源Web和 反向代理服务器&#xff0c;也是一个 IMAP/POP3/SMTP 代理服务器。在高并发访问的情况下&#xff0c;Nginx是Apache服务器不错的替代品。官网数据显示每秒TPS高达50W左右。本文为读者朋…

PHP项目搭建与启动

1、拉取项目 2、安装phpstudy 下载地址&#xff1a; Windows版phpstudy下载 - 小皮面板(phpstudy) (xp.cn) 软件安装&#xff1a; Apache2.4.39、Nginx1.15.11、MySQL8.0.12、 composer2.5.8 添加伪静态 将下面代码写入到伪静态配置文本域框内&#xff1a; location ~* (ru…

SD-WAN怎样助力企业网络升级

随着企业规模的持续扩张&#xff0c;其网络建设的重要性日益凸显&#xff0c;成为业务成功的基石。尤其对于中小企业而言&#xff0c;信息化和电脑化已成为推动生产力和竞争力提升的关键所在。办公室自动化、数据库、ERP、CRM、物流供应链等关键业务应用的不断增加&#xff0c;…

Windows下Golang初学乍到

安装 没啥说的&#xff0c;官网下载即可&#xff0c;地址&#xff1a;All releases - The Go Programming Language 根据系统类型下载即可&#xff01; 配置 Windows下安装完后&#xff0c;发现path中已经有了&#xff0c;但为了避免可能的问题&#xff0c;还是建议配置GOPA…

pwn | BUUCTF rip 1 pwn基本思路

题目&#xff1a;BUUCTF rip 1 整体参考&#xff1a; 添加链接描述&#xff08;写得好详细我好爱 BUUCTF (PWN) RIP详细分析&#xff08;为什么payload有两种写法 其他知识&&引用&#xff1a; 二进制的保护机制 8086汇编语言dup指令学习 【Tips】使用gdb-peda进行RIP覆…

java 词法分析练习

import parser.Parser;import java.util.ArrayList; import java.util.Arrays; import java.util.List;public class Main {public static void main(String[] args) {// 关键词List<String> keyList new ArrayList<>(Arrays.asList("int","String…

Android --- 网络请求

通常在 Android 中进行网络连接一般使用 Scoket 和HTTP&#xff0c;HTTP 请求方式比 Scoket 多。HTTP 请求一般采用原生的 HttpClient 和 HttpUrlConnection 的两种网络访问方式&#xff08;系统自带的&#xff09;。但是在 Android 5.0 的时候 Google 就不推荐使用 HttpClient…

力扣--N皇后

题目: 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方案。…

免费预约即将截止,5月7日上海TCT亚洲3D打印展参观指南,收藏!

进入TCT亚洲展官网&#xff08;网页搜索TCT亚洲展&#xff09;&#xff0c;免费登记预约 2024年TCT亚洲展作为推动增材制造在亚洲市场的业务交流的重要平台&#xff0c;将于2024年5月7日至9日在国家会展中心&#xff08;上海&#xff09;7.1&8.1馆举办&#xff0c;与海内外…

【LAMMPS学习】八、基础知识(4.4)TIP4P水模型

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…