对射式红外传感器计次&旋转编码器计次

DO数字输出端,随便选择一个GPIO口接上,这里接在了B14端口
当我们的挡光片或者编码盘在这个对射式红外传感器中间经过时,这个 DO 就会输出电平跳变的信号。然后这个电平跳变的信号触发 STM32PB14 号口的中断。我们在中断函数里执行变量加加的程序,然后主循环里调用 Oled 显示这个变量。这样第一个程序就完成了。



删掉其他代码:







要进行外部中断的配置了,那如何配置外部中断呢?我们可以看一下 PPT 的这个外部中断整体结构图,看一下这个图使用外部中断要配置哪些东西就很清晰了,我们只需要把这个外部中断从 GPIO 到 NVIC 这一路中出现的外设模块都配置好,把这条信号电路给打通就行了,具体步骤就是,第一步配置 RCC,把我们这里涉及的外设的时钟都打开,这个别忘了,不打开时钟外设是没法工作的。

第二步配置 GPIO,选选择我们的端口为输入模式。第三步配置 A F L,选择我们用的这一路 G P L 连接到后面的 E X T I 第四步配置 E X T I,选择边沿触发方式,比如上升沿、下降沿或者双边沿,还有选择触发响应方式,可以选择中断响应和事件响应,当然我们一般都是中断响应啊。第五步配置 NVIC,给我们这个中断选择一个合适的优先级,最后通过 NVIC 外部中断信号就能进入 CPU 了,这样 CPU 才能收到中断信号,才能跳转到中断函数里执行中断程序。那这五步就是外部中断的配置流程,这里涉及的外设比较多哈,有 RCC,GPIO,AFO,EXTI,NVIC,大家不要慌啊,我们一个个来看,

那我们回到 Keil,首先第一步使 RCC 开启时钟,这个我们之前讲过哈,主要就这是三个开启时钟的函数,那我们在这里直接写 RCC APB2 外设时钟控制,首先是 RCC APB2 外设,GPIOB Enable 开启 GPIOB 的时钟,这个注意一下哈,GPIOB 是 APB2 的外设,这个参数是 APB2,GPIOB 的这个函数也要用 APB2 的这个开启时钟函数
你如果这里用了 APB1 或者 AHB 的函数,然后填上 APB2 GPIOB 的参数,这样程序也是不会报错的哈,因为语法上没有错误,后面这个参数只是一个宏定义啊,只是一个数的替换而已,所以编译器也不知道写错了,但实际运行的话,你这个程序肯定是有问题的,所以这里要细心一点啊,注意函数和参数的这个 APP 二,APP 一和 AHB 要对应起来

那我们继续接下来开启 AFIO时钟,AFLIO也是 APP 2的外设,那我们复制一下这个函数啊,然后这里把 GPIOB 改成 AFIO,这样就行了,你如果不确定哪个外设是接在哪个总线上的,可以转到这个函数的定义啊,看一下上面这个参数列表,有的话就是这个总线上的外设,那这里可以看到有这个 RCC AHB2 外设 AFIO,所以这里就可以调用 AHB2 开启时钟的函数,然后填 AFIO的这个参数开启时钟了

接着还有 EXTI 和 NVIC 两个外设,这两个外设的时钟是一直都打开着的 ,不需要我们再开启时钟了,EXTI 作为一个独立外设,按理说应该是需要开启时钟的,但是计存器里面却没有 EXTI 时钟的控制位啊,这个原因手册里也没找到,个人推测可能是和 EXTI 唤醒有关,或者是其他的一些电路设计上的考虑哈,那暂时就不用管了,知道它不需要开启时钟就行了,另外 NVIC 也不需要开启时钟,因为NVIC 是内核的外设,内核的外设都是不需要开启时钟的啊,人家跟 CPU 一起啊,都是住在皇宫里的,而 RCC 管的都是内核外的外设,所以 RCC 管不着 NVIC。
好,拉到这里时钟就配置完了,接着我们就来进行第二步,配置 GPIO,这一部分我们之前也学过哈,也还是一样的流程:
首先定义结构体 GPIO_InitTypeDef GPIO_InitStructure,然后复制一下结构体名字,把结构体成员都引出来,最后 GPIO_Init GPIO B ,取地址 GPIO_InitStructure 初始化 GPIO B 外设,接着填下参数啊,第一个 GPIO Mode,对于外部中断来说,要选择浮空输入,上拉输入或者下拉输入这其中的一个模式

