首页/文章/ 详情

嵌入式学习(九)—STM32外部中断

1年前浏览366
在学习单片机时,外部中断作为外部信号常用的输入信号,那么中断具体的原理是什么,查阅了一些资料,整理一下相关知识。
在你读了很多程序,也写了一些程序后会发现,大多数在配置寄存器时,都要先把需要配置的位读取出来,再进行清零操作。
在配置中断时,我们一般要注意:对应中断位使能(ISER寄存器组,一共8组,但STM32F407只用了ISER[0],ISER[1],ISER[2],可以配置82个可屏蔽中断,ISER寄存器组中每个位控制一个中断),清除(ICER寄存器),挂起(ISPR寄存器)、解挂(ICPR寄存器)、激活(IABR寄存器)、优先级(IP寄存器、中断分组、屏蔽、I/O口映射等设置,其中优先级(IP寄存器)这个寄存器非常重要,这里重点说一下。
优先级(IP):中断优先级控制的寄存器组,由240个8Bit的寄存器组成,每个可屏蔽中断占用8bit,这样总共可以表示240个可屏蔽中断。比如像STM32F407只用到了其中的82个,STM32F429只用到了其中的91个,IP[90]~IP[0]分别对应中断90~0,每个可屏蔽中断占用的8bit 并没有全部使用,而是只用了高4位,这4位又分为抢占优先级和响应优先级(子优先级),抢占优先级在前,响应优先级在后,而这两个优先级各占的位数又要根据SCB->AIRCR中的中断分组设置来决定,分配关系如下图。

以下举例说明,如何配置

  1. 例如设置组2,那么此时所有的82个中断,每个中断优先级寄存器的高四位中最高2位是抢占优先级,低2位是响应优先级,此处有一个小技巧,比如0组,在配置AIRCR的[10:8]位时,正好是0的取反值,即111,依次类推,001(1组)->110,010(2组)->101,011(3组)->100,100(4组)->011。

  2. 根据上表可知,抢占优先级和子优先级的高4位是如何分配的,与中断分组息息相关,那么在配置IP寄存器的时候,应该考虑到:

    1)抢占优先级和响应优先级的分配和分组有关系,那么就借助中断分组这个桥梁来进行位的计算。

    2)根据以上分析可知,

    (0)组:(0)抢占-(4)响应、

    (1)组:(1)抢占-(3)响应、

    (2)组:(2)抢占-(2)响应、

    (3)组:(3)抢占-(1)响应、

    (4)组:(4)抢占-(0)响应

    抢占优先级在寄存器中占的位数:设x为组别,(4-x)就是要左移或者右移的位数,如何判断左移还是右移,根据IP寄存的高4位为抢占优先级和子优先级的位数,并且抢占优先级在前,子优先级在后,所以能知道抢占优先级的位数为<<(4-x)位如果0组,那么抢占优先级的位数为0,即左移4位,即在IP寄存器高4位没有抢占优先级的位,以此类推,所以抢占优先级在寄存器占的位数 = 抢占优先级数<<(4-x);

    子优先级在寄存器中所占的位数:子优先级在抢占优先级的后面,那么计算位数就得右移,子优先级位数为(0x0f>>x)。要用0x0f当一个介质,0000 1111正好是后四位为1,如果右移某一位,那么就少一个1,比如分组为1,子优先级位数为3(子优先级可以为0级、1级、2级、3级...7级,假设为7级,那么)0x0f>>1-->00001111 -->>1 -->0000 0111,根据分组为1时抢占优先级位数为1,子优先级位数为3正好满足要求.

  3. 所以IP寄存器的高四位应该这么设置:设一个u32的temp变量,抢占优先级为PreemptionPriority,子优先级为SubPriority则:

  4. temp = PreemptionPriority << (4-x);  temp |= SubPriority&(0x0f>>x);   现在有了抢占优先级和子优先级的位数了,那么接下来就应该判断是哪一个中断了。根据“中断优先级控制的寄存器组。由240个8Bit的寄存器组成,每个可屏蔽中断占用8bit,这样总共可以表示240个可屏蔽中断。比如像STM32F429只用到了其中的91个。IP[90]~IP[0]分别对应中断90~0”这句话,我们知道一共有多少个中断,并且每个中断的序号也都知道,那么我们到底需要设置哪个中断的哪个通道呢?

  5. 设置中断组和中断号:ISER中断使能寄存器可得要使能哪一个中断组和中断号?(每一个中断组一共有32个中断,32位寄存器,每一位代表一个中断)。我们可以这么考虑:首先我们知道中断号一共就0,1,2这三个,0有32个中断,1有32个中断,2有18个中断,一共82个中断。ISER[?]这里面的值应该这么计算呢?中断组有一个共同的特点就是都有32个中断,我们何不尝试对32取整、第几个中断那就对32取余呢?恩,豁然开朗啊。SO,设x为中断的编号,则ISER[X/32] |= 1<< X%32,“或”用来不改变其他位的值作用。真的是一举两得啊,不仅把中断组确定好了,还把哪一个中断确定下来并置位。

  6. 外部IO口的中断还需一个寄存器配置,也就是外部中断配置寄存器EXTICR.

    STM32F407 的 EXTI 控制器支持 23 个外部中断/事件请求每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。STM32F407 的 23 个外部中断为:

    线 0~15:对应外部 IO 口的输入中断。

    线 16:连接到 PVD 输出。

    线 17:连接到 RTC 闹钟事件。

    线 18:连接到 USB OTG FS 唤醒事件。

    线 19:连接到以太网唤醒事件。

    线 20:连接到 USB OTG HS 唤醒事件。

    线 21:连接到 RTC 入侵和时间戳事件。

    线 22:连接到 RTC 唤醒事件

    从上面可以看出,STM32F4供IO口使用的中断线只有16个,但是STM32F4的IO口却远远不止16个,那么STM32F4是怎么把16个中断线和IO口一一对应起来的呢?于是STM32就这样设计,GPIO的管脚GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G,H,I)分别对应中断线0~15。这样每个中断线对应了最多9个IO口,以线0为例:它对应了GPIOA.0、GPIOB.0,GPIOC.0,GPIOD.0,GPIOE.0,GPIOF.0,GPIOG.0,GPIOH.0,GPIOI.0。而中断线每次只能连接到1个IO口上,这样就需要通过配置来决定对应的中断线配置到哪个GPIO上了。

  7. 如图所示,好久没理解这个图啥意思,来回看了好多遍才恍然大悟。比如如我要设置 GPIOB.1 映射到 EXTI1,则只要设置 EXTICR[0]的 bit[7:4]为 0001 即可。比如我要设置GPIOE3映射到EXTI3,则只要设置EXTICR[0]的bit[15:12]为0100即可。也就是说EXTIx[15:0]分别对应0-15中断线,而每个中断线只能同时设置一个端口号的中断。比如想设置PB0为外部中断,那么其他的PE0,PD0等等都不能作为外部中断,因为EXTI0的值已经是0001了。也就是说EXTIx[15:0]对应的是端口的0-15引脚,和GPIOx的x无关,与GPIOx有关的是在中断线里的值。你想设置PB3为中断线,那么首先要找的是EXTI3,然后给EXTI3这四个位赋值,确定GPIOx的x值。EXTICR[0]只管了 GPIO 的 0~3 端口,EXTICR[1]只管了4~7端口,EXTICR[2]只管了8~11端口,EXTICR[3]只管了12~15端口。所以EXTICR寄存器一共有0~3四组。

