GPIO通常都会具有中断功能,上一讲的GPIO中并没有涉及到中断的相关寄存器。
51822将GPIO的中断相关做成了一个单独的模块GPIOTE,这个模块不仅提供了GPIO的中断功能,同时提供了 通过task和event的方式来访问GPIO的功能。其实中断功能也是通过 event来使能的,即中断是通过设置寄存器中相关位来决定 当event发生时是否发生中断 来设置中断功能的,具体下面介绍寄存器就明白了。
(task和event的引入主要是为了和51822中的PPI(可编程外围设备互联系统)模块的配合使用,举个简单的例子,ppi模块可以将event和task分别绑定在它的两端,当event发生时,taks就会自动触发,具体的细节 参见 PPI教程)
本将主要介绍GPIO的中断功能,所以只牵涉到GPIOTE中的event,(上面说过中断是通过设置 event发生时触发中断 来使用中断功能的)。
GPIOTE模块主要提供的了 4 个通道,这四个通道可以通过单独设置分别和普通的 GPIO 绑定,我们需要使用GPIO的中断功能可以设置相关寄存器的相关位让某个通道作为event,以及配置触发event的动作,比如绑定的引脚有上升沿跳变或者下降沿跳变触发event , 然后配置中断使能寄存器,配置让其event产生时是触发中断。 这样就实现了我们需要的 GPIO的中断方式。
初学者可能对这个event无法理解,event理解不就是事件么?设置的相应动作发生了会产生事件这不就是中断么?为什么又搞了一个 事件发生了才触发中断。
比如设置上升沿产生中断:
通常的 中断的都是 : 引脚上升沿->触发中断
51822却是: 引脚上升沿->产生eventà产生中断。
为什么中间多了个event? 就像上面说的主要是因为GPIOTE除了可以简单的作为设置GPIO来产生中断外还可以和PPI来配合作出更灵活的应用。所以才会有了这个event。PPI教程中会对这个event和task做更详细说明。 目前需要知道 中断的产生是需要配置相关寄存器中的位 来使能event发生时触发中断 才能实现GPIO的中断功能。
GPIOTE的四个通道都是通过CONFIG[0]-CONFIG[3]来配置的。其对应event0-event4以及task0-task4。来看下该寄存器中具体的配置位。
MODE功能位:该位用来配置本GPIOTE通道是作为event还是task的,这里我们用的是作为event,作为task的例子会在PPI教程中见到
PSEL功能位:用来设置本GPIOTE通道与哪个引脚绑定。
POLARITY功能位:用来设置作为event和task时的相应动作。如果作为前面的mode设置为event即相应动作发生时产生event, 如果上面的mode设置为task即当task被触发时就会执行该动作。
OUTINIT功能位:当上面的Mode配置为task时,这里设置的是引脚的初始值,当上面的mode配置为event时,设置为影响
比如我的板子上 按钮连再引脚21上,按钮按下时会产生低电平,为按下时为高电平。现在我想在按钮按下时(下降沿)产生中断。我使用GPIOTE的通道0,那么我就可以如下配置 CONFIG[0],让其在我按键按下时产生event0。
CONFIG[0] = (1 << 0 ) //作为event
| (21 << 8) //与PIN21绑定
|(2 << 16) //配置为下降沿产生event 0
上面说过 中断的产生不仅需要有event产生(event0-event4都是event,是对应的四个通道产生的event)。还需要设置 event发生时触发中断 才能真正产生我们需要的中断功能。
这个配置就是在INTEN 寄存器中配置,它也有两个更方便的配置寄存器INTENSET和INTENCLR。
按照上面的配置,我们按键按下后按钮后会产生event0,然后配置event0发生时产生中断
INTENSET = 0x01;
这样配置后 才会真正在按键按下后出发中断。
下面是完整的使用GPIOTE来实现一个按键中断的程序
首先建立一个工程,这里是 keil5.14 + sdk9.0 的开发环境
创建一个新的工程,选择自己板子的芯片型号,我的是51422
根据使用到的资源勾选相应的组件,这里就是用到了GPIO相关的部分,所以勾选必要的 core部分(包含了芯片的系统控制相关寄存器的定义)和startup(启动函数,处理一些底层设置然后跳转到我们自己写的Main函数)。 以及nrf_drivers下面的nrf_gpio部分就可以了。
然后创建自己的 gpiote_main.c文件并添加到工程中,并编写自己的main.c函数
Main.c文件
#include "nrf_gpio.h" #include "nrf51.h" //定义自己板子上的按键和led灯 #define BUTTON_PIN 17 #define LED_PIN 21 int main(void){ //讯联的板子上按钮上没有接上拉电阻,所以需要下面这两句来设置输入有 //上拉电阻,不然的话每次按键后几秒钟内再按键都会没反应,要等一会按 //才会有反应 nrf_gpio_pin_pull_t config = NRF_GPIO_PIN_PULLUP; nrf_gpio_cfg_input(BUTTON_PIN, config); //配置GPIOTE通道0作为event,”绑定”按键引脚,设置下降沿产生event NRF_GPIOTE->CONFIG[] = << |(BUTTON_PIN << ) |( << ); //配置LED输出 nrf_gpio_cfg_output(LED_PIN); //配置 event发生的时候产生中断, NRF_GPIOTE->INTENSET = 0X01; //配置GPIOTE中断优先级,并使能GPIOTE中断 NVIC_SetPriority(GPIOTE_IRQn, ); NVIC_ClearPendingIRQ(GPIOTE_IRQn); NVIC_EnableIRQ(GPIOTE_IRQn); ); ; } //中断处理函数: void GPIOTE_IRQHandler(void){ ] == ){ //首先清楚event,不然会导致中断退出有event任然存在导致一直 //触发中断 NRF_GPIOTE->EVENTS_IN[] = ; nrf_gpio_pin_toggle(LED_PIN); } }
上面虽然实现了GPIO的中断,但是我们是利用了GPIOTE,虽然能实现中断,但是GPIOTE只有四个通道,弊端就在这里。使用GPIOTE只能最多四个I/O中断。
所以51822提供了另外一种产生I/O中断的方式 即port event。这个event可以在所有 配置了可以产生DETECT signal 的引脚触发DETECT signal的时候产生。(DETECT signal在GPIO教程中有说明)
同样还是实现按键中断,通过port event方式产生中断的代码
main.c文件
#include "nrf_gpio.h" #include "nrf51.h" //定义自己板子上的按键和LED #define BUTTON1 17 #define BUTTON2 18 #define BUTTON3 19 #define BUTTON4 20 #define LED1 21 #define LED2 22 #define LED3 23 #define LED4 24 int main(void){ //配置上拉和 sense nrf_gpio_pin_pull_t config = NRF_GPIO_PIN_PULLUP; nrf_gpio_pin_sense_t sense = GPIO_PIN_CNF_SENSE_Low; //配置四个按键最为输入和上拉,并且设置低电平产生DETECT signal nrf_gpio_cfg_sense_input(BUTTON1,config, sense); nrf_gpio_cfg_sense_input(BUTTON2,config, sense); nrf_gpio_cfg_sense_input(BUTTON3,config, sense); nrf_gpio_cfg_sense_input(BUTTON4,config, sense); //配置LED引脚输出 nrf_gpio_cfg_output(LED1); nrf_gpio_cfg_output(LED2); nrf_gpio_cfg_output(LED3); nrf_gpio_cfg_output(LED4); //跟GPIOTE配置产生中断一样,这里也要配置port event产生时触发中断 NRF_GPIOTE->INTENSET = << ; NVIC_SetPriority(GPIOTE_IRQn, ); NVIC_ClearPendingIRQ(GPIOTE_IRQn); NVIC_EnableIRQ(GPIOTE_IRQn); ); ; } void GPIOTE_IRQHandler(void){ ){ //中断处理函数中要清楚event,不然会导致一直产生中断 NRF_GPIOTE->EVENTS_PORT = ; ){ nrf_gpio_pin_toggle(LED1); } ){ nrf_gpio_pin_toggle(LED2); } ){ nrf_gpio_pin_toggle(LED3); } ){ nrf_gpio_pin_toggle(LED4); } } }