🚀write in front🚀
🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
💬本系列哔哩哔哩江科大STM32的视频为主以及自己的总结梳理📚
🚀Projeet source code🚀💾工程代码放在了本人的Gitee仓库:iPickCan (iPickCan) - Gitee.com
引用:
STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili
Keil5 MDK版 下载与安装教程(STM32单片机编程软件)_mdk528-CSDN博客
STM32之Keil5 MDK的安装与下载_keil5下载程序到单片机stm32-CSDN博客
0. 江协科技/江科大-STM32入门教程-各章节详细笔记-查阅传送门-STM32标准库开发_江协科技stm32笔记-CSDN博客
江科大STM32学习笔记(上)_stm32博客-CSDN博客
STM32学习笔记一(基于标准库学习)_电平输出推免-CSDN博客
术语:
|--------------------------------------|--------|
| 英文缩写 | 描述 |
| GPIO:General Purpose Input Onuput | 通用输入输出 |
| AFIO:Alternate Function Input Output | 复用输入输出 |
| AO:Analog Output | 模拟输出 |
| DO:Digital Output | 数字输出 |
正文:
0. 概述
从 2024/06/12 定下计划开始学习下江协科技STM32课程,接下来将会按照哔站上江协科技STM32的教学视频来学习入门STM32 开发,本文是视频教程 P2 STM32简介一讲的笔记。
1.STM32中断系统
中断系统是管理和执行中断的逻辑结构,外部中断是众多能产生中断的外设之一,所以本节我们就借助外部中断来学习一下中断系统。在以后学习其它外设的时候,也是会经常和中断打交道的。
中断系统
🌵中断:在主程序运行过程中,出现了特定的中断触发条件(中断源。比如对于外部中断来说,可以是引脚发生了电平跳变;对于定时器来说,可以是定时的时间到了;对于串口通信来说,可以是接收到了数据),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。(就好比晚上睡觉前定了个闹钟,时间到了提醒你,不管时间到不到你可以安心睡觉)。
🌵中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急 进行裁决,优先响应更加紧急的中断源。(这个中断优先级是我们根据程序设计的需求,自己设置的)
🌵中断嵌套:(中断程序再次中断,二次中断现象 )当一个中断程序正在运行时,又有新的更高优先级 的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。(也是为了照顾非常紧急的中断)。
中断执行流程
中断程序的执行流程如下,当它执行到某个地方时,外设的中断条件满足了,那这时,无论主程序是在干什么事情(比如OLED显示程序才执行一半,Delay函数还在等待等)中断来了,主程序都得立即暂停,程序由硬件电路自动跳转到中断程序中,当中断程序执行完之后,程序再返回被暂停的地方继续运行(这个暂停的地方,叫做断点)。为了程序能在中断返回后继续原来的工作,在中断执行前,会对程序的现场进行保护,中断执行后,会再返回现场,这样保证主程序被中断了,回来之后也能继续执行。
中断嵌套的执行流程如下。当一个中断正在执行时,又有新的优先级更高的中断来,那个旧中断会被打断,执行新的中断,新的中断结束,再继续执行原来的中断,原来的中断结束,再继续主程序,这就是中断嵌套的执行流程。
c语言中,中断的执行流程如下。上面是主函数,while(1)死循环里就是主程序,正常情况下,程序就是在主程序中不断循环执行,当中断条件满足时,主程序就会暂停,然后自动跳转到中断程序里运行,中断程序执行完之后,再返回主程序执行。一般中断程序都是在一个子函数里,这个函数不需要我们调用,当中断来临时,由硬件自动调用这个函数,这就是在c语言中,中断的执行流程。
STM32中断
- 68个可屏蔽中断通道(中断源),包含EXTI(外部中断)、TIM、ADC(模数转换器)、USART(串口)、SPI、I2C、RTC(实时时钟)等多个外设。(几乎所有模块都能申请中断)
- 使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级。
- NVIC就是STM32中用来管理中断、分配优先级的,NVIC的中断优先级共有16个等级。
- EXTIx是外部中断对应的中断资源。
下图为stm32的中断资源,上面灰色的是内核中断(我们一般不用,了解即可),下面不是灰色的部分就是stm32外设的中断了,外设电路检测到有什么异常或事件,需要提示一下CPU的时候,它就可以申请中断,让程序调到对应的中断函数里运行一次,用来处理这个异常或事件。
图中最右边是中断的地址,因为程序中的中断函数,它的地址是由编译器来分配的,是不固定的,但是我们的中断跳转,由于硬件的限制,只能跳到固定的地址执行程序,所以为了硬件能够跳转到一个不固定的中断函数里,这里就需要在内存中定义一个地址的列表,这个列表的地址是固定的,中断发生后,就跳到这个固定位置,然后在这个固定位置,由编译器,再加上一个跳转到中断函数的代码,这样中断跳转就可以跳转到任意位置了,这个中断地址的列表,就叫中断向量表,相当于中断跳转的一个跳板,不过我们用c编程,是不需要管这个中断向量表的,因为编译器都帮我们做好了。
NVIC基本结构
NVIC(嵌套中断向量控制器),在stm32中,它是用来统一分配中断优先级和管理中断的,NVIC是一个内核外设,是CPU的小助手(如果把中断全接到cpu上,会很麻烦,毕竟CPU主要是用来运算的),NVIC有很多输入口,下图中线上划了个斜杠上面写了n(这个意思是:一个外设可能会同时占用多个中断通道,所以这里有n条线),然后NVIC只有一个输出口,NVIC根据每个中断的优先级分配中断的先后顺序,之后通过右边这一输出口就告诉CPU该处理哪个中断,对于中断先后顺序分配的任务,CPU不需要知道。
举个例子:比如CPU是医生,如果医院只有一个医生时,当看病人很多时,医生就得先安排一下先看谁后看谁,如果有紧急的病人,那还得让紧急的病人最先来,这个安排先后顺序的任务很繁琐会影响医生看病的效率,所以医院就安排了一个叫号系统(NVIC),来病人了统一取号并且根据病人的等级,分配一个优先级,然后叫号系统看一下现在在排队的病人,优先叫号紧急的病人,最后叫号系统给医生输出的就是一个一个排好队的病人,医生就可以专心看病了。(EXTI、TIM、ADC等就是病人)。
NVIC优先级分组
为了处理不同形式的优先级,STM32的NVIC可以对优先级进行分组,分为抢占优先级和响应优先级。
抢占优先级和响应优先级的区别,例子理解:还想一下病人叫号的例子,对于紧急的病人,其实有两种形式的优先。一种是,上一个病人1在看病,外面排队了很多病人,当病人1看完后,外面排队中的紧急病人最先进去看病即使这个紧急病人是最后来的,这种在排队中的插队的就叫响应优先级,响应优先级高的可以插队提前看病。另一种是,上一个病人1在看病,外面排队中的病人2比病人1更加紧急,病人2可以不等病人1看完直接冲到医生的屋里,让病人1先靠边站,先给病人2看病,病人2看完病接着病人1看病,然后外面排队的病人再进来,这种形式的优先级就是中断嵌套,这种决定是不是可以中断嵌套的优先级,就叫抢占优先级,抢占优先级高的,可以进行中断嵌套。
为了将优先级区分为抢占优先级和响应优先级,就需要对这16个优先级优先级进行分组,NVIC的中断优先级由优先级寄存器的4位(0~15,4位二进制,对应16个优先级)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级
优先级的数值越小,优先级越高,0就是最高优先级
抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队(中断号是中断表的左边数字,数值小的优先响应),所以stm32的中断不存在先来后到的排队方式,在任何时候都是优先级高的先响应。
下表,因为优先级总共是4位,所以就有(0,4)、(1.3)、(2,2)、(3,1)、(4、0)这五种分组方式,分组0,就是0位的抢占等级,取值为0,4位的响应等级,取值为0~15,分组1234雷同。这个分组方式是我们在程序中自己进行选择的,选好分组方式后,就要注意抢占优先级和响应优先级的取值范围了,不要超出这个表里规定的取值范围。
|------|-------------|-------------|
| 分组方式 | 抢占优先级 | 响应优先级 |
| 分组0 | 0位,取值为0 | 4位,取值为0~15 |
| 分组1 | 1位,取值为0~1 | 3位,取值为0~7 |
| 分组2 | 2位,取值为0~3 | 2位,取值为0~3 |
| 分组3 | 3位,取值为0~7 | 1位,取值为0~1 |
| 分组4 | 4位,取值为0~15 | 0位,取值为0 |
| | | |
EXTI简介
🌿EXTI(Extern Interrupt)外部中断
🌿EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。(简单说:引脚电平变化,申请中断)
🌿支持的触发方式(引脚电平的变化类型):上升沿(电平从低电平变到高电平的瞬间触发中断)/下降沿(电平从高电平变到低电平的瞬间触发中断)/双边沿(上升沿和下降沿都可以触发中断)/软件触发(程序执行代码就能触发中断)
🌿支持的GPIO口(外部中断引脚):所有GPIO口都能触发中断,但相同的Pin不能同时触发中断(比如PA0和PB0不能同时使用,智能选一个作为中断引脚;所以如果有多个中断引脚要选择不同的pin引脚,比如PA0和PA1、PB3就可以)
🌿通道数:总共有20个中断线路。16个GPIO_Pin(对应GPIO_pin0到15,是外部中断的主要功能),外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒(这4个中断线路,是因为外部中断有个功能是从低功耗模式的停止模式下唤醒STM32那对于PVD电源电压检测,当从电源从电池过压恢复时就需要PVD借助一下外部中断的退出停止模式;对于RTC闹钟来说,有时候为了省电,RTC定一个闹钟之后,STM32回进入停止模式,等到闹钟响的时候再唤醒,这叶需要借助外部中断,剩余USB唤醒、以太网唤醒也是类似的作用)
🌿触发响应方式:中断响应 (引脚电平触发中断,申请中断,让CPU执行中断函数 )/事件响应 (不会触发中断,而是触发别的外设操作,属于外设之间的联合工作。外部中断的信号不会通向CPU而是通向其它外设,用来触发其它外设的操作,比如触发ADC转换、触发DMA等)
EXTI基本结构
外部中断的整体结构图如下:
首先,最左边是GPIO口的外设 ,每个GPIO外设有16个引脚,所以进来16根线;如果每个引脚占用一个通道,那EXTI的16个通道是不够用的,所以在这里会有一个AFIO中断引脚选择的电路模块,这个AFIO就是一个数据选择器 (可以将图中前面的3个GPIO外设的16个引脚中的其中一个连接到后面的EXTI通道(16个GPIO通道),所以对于PA0\PB0\PC0这些,通过AFIO选择之后只有其中一个能接到EXTI的通道0上),然后通过AFIO选择后的16个通道,就能接到了EXTI边沿检测及控制电路上,同时下面这4个蹭网的外设(PVD\PTC\USB\ETH)也是并列接进来的,这些加起来就组成了EXTI的20个 输入信号,然后经过EXTI电路之后,分为了两种输出,也就是中断响应 和事件响应(上面接到了NVIC用来触发中断,下面有20条输出线路到了其它外设,也就是事件响应)
注意点:EXTI9_5是外部中断的5,6,7,8,9分到了一个通道里,EXTI15_10也是一样;也就是说外部中断的9到5会触发同一个中断函数,15到10也会触发同一个中断函数;在编程的时候,我们在这两个中断函数里,需要再根据标志位区分到底是哪个中断进来的。(本来20路输入,应该有20路中断的输出,可能20个输出太多了比较占用NVIC的通道资源,所以就把其中的外部中断9~5,15~10,给分到了一个通道)
AFIO复用IO口内部电路
内部电路就是一系列的数据选择器,如下图的最上面输入是PA0\PB0\PC0等尾号都是0,然后通过数据选择器最终选择一个,连接到EXTI0上,上面写的文字是说配置这个寄存器的哪一个位就可以决定选择哪一个输入,图中后面部分内容都雷同。
🌵AFIO主要用于引脚复用功能的选择和重定义(也就是数据选择器的作用)。
🌵在STM32中,AFIO主要完成两个任务:复用功能引脚重映射(就是最开始提到的引脚定义表 ,当想把默认复用功能换到重定义功能时,就是用AFIO来完成的,这也是AFIO的一大主要功能)、中断引脚选择。
EXTI内部电路框图
EXTI的右边就是20根输入线,然后输入线首先进入边沿检测电路,在上面的上升沿寄存器和下降沿寄存器可以选择是上升沿触发还是下降沿触发或者两个都触发,接着硬件触发信号和软件中断寄存器的值就进入到这个或门 的输入端(也就是任意一个为1,或门就可以输出1),然后触发信号通过这个或门后就兵分两路,上一路是触发中断的,下一路是触发事件的:触发中断首先会置一个挂起寄存器(挂起寄存器相当于一个中断标志位,可以读取这个寄存器判断是哪个通道触发的中断,如果挂起寄存器置1,它就会继续向左走和中断屏蔽寄存器共同进入一个与门(与门实际上就是开关控制作用,中断屏蔽寄存器给1那另一个输入就是输出,也就是允许中断;中断屏蔽寄存器给0,那另一个输入无论是什么,输出都是0,相当于屏蔽了这个中断),然后是NVIC中断控制器)。接着就是下一路的选择是触发事件,首先也是一个事件屏蔽寄存器进行开关控制,最后通过一个吗,脉冲发生器到其它外设(脉冲发生器就是给一个电平脉冲,用来触发其它外设的动作)
补充:框图最上面两个就是外设接口和APB总线,我们可以通过总线访问这些寄存器。
EXTI外部中断的特性和使用场景
🌵什么样的设备需要用到外部中断,使用外部中断有什么好处呢?大概总结了使用外部中断模块的特性:就是对于stm32来说,想要获取的信号是外部驱动的很快的突发信号。
🌵外部中断的使用场景。比如,旋转编码器的输出信号,可能很久都不会拧它,这时不需要stm32做任何事情,但是我一拧它,就会有很多脉冲波形需要stm32接收,这个信号是突发的,stm32不知道什么时候会来,同时它是外部驱动的,stm32只能被动读取,最后这个信号非常快,stm32稍微晚一点来读取就会错过很多波形,所以对于这种情况来说,就可以考虑使用stm32的外部中断,有脉冲过来,stm32立即进入中断函数处理,没有脉冲的时候stm32就专心做其他事情;另外还有比如,红外遥控接收头的输出,接收到要遥控数据之后,它会输出一端波形,这个波形转瞬即逝,并且不会等你,所以就需要使用外部中断来读取;最后还有按键,虽然它的动作是外部驱动的突发事件但我并不推荐使用外部中断来读取按键,因为外部中断不好处理按键抖动和松手检测,对于按键来说,它的输出波形也不是转瞬即逝的,所以要求不高的话可以在主程序中循环读取,也可以考虑一下定时器中断读取的方式(这样即可以后台读取按键值、不会阻塞主程序也可以很好的处理按键抖动和松手检测的问题)
手册
大概看一下每个外设在手册的介绍
NVIC是内核外设,在这个内核cortex-m3编程手册中查看,这个cortex-m3编程手册就是内核和内核外设的详细介绍,想研究一下内核的运转,可以看一下这个手册
NVIC的一些寄存器
这个中断优先级寄存器就是用来设置每个中断的优先级的,用库函数直接给结构体赋值就行了,要知道库函数要最终落实到寄存器上来的
中断分组配置寄存器被分配到了这个SCB里面
这三位就是用来配置中断分组的
中断和外部中断的介绍在参考手册中
AFIO介绍
补充(与门,活门,非门)
**或门(**无直边)。它可以有多个输入,但只能有一个输出。执行的是或的逻辑,在输入端(曲边),只要有一个高电平1,输出的高电平就为1;只有全部输入低电平0,输出才为0。(尖头为输出)。(或1为1,全0则0)
与门(直边)。它可以有多个输入,但只能有一个输出。执行的是与的逻辑,在输入端(直边),只要有一个是低电平0,输出就是0;只有全部输入1,输出才为1。(与0为0,全1则1)
非门(三角号加个圈)。它只有一个输入,一个输出;输入1就输出0,输入0就输出1,执行的是非得逻辑(圈为输出,取反)
数据选择器(一个梯形)。有多个输入,一个输出,在侧面有选择控制端,根据控制端的数据,从输入选择一个接到输出。
表示20根线,代表20个通道
3.旋转编码器和红外对射计数器
本节先主要学习外部中断读取编码器计次数据的用法,后面学了定时器,还会再来看一下编码器测速的用途。
旋转编码器简介
旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向
类型:机械触点式/霍尔传感器式/光栅式
1.对射式红外传感器
下面的是一种最简单的编码器样式,这里使用的也是对射式红外传感器来测速的,为了测速还需配合一个光栅编码盘(银色圆圈),当这个编码盘转动时,红外传感器的红外光就会出现遮挡、透过、遮挡、透过这样的现象,对应模块输出的电平就是高低电平交替的方波,方波的个数代表了转过的角度,方波的频率表示转速,我们就可以用外部中断来捕获这个方波的边沿,以此来判断位置和速度,不过这个模块只有一路输出,正转反转输出波形没法区分,所以这种测试方法只能测位置和速度,不能测量旋转方向,为了进一步测量方向,我们就可以用后面的几种编码器
2.旋转编码器
.如下是我们接下来将要用过的旋转编码器,左边是外观,右边是内部拆解的结构;可以看到内部是用金属触电进行通断的,所以它是一种机械触电式编码器,左右是两部分开关触电;中间银色圆形金素片为一个按键,这个旋转编码器的轴是可以按下去的,这种编码器一般是用来进行调节的,比如音响调节音量,因为它是触电接触的形式,所以不适合电机这种高速旋转的地方,另外三种都是非接触的形式,可以用于电机测速(电机测速在电机驱动的应用中还是很常见的)
下面为详细讲解旋转编码器的硬件部分:
金属触点
内侧的两根细的触电都是和中间的引脚c连接的,外侧触电一个连接A,一个连接B。
圆形金属片(按键)的两根线,就在上面引出来了;按键的轴按下,上面两根线短路,松手,上面两根线断开,就是个普通的按键
这个旋转编码器的轴是可以按下去的;轴的外侧是白色的编码盘,它也是一系列光栅一样的东西,只不过这是金属触电,在旋转时,依次接通和断开两边的触电;这个金属盘的位置是经过设计的,它能让两侧触电的通断产生一个90度的相位差,最终配合一下外部电路,这个编码器的两个输出就会输出如下这样的正交波形,带正交波形输出的编码器是可以用来测方向的(这就是单相输出和两相正交输出的区别),当然还有的编码器不是输出正交波形,而是一个引脚输出方波信号代表转速,另一个输出高低电平代表旋转方向,这种不是正交输出的编码器也是可以测方向的。
当正转时,A相引脚输出一个方波波形,B相引脚输出一个和它相位相差90的波形(正交波形),如下
当反向旋转时,A相引脚还是方波信号,B相引脚会提前90度,如下
3..霍尔传感器形式编码器
霍尔传感器形式编码器,这种是直接附在电机后面的编码器,中间是一个圆形磁铁,边上有两个位置错开的活儿传感器,当磁铁旋转时,通过霍尔传感器就可以输出正交的方波信号,如下。
4.独立的编码器元件
.这是独立的编码器元件,它的输入轴转动时,输出就会波形,这个也是可以测速和测方向的,具体用法再看相应的手册。如下
旋转编码器的硬件电路
模块的电路图如下,图中正方形区域就是旋转编码器,上面按键的两根线这个模块没有使用,是悬空的
下面为模块电路细节介绍:
这里是编码器内部的两个触点,旋转轴旋转时,这两个触电以相位相差90度的方式交替导通,因为这只是个开关信号,所以要配合外围电路才能输出高低电平
左边接了一个10k的上拉电阻,默认没旋转的情况下,这个点被上拉为高电平,再通过R3这个电阻输出到A端口的就也是高电平,当旋转时,内部触电导通,那C端口处就直接被拉低到GND,再通过R3输出,A端口就是低电平了,之后这个R3是一个输出限流电阻(是为了防止模块引脚电流过大的);C1是输出滤波电容,可以防止一些输出信号抖动。剩下的右边电路和左边是雷同的。
使用这个模块时的接线如下,下面的A相输出和B相输出接到STM32的两个引脚上(主要引脚的尾数不能一样),中间的C引脚就是GND,我们暂时不用
4.实验
4.1 对射式红外传感器计数
接线图
程序源码:
设置GPIO EXTI外部中断的步骤如下
- 🌾RCC使能GPIO外设时钟,STM32必须先使能外设时钟后对应外设才能工作。
- 🌾RCC使能AFIO外设时钟,AFIO也是STM32的一种外设,所以要RCC使能AFIO外设时钟。
- 🌾EXTI和EVIC外设的时钟默认开启,不需要RCC使能EXTI和EVIC外设的时钟。
- 🌾GPIO外设初始化,配置GPIO的引脚,GPIO上拉/下拉,GPIO速度。
- 🌾AFIO外设设置外部中断线选择,STM32 GPIOA,GPIOB,GPIOC 的16个gpio引脚都能触发外部中断,但同时只能GPIOA/GPIOB/GPIOC中的一个 gpio_pin_x 引脚工作为中断模式。
- 🌾EVIC外设配置,EVIC是内核外设,是内核的小助手,EVIC根据中断的抢占优先级和排队优先级对中断进行仲裁,将最紧急的中断送给内核处理。
- 🌾实现EXTI中断处理函数,中断处理函数都在STM32的启动文件里 'startup_stm32f10x_md.s',中断函数的名字一般从该文件里复制出来不要写错。
- 🌾中断处理函数中需要清除外部中断标志位,如果不清除中断标志位就会一直触发中断。
Countersensor.c
cpp
#include "stm32f10x.h" // Device header
#include "Countersensor.h"
uint16_t Counter = 0;
void Countersenor_Init(void)
{
//使能APB2总线上GPIOB外设的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//不要忘记AFIO也是外设,也需要在RCC中启用AFIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//STM32, EXTI 外设不需要启用RCC使用
//STM32, NVIC是内核外设,RCC管理不到,RCC是管理外部设备的
//配置GPIOB_Pin12的工作模式
GPIO_InitTypeDef GPIOInitStruture;
GPIOInitStruture.GPIO_Mode = GPIO_Mode_IPU;
GPIOInitStruture.GPIO_Pin = GPIO_Pin_14;
GPIOInitStruture.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIOInitStruture);
//AFIO配置,EXTI外设中断源选择GPIO Pin
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
//EXTI配置,设置GPIO EXTI外设中断电平触发方式:上升沿/下降沿/双边沿
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line14;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
//NVIC配置,设置EXTI外设中断优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
}
uint16_t getCountersensor(void)
{
return Counter;
}
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line14) == SET){
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0){
Counter++;
}
//一定要清除中断标志位,如果不清除中断标志位,就会一直触发中断
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
Countersensor.h
cpp
#ifndef __COUNTER_SENSOR__
#define __COUNTER_SENSOR__
void Countersenor_Init(void);
uint16_t getCountersensor(void);
#endif
实验结果:
实验问题记录:
- 🌵1. 红外对射式传感器计数程序使用红外对射传感器 DO 引脚输出的电平下降沿触发STM32 外设中断,然后在中断处理函数里对计数器加一。
- 测试发现当每次使用纸片挡住红外对射传感器的时候,中断计数器可能会一次加多个数字,说明在挡住红外对射式传感器的时候出现了抖动从而一次动作触发了多次中断。
- 需要对红外对射式传感器进行消抖。哪怎么对红外对射式传感器进行消抖哪?在网上搜索了一下例子,也看了下江科大提供的示例程序里面通过再次读取外部中断引脚的电平来进行消抖(实际测试效果不好)。
红外对射式传感器计数中断消抖:
参考这里的程序
4.2 旋转编码器计数
旋转编码器计数程序,可以使用旋转编码器获取旋转的方向和旋转的速度。
程序源码:
Encoder.c
cpp
#include "stm32f10x.h" // Device header
#include "Encoder.h"
void Encoder_Init(void)
{
//RCC开启外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //GPIOB外设RCC时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //AFIO外设RCC时钟使能
//GPIO配置
GPIO_InitTypeDef GPIOInitStructure;
GPIOInitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIOInitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14;
GPIOInitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIOInitStructure);
//AFIO中断线选择
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource13);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
//EXTI中断触发模式
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line13 | EXTI_Line14;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStruct);
//NVIC中断优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
}
int16_t EncoderCount = 0;
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line13) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 1)
{
EncoderCount++;
}
EXTI_ClearITPendingBit(EXTI_Line13);
}
if(EXTI_GetITStatus(EXTI_Line14) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 1)
{
EncoderCount--;
}
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
int16_t Encoder_GetCount(void)
{
int16_t temp;
temp = EncoderCount;
return temp;
}
Encoder.h
cpp
#ifndef __ENCODER_H__
#define __ENCODER_H__
void Encoder_Init(void);
int16_t Encoder_GetCount(void);
#endif
Main.c
cpp
#include "stm32f10x.h" // Device header
#include "oled.h"
#include "Countersensor.h"
#include "Encoder.h"
int main(int argc, char *argv[])
{
OLED_Init();
OLED_ShowString(1, 1, "Counter:");
Encoder_Init();
int16_t Counter;
while(1)
{
Counter = Encoder_GetCount();
OLED_ShowSignedNum(1,9,Counter,5);
}
return 1;
}
实验结果
编码器正转
编码器反转
5.中断编程建议
中断编程的建议
- 中断中不能做耗时的操作,例如在中断中执行Delay函数,中断要快进快出,可以在中断里设置标记Flag值的方式来处理。
- 最好不要在中断函数和主函数里调用同一个硬件处理函数,例如在主程序里执行OLED显示函数,此时中断来了进入中断执行OLED显示函数OLED显示之后OLED的(光标)位置就挪到其他地方了,这没有问题;但是当从中断函数返回之后继续执行主程序里的OLED显示函数,此时OLED上显示的位置已经被中断里的OLED显示函数更新过了,这样就会造成OLED显示异常。