当然像这种其他外设使用 GPIO 的情况,如果你不清楚该配置为什么模式,可以看一下这个参考手册啊,在 GPIO 这一章找一下啊,有一个外设的 GPIO 配置表,里面有写每个外设的各个引脚都需要配置为什么模式,这个我们之前也看过:


那在最后就有EXTI 输入线,它给的推荐配置就是浮空上拉或者下拉,所以在这里我就给一个 GPIO Input IPU 上拉输入默认为高电平的输入方式,然后 GPIO pin,我们用的 Pin14 号口,所以就写 GPIO pin 14 GPIO speed,这个不是很重要啊,我们就还是 GPIO speed 50 兆赫兹,最后调用 GPIO_Init函数,初始化一下 GPIO B 外设,这样 GPIO 部分我们就配置好了

然后我们就来进行第三步,配置 AFO,这个 AFO 外设 ST 公司并没有给它分配专门的库函数文件啊,它的库函数是和 GPIO 在一个文件里的,那我们找一下 GPIO 的点 h 文件,看一下最后的库函数啊,我们之前学习 GPIO 的时候讲了这上面的一些函数啊,那还剩下一些函数没讲,这些就是和 AFO 有关的库函数,我们来看一下:

首先上面这里有一个GPIO_AFIODeInit,这个函数是用来复位 AFIO 外设的,调用一下这个函数,AFIO 外设的配置就会全部清除,这好理解啊
然后下面这个GPIO_PinLockConfig,这个函数是用来锁定 GPIO 配置的,调用这个函数参数指定某个引脚,那这个引脚的配置就会被锁定,防止意外更改,这个也是 GPIOB 的函数啊。
然后是 GPIO_EventOutputConfig 和GPIO_EventOutputCmd,这两个函数是用来配置 AFIO的事件输出功能的,用的也不多啊,大家了解一下
接下来 GPIO_PinRemapConfig 和 GPIO_EXTILineConfig,这两个函数就比较重要了,GPIO_PinRemapConfig 可以用来进行引脚重映射,第一个参数可以选择你要重映射的方式,第二个参数是新的状态,使用还是非常简单的啊,但是由于目前我们还没有学习到需要映射引脚的外设,所以实际调用的话我们之后再演示啊
然后GPIO_EXTILineConfig 就是我们本节外部中断需要用的函数,调用这个函数就可以配置 AFIO 的数据选择器来选择我们想要的中断引脚
那最后一个 GPIO_ETH_MediaInterfaceConfig,这个是和以太网有关的哈,我们这个芯片没有以太网外设,所以也用不到。
接着我们就回到这个主线任务来,我们现在想要配置 AFLIO外部中断引脚,选择,那就直接复制这个GPIO_EXTILineConfig函数放到这里来,然后右键跳到定义看一下,可以看到啊

这函数虽然是 GPIO 开头,但实际上里面操作的是 AFIO的计存器,所以这函数实际上是 AFIO 的函数,这个知道一下,那上面这里简介写的是,选择 GPIO pin 作为外部中断线,第一个参数是 GPIO port source,选择某个 GPIO 外设作为外部中断源,这个参数可以是 GPIO port source GPLX,其中 X 可以是 A 到 G,那我们就复制一下这个参数放到这里,我们用的是 PB14 号引脚,所以把 X 改成 B 就行了 ,然后再看一下第二个参数,第二个参数是 GPIO Pin Source 指定配置的外设中断线,这个参数可以是 GPIO Pin Source X,其中 X 可以是 015,那我们再复制一下这个参数放到第二个参数这里,然后 X 改成 14,代表连接 PB14 号口的第 14 个中断线路,到这里 AFIO 外设中断引脚选择配置就配置完成了