四、外部中断配置函数的编写

  1. 首先开启SYSCFG的时钟,然后根据GPIOx的位得到中断寄存器组的编号,即EXTICR的编号,在EXTICR里面配置中断线应该配置到GPIOx的哪个位。然后使能该位的中断,最后配置触发方式。

  2. 怎么去配置呢?根据GPIOx的位得到中断寄存器组的编号:假设我们要设置GPIOE15这个引脚为中断引脚,我们可以得知要用中断线15,中断线应该在EXTICR[3]最高四位并且中断线EXTI15的值应该是0100,在EXTICR[3]寄存器中,我们只需左移三个四位就能到达中断线15的位置,每个中断线都是四位,我们为何不把四位看成一个整体进行移呢?所以我们要确定好怎么去移位。SO,有感觉了。左移(BIT%4)*4位,是不是就能够完成所有的位的需求呢?测试一下,15%4*4=12,左移12位,在EXTICR[0]左移12位到达EXTI3,(7%4)*4=12,在EXTICR[1]左移12位到达EXTI7。解决了中断线的问题,不难发现又出现了新的问题。EXTICR[?]怎么确定呢?中断线3:EXTICR[0],3--0,4--1,5--1,7--1,8--2,9--2,10--2,12--3,13--3,14--4,通过这么多数据不难发现,BITx对4取整不就正好对上么,是不是恍然大悟啊!……so,EXTICR组的值应该是EXTICR[BITx/4] |= ?<<(BITx%4)*4,是又是一个新的问题出现了,等式中未知的值应该是什么呢?谁左移这么位呢????应该这么考虑:根据上图所示,每一个GPIOx的分组在EXTICR里面的值都是固定的,也就是自动形成的,比如我们想定义GPIOA,那么在EXTICR里的值就应该是0000,GPIOB-0001,这是ST官方设计的,和我们没有关系。所以我们就知道这个未知的变量是什么呢,当然是GPIOx.即EXTICR[BITx/4] |= GPIOx<<(BITx%4)*4;我们来验证一下,比如GPIOF13引脚作为中断输入口,那么EXTICR[13/4] |=GPIOF<<(13%4)*4========>EXTICR[3] |= 0101<<1*4(左移四位),配置的正好,Perfect.别忘了我们在置位之前首先要进行清零,即EXTICR[BIT/4] &=~(0X000F<<(BITx%4)*4).这就设置好了EXTICR这个寄存器。

3.使能该位的中断.

IMR:中断屏蔽寄存器。这是一个 32 寄存器。但是只有前 23 位有效。当位 x 设置为 1 时,则开启这个线上的中断,否则关闭该线上的中断。EXTI->IMR |= 1<<BITx,简单的设置完成;

