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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

新兴游戏引擎Godot vs. 主流游戏引擎Unity和虚幻引擎,以及版本控制工具Perforce Helix Core如何与其高效集成

游戏行业出现一个新生事物——Godot,一个免费且开源的2D和3D游戏引擎。曾经由Unity和虚幻引擎(Unreal Engine)等巨头主导的领域如今迎来了竞争对手。随着最近“独特”定价模式的变化,越来越多的独立开发者和小型开发团队倾向于选择…

【数据结构】反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 Definition for singly-linked list.struct ListNode {int val;struct ListNode *next;};typedef struct ListNode ListNode; struct ListNode* reverseList(struct ListNode* head) {i…

JavaEE初阶——文件操作和IO

T04BF 👋专栏: 算法|JAVA|MySQL|C语言 🫵 小比特 大梦想 此篇文章与大家分享文件操作及IO的内容 如果有不足的或者错误的请您指出! 目录 *1.解释IO**2.关于文件的基本知识*2.1路径2.1.1绝对路径2.1.2相对路径 2.2文件分类 *3.通过Java代码操作文件*3.1针…

Arcpy入门笔记(三):数据属性的读取

Arcpy入门笔记(三):数据属性的获取 文章目录 Arcpy入门笔记(三):数据属性的获取常用的属性Describe对象属性(部分)数据集属性(部分)表属性(部分&a…

python 脚本头(PyCharm+python头部信息、py头部信息、python头信息、py头信息、py文件头部)

文章目录 参考PyCharm设置脚本头头部信息 参考 https://developer.aliyun.com/article/1166544 https://blog.csdn.net/Dontla/article/details/131743495 https://blog.csdn.net/dongyouyuan/article/details/54408413 PyCharm设置脚本头 打开pycharm,点击file–…

5G赋能 扬帆未来|AGV无人仓成黑科技“顶流”

AGV 近年来,无人化这个概念逐渐被运用到了社会中的各个行业,而跟物流有关的就有无人分拣机器人、无人驾驶卡车、和无人叉车,越来越多的新装备也开始投入到实际运用中。 仓储管理在物流管理中占据着核心地位。传统的仓储管理中存在诸多的弊端…

怎样选购内衣洗衣机?2024年5款最新推荐机型种草

随着科技的不断发展,内衣洗衣机成为了家家户户必备的小家电之一,为我们的生活带来了极大的便利。但面对市场上众多的内衣洗衣机品牌,如何选择一款质量好的内衣洗衣机呢?本文将为您推荐5款最新的内衣洗衣机品牌,从而帮助…

一文解析golang中的协程与GMP模型

文章目录 前言1、线程实现模型1.1、用户级线程与内核级线程1.2、内核级线程模型1.3、用户级线程模型1.3、两级线程模型 2、GMP模型2.1、GMP模型概述2.1、GMP v1版本 - GM模型2.2、GMP v2版本 - GMP模型2.3、GMP相关源码2.4 调度流程2.5 设计思想 3.总结 前言 并发(并行&#x…

Babylon.js 程序化建模简明教程

Babylon.js 中的每个形状都是由三角形或小面的网格构建而成,如题图所示。 NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - Th…

(二十九)加油站:面向对象重难点深入讲解【重点是元类】

目录: 每篇前言:0. Python中的元类:1. 本文引子:2. Python中的mro机制:3. Python中类的魔法属性dict:注意事项: 拓展——内建函数dir() 4. 正式谈一谈元类(metaclass):&a…

IIR滤波器的设计与实现(内含设计IIR滤波器的高效方法)

写在前面:初学者学习这部分内容,要直接上手写代码可能会感到比较困难,我这里推荐一种高效快速的设计IIR,FIR滤波器的方法——MATLAB工具箱:filterDesigner。打开的方法很简单,就是在命令行键入:filterDesig…

virtio-wayland

CrosVM是Chrome操作系统中,用于创建虚拟机的应用。是一个Rust编写的轻量级的虚拟机。借助于CrosVM 用户可以很容易的在ChromeOS中运行Linux、Android以及Windows应用程序 概述 目前crosvm实现了virtio wayland协议,实现了对linux虚拟机wayland协议支持 …

动态规划——斐波那契数列模型:面试题08.01.三步问题

文章目录 题目描述算法原理1.状态表示2.状态转移方程3.初始化4.填表顺序5.返回值 代码实现CJava 题目描述 题目链接:面试题08.01.三步问题 如果n是0走法可能是1也可能是0,所以本题范围并不需要考虑直接从1开始即可 因为以3为结尾有直接从0到3的方式&a…

Kafka 3.x.x 入门到精通(04)——对标尚硅谷Kafka教程

Kafka 3.x.x 入门到精通(04)——对标尚硅谷Kafka教程 2. Kafka基础2.1 集群部署2.2 集群启动2.3 创建主题2.4 生产消息2.5 存储消息2.5.1 存储组件2.5.2 数据存储2.5.2.1 ACKS校验2.5.2.2 内部主题校验2.5.2.3 ACKS应答及副本数量关系校验2.5.2.4 日志文…

从哪些角度优化数据资产管理?详解如何将数据转化为企业持续竞争力

在上一篇文章中我们介绍了数据资产管理的诸多保障措施,上篇文章指路👉如何保障数据资产管理有效开展?做好这几点就够了! 本文重点将转向数据资产管理的实践。在当今这个数据驱动的时代,数据已成为企业最宝贵的资产之一…

使用Excel生成sql脚本(insert/update/delete)

目录 前言 一、Excel文件脚本变量 二、操作示例 前言 在系统使用初期,存在某种原因,需要对数据库数据进行批量处理操作。往往都是通过制定Excel表格,通过Excel导入到数据库中,所以就弄一个excel生成sql的导入脚本,希…

呼叫中心常用名词解释

ACD Automatic Call Distribution 自动呼叫分配,即排队。一般是用于呼叫中心的功能。在一个呼叫中心中,会有很多的座席来应答用户的来话,但是每个座席所具有的技能或者所承担的工作负荷是 不同的,如何根据一定的算法来保证所有的座…

C语言中浮点型存储方式

前言 这次是上次博客的续写哦,如果有小伙伴不了解,可以点击链接跳转 C语言中整数与浮点数在内存中的存储 我们在上次的博客中给大家留了一段代码,不知道大家现在有没有想明白呢,让我来为大家揭秘吧!! int m…

Azure AKS集群监控告警表达式配置

背景需求 Azure AKS集群中,需要对部署的服务进行监控和告警,需要创建并启用预警规则,而这里怎么去监控每个pod级别的CPU和内存,需要自己写搜索查询 解决方法 搜索和查询的语句如下,需要自己替换其中的部分信息,其中…

python爬虫 - 爬取html中的script数据(36kr.com新闻信息)

文章目录 1. 分析页面内容数据格式2. 使用re.findall方法,爬取新闻3. 使用re.search 方法,爬取新闻 1. 分析页面内容数据格式 打开 https://36kr.com/ 按F12(或 在网页上右键 --> 检查(Inspect)) 找…