AFIO 外设中断引脚选择配置就完成了,就这一个函数就行了。当执行完这个函数后,AFIO 的第 14 个数据选择器就拨好了。其中输入端被拨到了 GPIOB 外设上,对应的就是 PB14 号引脚,输出端固定连接的是 EXTI 的第 14 个中断线路。这样 PB14 号引脚的电平信号就可以顺利通过 AFIO 进入后级 EXTI 电路了。

那接下来我们就可以进入第四步配置 EXTI 了。
第四步,配置 EXTI 了。我们先看一下 EXTI 的库函数文件啊,看一下 EXTI 都有哪些库函数可以用。我们找到 EXTI 点 h 的文件,打开,拖到最后。这些就是 EXTI 的所有库函数了。库函数总共就只有这些啊,看上去还是比较简单的,我们来一一学习一下。

首先第一个 EXTI_DeInit调用它就可以把 EXTI 的配置都清除,恢复成上电默认的状态。
然后第二个 EXTI_ Init 调用这个函数就可以根据这个结构体里的参数配置 EXTI 外设。我们初始化 EXTI TI 主要用的就是这个函数啊,使用方法和 GPIO _Init的也是一样的,这个应该也好理解。
接着第三个,EXTI_StructInit,调用这个函数可以把参数传递的结构体变量赋一个默认值。
像这前面的三个函数啊,基本所有的外设都有,就像指库函数的模板函数一样,基本每个外设都需要这些类型的函数,这些模板函数使用方法和意思也都是一样的哈。会使用一个之后,再见到这种函数就能很容易的上手。所以当你学 GPIO 的时候,你会觉得为啥要用结构体来初始化模块呢?还得定义结构体,结构体赋值,然后再传递结构体的地址,简直太麻烦了是吧?但当你继续学习其他外设之后,你就会发现外部中断也只使用结构体初始化的方式。定时器也是, ADC 也是,串口也是,都是一个套路啊。而且结构体可以看到参数的名字,参数也都是复制粘贴来的,根本不用查看寄存器,随便选选参数就配置好了。从这个角度看, STM32 的库函数是不是比寄存器方便多了哈?这就是库函数的好处。
那接着下面继续看啊,EXTI_GenerateSWInterrupt这个函数是用来软件触发外部中断的。调用这个函数,参数给一个指定的中断线,就能软件触发一次这个外部中断。这个如果你程序中需要用到这个功能的话,可以使用这个函数啊。如果你只需要外部引脚触发中断,那就不需要用这个函数了。

然后剩下的这四个函数也是库函数的模板函数啊,很多模块都有这四个函数,因为在外设运行的过程中会产生一些状态标志位,比如外部中断来了是不是会有一个挂起寄存器置了一个标志位啊?对于其他外设,比如串口收到数据啊会置标志位,定时器时间到啊也会置标志位,这些标志位都是放在状态寄存器的,当程序想要看这些标志位时,就可以用到这四个函数。
其中这前两个函数 EXTI_GetFlagStatus可以获取指定的标志位是否被置一了。EXTI_ClearFlag 可以对置一的标志位进行清除。那对于这些标志位啊,有的比较紧急,在置标志位后会触发中断。在中断函数里,如果你想查看标志位和清除标志位,那就用下面两个函数。
EXTI_GetITStatus 获取中断标志位是否被置一了。EXTI_ClearITPendingBit 清除中断挂起标志位。
所以总结一下就是,如果你想在主程序里查看和清除标志位,就用上面这两个函数。如果你想在中断函数里查看和清除标志位,就用下面这两个函数。其实本质上这四个函数都是对状态寄存器的读写哈。上面两个和下面两个都是类似的功能,都是读写状态寄存器。
只不过是下面这两个函数只能读写与中断有关的标志位,并且对中断是否允许做出了判断。而上面的这两个函数只是一般的读写标志位,没有额外的处理,能不能触发中断的标志位都能读取。所以建议在主程序里用上面两个,中断程序里用下面两个。当然你如果非要在中断里用上面两个,那其实也是没问题的哈,只不过是库函数针对这两种场景区分了这两类读写函数。到这里,这个 EXTI 的库函数就都看完了。对于 EXTI 的初始化配置,很明显啊,用这个EXTI_Init的函数就行了。那我们回到这个初始化程序里来,在这里直接先调一个 EXTI 一例的函数。