3上升沿或者下降沿.

RTSR:上升沿触发选择寄存器。该寄存器同 IMR,也是一个 32 为的寄存器,只有前 23位有效。位 x 对应线 x 上的上升沿触发,如果设置为 1,则是允许上升沿触发中断/事件。否则,不允许。EXTI->RTSR |= 1<<BITx;

FTSR:下降沿触发选择寄存器。同 RTSR,不过这个寄存器是设置下降沿的。下降沿和上升沿可以被同时设置,这样就变成了任意电平触发了。EXTI->FTSR |= 1<< BITx.


说点题外话,怎么去设计一个通用的GPIO的设置函数呢?

设置一个GPIO我们需要设置如下参数:GPIOx哪个组、BITx组里的哪个引脚、MODE:0~3;模式选择,0,输入(系统复位默认状态);1,普通输出;2,复用功能;3,模拟输入、OTYPE:0/1;输出类型选择,0,推挽输出;1,开漏输出、OSPEED:0~3;输出速度设置,0,2Mhz;1,25Mhz;2,50Mhz;3,100Mh.、PUPD:0~3:上下拉设置,0,不带上下拉;1,上拉;2,下拉;3,保留.根据这些参数我们就可以来设计一个通用GPIO设置的函数了,参数包括:GPIOx、BITx、MODE、OTYPE、OSPEED、PUPD.

  1. 好了有了参数,接下来就该想办法怎么去使用这些参数。

    首先我们得考虑输入一个引脚的话我们怎么知道是这个引脚,所以得需要一个遍历,即一个for循环:

    u8 pinpos=0,u8 pos=0,u8 curpin=0;根据名称就可以知道这三个变量的作用,第一个pinpos是需要遍历的变量,pos是一个中间值,curpin是目前的引脚号。

    void GPIO_SET(GPIO_TypeDef* GPIOx,u32 BITx,u32 MODE,u32 OTYPE,u32 OSPEED,u32 PUPD){

    for(pinpos=0;pinpos<16;pinpos++){

    pos = 1<<pinpos;//一个个的位去遍历

    curpin = BITx & pos;//把写入的引脚号与遍历进行对比,检查是否要设置

    if(curpin == pos){//说明找到了需要设置的引脚

    GPIOx->MODE &= ~(3<<(pinpos*2))//首先清零模式寄存器的值

    GPIOx->MODE |=(MODE<<(pinpos*2));//模式寄存器是两位配置,所以要乘2

    if((MODE==0X01)||(MODE==0X02)){//判断输出模式/复用,输入模式没有速度

    GPIOx->OSPEEDR &= ~(3<<(pinpos*2)); //清除原来的设置

    GPIOx->OSPEEDR |= (OSPEED<<(pinpos*2));//设置新的速度值

    GPIOx->OTYPER &= ~(1<<pinpos);       //清除原来的输出类型

    GPIOx->OTYPER |= (OTYPE<<pinpos);   //设置新的输出类型

    }

    GPIOx->PUPDR &=~(3<<(pinpos*2));       //清除原来的设置

    GPIOx->PUPDR |= (PUPD<<(pinpos*2));   //设置新的上下拉

    }

    }

    要把I/O口作为外部中断输入,有以下几个步骤:

    1) 初始化 IO 口为输入。

    这一步设置你要作为外部中断输入的 IO 口的状态,可以设置为上拉/下拉输入,也可以设置为浮空输入,但浮空的时候外部一定要带上拉,或者下拉电阻。否则可能导致中断不停的触发。在干扰较大的地方,就算使用了上拉/下拉,也建议使用外部上拉/下拉电阻,这样可以一定程度防止外部干扰带来的影响。

    2) 开启 SYSCFG 时钟,设置 IO 口与中断线的映射关系。

    STM32F429 的 IO 口与中断线的对应关系需要配置外部中断配置寄存器 EXTICR,这样我们要先开启 SYSCFG 的时钟,然后配置 IO 口与中断线的对应关系(通过 EXTICR 寄存器设置)。才能把外部中断与中断线连接起来。

    3) 开启与该 IO 口相对的线上中断,设置触发条件。

    这一步,我们要配置中断产生的条件, STM32F429 可以配置成上升沿触发,下降沿触发,或者任意电平变化触发,但是不能配置成高电平触发和低电平触发。这里根据自己的实际情况来配置,同时要开启中断线上的中断。

    4) 配置中断分组(NVIC),并使能中断。

    这一步,我们就是配置中断的分组,以及使能,对 STM32F429 的中断来说,只有配置了NVIC 的设置,并开启才能被执行,否则是不会执行到中断服务函数里面去的。


来源:不懂幽默的秦二
通用控制
著作权归作者所有,欢迎分享,未经许可,不得转载
首次发布时间:2023-06-21
最近编辑:1年前
点墨设计
本科 | 高级硬件工程... 十年饮冰,难凉热血!
获赞 0粉丝 6文章 48课程 0
点赞
收藏
未登录
还没有评论
课程
培训
服务
行家
VIP会员 学习 福利任务 兑换礼品
下载APP
联系我们
帮助与反馈