一个 EXTI_ Init 函数,里面只有一个参数啊,就是 EXTI 初始化的结构体。因为 EXTI 只有一个,所以不需要像 GPIO 那样先指定要配置哪个 EXTI 了。那我们右键跳转定义,看一下说明啊。函数简介写的是初始化 EXTI 外设,根据结构体里面指定的参数啊,第一个参数是 EXTI EXTI_ InitStruct 它是一个 EXTI_ InitTypeDef 类型的结构体指针,包含了对于 EXTI 外设的配置信息。

那我们复制一下结构体类型的名字,回到这里啊,粘贴一下类型名,然后起个变量名啊,叫 EXTI_ InitStruct。 接着复制变量名,在下面粘贴啊,先把所有的结构体成员都用点引出来,放在这里。最后把结构体变量放在 EXTI_ Init 的参数里,前面加上取地址的符号。

接着我们来依次看一下参数啊,第一个参数 EXTI_ Line, 这个是指定我们要配置的中断线,那我们跳转到定义。下面的这个选一下。

然后看一下右边的注释,这个参数可以是 EXTI Lines 的任意组合:

我们选中 EXTI Lines, Ctrl + F 搜索一下。这里可以看到啊,这些就是这个参数的取值。那我们需要用 PB14 所在的第 14 个线路,所以选择 EXTI_ Line_ 14:


复制,然后放到这里,第一个参数就 OK 了。接着第二个 EXTI_ Line_ Command, 跳到定义,看一下注释。这里写的是指定选择的中断线的新状态,这个参数可以是 enable 或 disable,那我们肯定要开启中断对吧?

所以复制 enable,放到这里,第二个参数就 OK 了。

接着第三个 EXTI Mode,跳到定义,看一下注释。这里写的是指定外部中断线的模式,这个参数可以是 EXTI_ Mode 的 TypeDef 里面的一个值,那还是一样啊:

选中, Ctrl + F 搜索一下。可以看到啊,这是一个枚举,第一个是中断模式,第二个是事件模式,那我们要用中断模式啊,所以复制上面这个参数。


放到这里,这样第三个参数也 OK 了。接着看一下第四个参数啊, EXTI Trigger, 跳到定义,看一下注释啊,这里写的是指定触发信号的有效边缘,这个参数可以是这个定义里的一个值。

那这个定义它这里写错了哈,这里应该是 EXTI Trigger TypeDef,而不是 EXTI Mode 才不离谱呢,他现在搞得和上面这个一样了是吧?这应该是个小疏忽啊。那我们往上稍微找一下,可以找到这个 EXTI_ Trigger_ TypeDef 的定义。

Rising 上升沿触发, Falling 下降沿触发, Rising Falling 上升沿和下降沿都触发。这个选择根据你的实际需求来啊,我就选择 Falling 下降沿触发了

那放到这里,这样我们的外部中断就配置完成了。
我们当前的配置是将 EXTI 的第 14 个线路配置为中断模式,下降沿触发,然后开启中断。这样 PB14 的电平信号就能通过 EXTI 通向下一级 NVIC 了。那最后我们就来执行第五步,配置 NVIC
我们还是先看一下库函数文件里的函数啊。因为 NVIC 是内核外设,所以它的库函数是被 ST 发配到杂项这里来的。我们打开 misc 点 h, 拖到最后啊。这里有 NVIC 的 4 个函数和 systick 的一个函数。我们来看一下

第一个 NVIC_PriorityGroupConfig, 这个函数是用来中断分组的,参数是中断分组的方式。
然后第二个 NVIC_Init, 根据结构体里面指定的参数初始化 NVIC。
然后下面这这个 NVIC_SetVectorTable设置中断向量表,和 NVIC_SystemLPConfig, 系统低功耗配置。这两个函数用的不多啊,大家可以先不看。这样这个 NVIC 的库函数就看完了,只需要用上面的两个函数就行了。在配置中断之前,先指定一下中断的分组。然后使用 NVIC_ Init 初始化一下 NVIC 就行了。
那我们复制一下 NVIC_ PriorityGroupConfig 这个函数,回到配置这里来,放在这里。

然后右键跳转到定义,看一下上面啊,这个简介是配置优先级分组,先占优先级和从占优先级。这里先占优先级就是抢占优先级啊,从占优先级就是响应优先级。然后下面这个参数可以取这个列表里的一个值,每个参数的解释这里可以看一下几位抢占几位响应,和我上节讲的都是一样的啊。这个具体要选哪个,其实是根据我们的实际需求来的。一般的话中断不多,很难导致中断冲突,对优先级分组来来说就比较随意了啊,哪个都行。那这里我就选择第二个分组了,两位抢占,两位响应,这个比较平均一下。

我们复制一下这个参数,然后放到这里来。这样分组就完成了。另外注意一下,这个分组方式整个芯片只能用一种。所以按理说这个分组的代码整个工程只需要执行一次就行了。如果你把它放在模块里面进行分组,那你要确保每个模块分组都选的是同一个啊。要不你也可以把这个代码放在主函数的最开始,这样模块里就不用再进行分组了。那我这里就直接放在模块里进行分这样分组就完成了。

接下来直接调用 NVIC_ Enable 的函数,右键跳转一下定义。


直接复制 NVIC_InitTypeDef,结构体,放到这里。然后结构体变量名叫NVIC_InitStructure。接着复制变量名啊,把成员都引出来。最后把结构体变量的地址放在 NVIC_ Enable 的函数里。然后依次看一下参数:

第一个 IRQ_ Channel 跳转定义,看一下解释啊,指定中断通道来开启或关闭。这个参数可以是 IRQn_ Type 里面的一个值。下面还有个括号啊,写的是对于完整的 STM32 中断通道列表,请参考 STM32F10x.h 文件。这个意思是这个 IRQN type 的定义不在这个文件哈,你要到 STM32F10X 点 H 里面去找。那我们还是选择这个 IRQN type,Ctrl + F 搜索一下。如果直接在这里搜索啊是搜索不到的。那我们把下面的搜索范围由当前文件换成当前工程,然后再搜索。

可以看到啊,现在是跳到了 STM32F10X 点 H 文件里,这个定义就在这里啊,我们可以在这上面的这个列表选择啊,可以看到这里有非常多的中断通道。因为这个库函数可以兼容所有的 F1 系列芯片,但是不同的芯片中断通道列表是不一样的,所以这里有很多条件编译啊,用来选择你使用芯片的中断通道列表。那我们可以点一下这左边的减号,把所有的条件编译都折叠起来。然后我们芯片用的是 MD 中等密度的,所以只需要展开这个 MD 的条件编译即可:

其他的就不用看了。那在这个表里我们就可以找到这个, EXTI15_10_ IRQn, 32 的 EXTI10 到 EXTI15 都是合并到了这个通道里啊。所以我们复制这一个,然后放到这里。

这样通道就指定好了。接着, IRQ Channel Command 跳转下定义,右边的解释是指定中断通道是使能还是失能,参数可以是 Enable 或者 Disable,我们选择 Enable,放在这里。


接着下面两个参数就是指定所选通道的抢占优先级和响应优先级了。我们跳到定义,看一下这两个参数的解释啊。上面的是指定抢占优先级,下面的是指定响应优先级。这个值可以是 0~15 哈,具体的值可以参考这个表里的描述。

那我们选中这个表,搜索一下。可以看到啊,这里给出了每个分组对应抢占优先级和响应优先级的取值范围。这个表和我们 PPT 里的那个表是一样的啊,我们选择了分组 2,那抢占优先级和响应优先级的取值范围就都是 0~3。

那我们回到这里啊,因为我们这个程序的中断只有一个,所以中断优先级的配置也是非常随意的哈,那我这里就给抢占优先级和响应优先级都设置为一啊。

优先级是在多个中断源同时申请产生拥挤时才有作用啊,这只有一个中断,优先级就随便了。那到这里, NVIC 就配置好了,整个外部中断的配置也就结束了。
外部中断的信号从 GPIO 到 AFIO 再到 EXTI 再到 NVIC 最终通向 CPU 啊,这样才能让 CPU 由主程序跳到中断程序执行。那中断程序应该放在哪里呢?这就需要我们写一个中断函数了。在 STM32 中,中断函数的名字都是固定的,每个中断通道都对应一个中断函数。中断函数的名字我们可以参考一下启动文件哈,那我们找到启动文件,打开看一下。

在这里找一下,可以看到这里定义的中断向量表。这里面以 IRQHandler 结尾的字符串就是中断函数的名字。我们可以找到这个 EXTI15_10_ IRQHandler 这一项,这就是 EXTI15~10 的中断函数,我们复制一下,回到这里啊
中断函数都是无参无返回值的啊,中断函数的名字不要写错了,写错了就进不了中断啊,最好是直接从启动文件复制过来,这样就不会有问题了,然后在中断函数里一般都是先进行一个中断标志位的判断啊,确保是我们想要的中断源触发的这个函数,因为这个函数 EXTI10 到 EXTI15 都能进来,所以要先判断一下是不是我们想要的 EXTI14 进来的,

这时候我们就需要到 EXTI.h去看一下,复制一下这个 EXTI GetITStatus 放到这里来看一下参数啊,第一个参数复制一下这个 EXTI LineX 放到这里,然后 X 改成 14

看一下 EXTI14 的中断标志位是不是为一,然后再看一下返回值是 set 或 reset


那我们就 if 把这函数括起来,判断一下返回值是不是等于等于 set,如果是的话我们就可以执行中断程序了,最后中断程序结束后,一定要再调用一下清除中断标志位的函数,因为只要中断标志位置一了,程序就会跳到中断函数,如果你不清除中断标志位,那它就会一直申请中断,这样程序就会不断响应中断,执行中断函数,那程序就卡死在中断函数里了,所以我们每次中断程序结束后都应该清除一下中断标志位,我们复制一下EXTI_ClearITPendingBit放到这里,

参数也是 EXTI Line 14,将中断标志位清除,这样中断的全部逻辑我们就写好了

接下来我们就来测试一下能不能进入中断函数。我们复制一下这个初始化函数的第一行啊,放在头文件里声明一下。这个中断函数就不用声明了,因为中断函数不需要调用,它是自动执行的,那我们编译一下。

main.c

我们使用一下这个调试模式,看一下程序能不能跳转到中断函数。那我们点击这个调试模式的按钮,进入调试模式。然后找一下中断函数啊,在这里打一个断点

然后全速运行,看一下硬件。我们用挡光片遮挡一下这个传感器,这时我们看到程序成功跳转到中断函数里来了,停在了这个断点处。

那我们继续全速运行。再挡一下传感器,可以看到程序又停到中断里来了,这说明我们中断函数的配置和触发都是没问题的。那接下来的事情就简单了,我们退出调试模式啊,我们想要一个数字来统计中断触发的次数,那么就在这个模块上定一个变量啊:
类型可以是 uint 16_ t, 名字可以是 counter sensor 杠 count。

然后在中断函数里写 counter sensor 杠 count 加加,这样就行了。

最后再在这个模块写一个 get 函数,返回一下这个变量。可以在这里写啊, uint 16_ t counter sensor 杠 get。 void 然后里面直接写 return counter sensor 杠 count 这样就行了。

然后我们把这个函数也放到头文件声明一下。

给你看一下,没有问题。然后在 main 函数里先改一下这个OLED_ShowString啊,让它在一行一列显示一个 count。 冒号。在主循环里我们循环调用 OLED_ ShowNumber, 在一行七列显示 counter_ sensor_ get 的返回值,长度为 5。这样就是显示计数的数据了

我们试一下哈。



这就是第一个程序的现象了,我们现在用的是下降沿触发哈,在移开挡光片的时候触发中断。可以改下这里,变为上升沿触发,再试一下。现在就是遮挡的时候数字加一。


这里改成上升延下降沿都触发


旋转编码器计次:

这里右边还是 OLED 的电路,左边这里插了一个旋转编码器模块。上面 VCC 和 GND 接正负极,下面这两个 A B 相的输出引脚分别接到 STM32 的 PB0 和 PB1 两个引脚,这就是硬件电路,我们在面包板上接一下哈。








接着还是写一个初始化函数啊。 void encoder_ Init void 在这里面我们初始化一下 PB0 和 PB1 两个 GPIO 口的外部中断。当然这里只初始化一个外部中断其实也是可以完成功能的,因为对于这个编码器而言,正向旋转时, A,B 相输出的是这样的波形啊,反向旋转时输出的是这样的波形。如果把一项的下降沿用作触发中断,在中断时刻读取另一项的电平。那你看正转就是高电平,反转就是低电平,这样就能区分旋转方向了。

只不过这样在操作上有一些小瑕疵啊,比如你正转的时候,由于 A 项先出现下降沿,所以你刚开始动就进中断了。而反转是 A 项后出现下降沿,所以就是你转到位了才进入中断。这样实际上也没问题啊,就是有点不爽。
所以我准备的就是 A B 项都触发中断,只有在 B 项下下降沿和 A相低电平时,才判断为正转。在 A 项下降沿和 B 项低电平时,才判断为反转。

这样就能保证正转反转都是转到位了才执行数字加 1 的操作。同时这样也可以给大家演示一下两个中断的初始化代码。那我们开始写代码啊,我们先把上个代码的初始化部分复制过来,我们打开上个工程。然后把这些初始化代码复制一下,再回来啊。粘贴放在这里,然后改一下,

首先初始化时钟, GPIOB 和 AFIO 这也不用改了。接着初始化 GPIO 我们用的是 PB0 和 PB1,所以这里改一下啊,变成 GPIO_ Pin0 或上 GPIO_ Pin1。最后 GPIO 一例的 GPIO B 没问题。然后是 AFIO 中断引脚选择,我们把这个 14 改成 0,将第 0 个线路拨到 GPIO B 上,然后复制粘贴下这个代码,再将第一个线路拨到 GPIO B 上,这样 AFIO 就完成了。

然后是 EXTI 部分

我们把这个指定的中断线改成 EXTI Line 0 或上 EXTI Line 1。这样就能同时把第 0 条线路和第 1 条线路都初始化为中断模式下降沿触发了。

接着中断分组不用改的,
接着下面中断优先级,这个要对两个通道分别设置优先级啊,我们复制一下下面的这部分代码。定义结构体变量的就不用复制了,这个变量可以重复使用。

然后改一下,上面的改成 EXTI0 IRQn, 优先级抢占和响应都还是 1 吧。下面的改成 EXTI1 IRQn, 就把这个响应优先级改低一些,设置为 2 吧。好,这样同时初始化 EXTI 0 和 EXTI 1 的代码就改好了。

然后我们把这两个中断的中断函数都写一下,我们打开启动文件。

看到这里有这两个中断的中断函数啊,这里 EXTI0 和 EXTI1 是分别独占一个函数的,所以先复制一下这个 EXTI0 IRQHandler 回到这里啊
写上 void EXTI0 IRQHandler void 在中断里我们也固定的检查一下中断标志位啊,虽然这个函数只有 EXTI0 可以触发,但是作为固定模式的代码我们还是写一下吧,在这里就写 if EXTI Get ITStatus EXTI Line0 是不是等于等于 SET,如果是的话执行中断程序,最后 EXTI_ClearITPendingBit EXTI_Line0清除中断标志位然后复制一下这个函数,改一下函数名,这里是 EXTI1 IRQHandler 里面判断标志位改成 EXTI 那 1,清除标志位也是 EXTI 那 1,这样中断就完成了

这里这两个中断是进的两个函数啊,如果你使用的是 9-5 和 15-10 的这些中断,那只能写一个中断函数,这样就只需要把这两个 if 并列的放在一个函数里就行了,那然后我们就可以写我们实现功能的代码了,我们先在最上面定义一个变量,因为这里需要正反转,所以就定义一个带符号的变量吧,int 16 杠 t 起个名叫 encoder count

接着在中断函数里 EXTI0 里我们先判断一下另一个引脚的电平,if GPIO ReadInputDataBit GPIOB GPIO Pin 0 是不是等于等于 0,如果是那就是反转,就 encoder 杠 count 减减,

当然这个是正转还是反转你可以自己定义啊,正和反都是相对的,然后在 EXTI1 里我们就 if GPIO ReadInputDataBit GPIOB GPIO Pin 0 是不是等于等于 0,如果是,那就是正转,就 encoder 杠 count 加加,这样中断对这个变量的加减就完成了。

我们先编译一下,没有问题。然后我们需要再定义一个函数,把这个变量返回回去,所以在这个位置写上 int 16 杠 t Encoder Get void。在这里面我就不直接返回 Encoder Count 这个变量了,我打算返回每次调用这个 Get 函数之后,返回 Count 的变化值,用于外部加减一个变量,所以在这里我需要返回 Count,然后把 Count 清 0。那因为返回 Count 之后函数就结束了,没法清 0 了,所以我先定义一个离子面料碳布,把 count 先赋值给 temp,再把 count 清零,最后返回 temp,这样就完成了。

然后我们把这个函数放到头文件声明一下。



给大家提几个中断编程的建议哈。第一个就是在这个中断函数里最好不要执行耗时过长的代码。中断函数要简短快速,别刚进中断就执行一个 delay 多少毫秒这样的代码。因为中断是处理突发的事情,如果你为了处理一个突发的事情待在中断里不出来了,那主程序就会受到严重的阻塞。
另外就是最好不要在中断函数和主函数调用相同的函数,或者操作同一个硬件,尤其是硬件相关的函数,比如 OLED 显示函数。如果你既在主函数里调用 OLED 又在中断里调用 OLED OLED 就会显示错误啊。为什么呢?因为你想在主程序里 OLED 刚显示一半,它进中断了,结果中断里还是 OLED 显示函数,那 OLED 就挪到其他地方显示了。这时还没有问题啊,但当中断结束之后,需要继续原来的显显示,这时就出问题了,因为硬件的显示位置被挪到其他地方了,所以再回来的时候,继续显示的内容就会跟着跑到其他地方去,这就会造成问题。虽然在中断进入和退出的时候会有保护现场和恢复现场,但这只能保证 CPU 程序能正常返回不出问题,对于外部硬件的话,并没有在进入中断时进行现场保护,所以中断返回后就出问题了。
那为了避免这样的问题,就最好不要在主程序和中断程序里操作可能产生冲突的硬件,在实现功能的时候,别像我这样在中断里操作变量或者标志位,当中断返回时,我再对这个变量进行显示和操作。这样既能保证中断函数的简短快速,又能保证不产生冲突的硬件操作。这就是中断程序设计的注意事项啊。那在其他地方大家也都可以多用用变量或者标志位啊,来减少代码之间的耦合性,让各部分代码相互独立,仅使用变量标志位或者函数作为接口,这样能让程序更加清晰啊,代码更加强健。好那本节外部中断的部分到这里就结束了。