赞
踩
GPIO 任务和时间(GPIOTE)模块提供了使用任务和事件访问 GPIO 引脚的功能。每个 GPIOTE 通道被分配到一个引脚,GPIOTE 其实就是对 GPIO 口进行操作,同时引入了外部中断的概念。
比如按键控制分为两种情况:
在 nRF52832 内部普通的 IO 管脚设置成 GPIO,中断和任务管脚设置成 GPIOTE。
nRF5x 系列处理器将 GPIO 的中断的快速触发做成了一个单独的模块 GPIOTE,这个模块不仅提供了 GPIO 的中断功能,同时提供了通过 task 和 event 方式来访问 GPIO 的功能。GPIOTE 的后缀 T 即为 task,E 为 event。
Event 称为事件,来源与 GPIO 的输入、定时器的匹配中断等可以产生中断的外设来触发。Task 称为任务,就是执行某一特定功能,比如翻转 IO 端口等。那么事件 event 触发任务 task。task 和 event 的组合是为了和 52832 中的 PPI(可编程外围设备互联系统)模块的配合使用。这种机制不需要 CPU 参与,极大的减少了内核消耗,降低功率,特别适合于 BLE 低功耗蓝牙进行应用。
GPIOTE 实际上分为两种模式:
GPIOTE task 任务模式,每个 GPIOTE 通道最多可以使用三个任务来执行引脚的写操作。
置位
、清零
、切换
。GPIOTE event 事件模式,可以从以下输入条件之一在每个 GPIOTE 通道中生成事件:
任务模式有三种状态:置位、清零、翻转;事件模式有三种触发状态:上升沿触发、下降沿触发、任意变化触发。TASJK 任务通过通道 OUT[0]-OUT[7] 设置输出触发状态,Event 则可以通过检测信号产生 PORT event 事件,产生 IN[n] event 事件。
整个 GPIOTE 寄存器的个数非常少。GPIOTE 模块提供了 8 个通道,这 8 个通道都是通过 CONFIG[0]~CONFIG[7] 寄存器来配置。八个通道可以通过单独设置来分别和普通的 GPIO 绑定。当需要使用 GPIOTE 的中断功能时可以设置相应寄存器的相关位,让某个通道作为 event 事件模式,同时配置触发 event 动作。比如绑定的引脚有上升沿跳变或者下降沿跳变触发 event,然后配置中断使能寄存器,配置让其 event 产生时触发输入中断。这样实现了 GPIO 的中断方式。
任务状态 | 优先级 |
---|---|
TASKS_OUT | 1 |
TASKS_CLR | 2 |
TASKS_SET | 3 |
INTENSET 寄存器
INTENCLR 寄存器
修改前面 GPIO 应用按键应用改为中断控制方式。中断控制的效率很高,一旦系统 IO 口出现上升沿或者下降沿电平就会触发执行中断内的处理程序。这样可以大大节省 CPU 的占用。中断在计算机多任务处理,尤其是实时系统重尤为有用,这样的系统包括运行在骑上的操作系统,也成为“中断驱动”。
硬件方面,和前面讲述 GPIO 输入扫描哪里的按键输入应用一样。四个按键分别连接 P0.13、 P0.14、 P0.15、 P0.16。
在使用 nRF52832 完成中断时,当 IO 管脚为低的时候可以判断管脚已经按下。通过 key 的中断来控制 led 的亮灭。硬件上设计是比较简单的,这和普通的 MCU 的中断用法一致。如下创建工程。
这样我们需要编写 exit.c 文件,主要有两部分:初始化开发板上的按键中断;编写中断执行代码。
按键中断这里实际上使用了事件模式。在 CONFIG 寄存器里进行了事件模式的配置。代码如下:
NRF_GPIOTE->CONFIG[0] = (GPIOTE_CONFIG)_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos) //输入事件极性
| (13 << GPIOTE_CONFIG_PSEL_Pos)//绑定的引脚
| (GPIOTE_COFNIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);//模式配置
上面的代码严格按照寄存器要求进行,首先是 MODE 模式设置,配置 GPIOTE 通道作为 event 还是 task 使用,这里设置成 event 事件。PSEL 设置对应绑定的 IO 管脚,选择 P0.13 作为触发管脚,POLARIY 极性设置为下降沿触发。
设置好了工作方式后,我们就需要进行中断的使能了,因为前面绑定的是 GPIOTE 的 0 通道,因此中断使能代码如下:
NVIC_EnableIRQ(GPIOTE_IRQn);//中断嵌套是能
NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN0_Set <<
GPIOTE_INTENSET_IN0_Pos;//是能中断通道 IN0
上面任务基本上就可以把 GPIOTE 管脚中断配置好了,如果搞清楚这些寄存器配置很简单。中断函数的设计这里主要讲 LED 灯状态翻转,当然也可以加入对应的其他处理操作。
void GPIOTE_IRQHander()
{
if((NRF_GPIOTE->EVENTS_IN[0] == 1) &&
(NRF_GPIOTE->INTENSET & GPIOTE_INTENSET_IN0_Msk))
{
Delay(10000);//延迟消抖
NRF_GPIOTE->EVENTS_IN[0] = 0;//中断事件清零
}
LED_Toggle();//led 灯状态翻转
}
完成 exit.c 的编写后,main 主函数的操作就很简单,直接调用写好的驱动函数,然后尝试按键是否有对应的响应即可。
#include "nrf52.h"
#include "nrf_gpio.h"
#include "exit.h"
#include "led.h"
int mian()
{
LED_Init();
LED_Open();
EXIT_KEY_Init();
while(1)
{
//这里暂时控制,主操作死循环
}
return 0;
}
通过使用 nrf 官方库函数可以更加方便完成整个过程的操作,对于寄存器操作我们仅仅只需要了解原理即可,如果非要使用寄存器去完成开发对日后维护和使用有很大弊端,所以官方已经将这部分进行了完美的封装。我们根据官方 SDK 的封装接口进行调用完成对应操作即可。
添加了 SDK 库函数的工程和之前也有所不同,这里添加了 nRF_Libraries
库函数路径。
官方也提供了一个驱动 nrfx_gpiote 的 GPIOTE 驱动库,但是这个驱动库带有错误跟踪函数,所以工程中必须添加错误跟踪库。同时区别于寄存器编程,组件库还需要配置 sdk_config.h 配置文件。
打开 sdk_config.h 配置文件,打开配置向导 Configuration Wizard
,勾选以下两个使能项目,
函数 nrf_drv_gpiote_init
等同于函数 nrfx_gpiote_init
,
函数 nrf_drv_gpiote_in_init
等同于函数 nrfx_gpiote_in_init
,
nrf_drv_gpiote_in_event_enable
等同于函数 nrfx_gpiote_in_event_enable
介绍完了前面的库函数接口。由于驱动组件库 SDK 已经编写好了,我们只需要编写 main.c 的主函数调用即可。
#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "app_error.h"
#include "boards.h"
#ifdef BSP_BUTTON_0
#define PIN_IN BSP_BUTTON_0
#endif
#ifndef PIN_IN
#error "Please indicate input pin"
#endif
#ifdef BSP_LED_0
#define PIN_OUT BSP_LED_0
#endif
#ifndef PIN_OUT
#error "Please indicate output pin"
#endif
/**
GPIOTE中断处理
*/
void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
if(nrf_gpio_pin_read(PIN_IN)== 0)//按键防抖
{
nrf_gpio_pin_toggle(PIN_OUT);
}
}
/**
配置GPIOTE初始化
*/
static void gpio_init(void)
{
nrf_gpio_cfg_output(PIN_OUT);//led灯的输出
ret_code_t err_code;
err_code = nrf_drv_gpiote_init();//初始化GPIOTE
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
in_config.pull = NRF_GPIO_PIN_PULLUP;
//设置GPIOTE输入,极性,模式
err_code = nrf_drv_gpiote_in_init(PIN_IN, &in_config, in_pin_handler);
APP_ERROR_CHECK(err_code);
//使能GPIOTE
nrf_drv_gpiote_in_event_enable(PIN_IN, true);
}
/**
主函数,循环等待中断
*/
int main(void)
{
gpio_init();
while (true)
{
// Do nothing.
}
}
把普通的 GPIO 端口配置为 GPIOTE 中断输入事件,能够绑定的只有 8 个通道,如果我们中断的数据量超过了 8 个,多的中断无法处理,如何出现这种情况,怎么处理?显然芯片设计厂家为了针对这种情况,特别在 GPIOTE 模块中提出了 GPIOTE PORT 功能。
GPIOTE PORT 是从使用 GPIO DETECT 信号的多个 IO 输入引脚来生成的事件。该事件将在 DETECT 信号的上升沿而产生。也就是说这个功能可以通过 32 个 IO 端口来产生,相当于一个总通道,32 个 IO 端口共用这个通道来申请中断。
同时 GPIO DETECT 信号就是通过 GPIO 的 SENSE 寄存器打开,此功能始终处于启用状态。即便外围设备本身是休眠状态,也不需要请求时钟或其他功率密集型基础架构来启用此功能。因此此功能可用于在系统启动时从 WFI 或 WFE 类型的睡眠时,来唤醒 CPU、所有外设和 CPU 空闲。达到唤醒系统启动模式下的最低功耗模式。
为了在配置源时防止来自 PORT 事件的虚假中断,用户应首先禁用 PORT 事件中的中断(通过 INTENCLR.PORT),然后配置源(PIN_CNF[n].SENSE),清除配置期间可能发生的任何潜在事件(向EVENTS_PORT写入“1”),最后启用中断(通过INTENSET.PORT)。
采用组件库编写 GPIOTE 输入事件与 GPIOTE PORT 事件的主要区别:
#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "app_error.h"
#include "boards.h"
/**
GPIOTE中断处理
*/
void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
//事件由按键S1产生,即按键S1按下
if(pin == BUTTON_1)
{
//翻转指示灯D1的状态
nrf_gpio_pin_toggle(LED_1);
}
//事件由按键S2产生,即按键S2按下
else if(pin == BUTTON_2)
{
//翻转指示灯D2的状态
nrf_gpio_pin_toggle(LED_2);
}
//事件由按键S3产生,即按键S3按下
else if(pin == BUTTON_3)
{
//翻转指示灯D3的状态
nrf_gpio_pin_toggle(LED_3);
}
//事件由按键S4产生,即按键S4按下
else if(pin == BUTTON_4)
{
//翻转指示灯D4的状态
nrf_gpio_pin_toggle(LED_4);
}
}
/**
配置GPIOTE初始化
*/
static void gpio_init(void)
{ //配置LED灯输出
nrf_gpio_cfg_output(LED_1);
nrf_gpio_cfg_output(LED_2);
nrf_gpio_cfg_output(LED_3);
nrf_gpio_cfg_output(LED_4);
ret_code_t err_code;
//初始化GPIOTE
err_code = nrf_drv_gpiote_init();
APP_ERROR_CHECK(err_code);
//配置SENSE模式,选择fales为sense配置
nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(false);
in_config.pull = NRF_GPIO_PIN_PULLUP;
//配置按键0绑定POTR
err_code = nrf_drv_gpiote_in_init(BSP_BUTTON_0, &in_config, in_pin_handler);
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_in_event_enable(BSP_BUTTON_0, true);
//配置按键1绑定POTR
err_code = nrf_drv_gpiote_in_init(BSP_BUTTON_1, &in_config, in_pin_handler);
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_in_event_enable(BSP_BUTTON_1, true);
//配置按键2绑定POTR
err_code = nrf_drv_gpiote_in_init(BSP_BUTTON_2, &in_config, in_pin_handler);
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_in_event_enable(BSP_BUTTON_2, true);
//配置按键3绑定POTR
err_code = nrf_drv_gpiote_in_init(BSP_BUTTON_3, &in_config, in_pin_handler);
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_in_event_enable(BSP_BUTTON_3, true);
}
/**
主函数,循环等待中断
*/
int main(void)
{
gpio_init();
while (true)
{
// Do nothing.
}
}
修改 sdk_config.h 配置文件,将其中中断配置的事件数目修改为 4。
GPIOTE 具有任务模式,任务模式就是输出模式。如果把 GPIO 管脚绑定了 GPIOTE 通道后,把它配置为任务模式,则可以实现输出功能。任务模式的使用不是孤立的,一般都是由事件来触发任务,如果在事件和任务中间假设一个通道,也就是后面的 PPI,那么整个过程不需要 CPU 参与了,大大节省了 MCU 资源。
构建工程:
新建 GPIOTE.c,首先是 GPIOTE 任务初始化,初始化两个 GPIOTE 通道。初始化首先设置通道 CONFIG[0].PSEL 域设置绑定 GPIO 的 19 管脚,CONFIG[1].PSEL 绑定 20 管脚;再设置两个通道的 CONFIG.MODE 为 TASK 任务模式;最后设置 CONFIG.POLARITY 为 OUT[0] 任务输出位翻转电平,OUT[1] 输出低电平。具体如下:
#include "nrf52.h"
#include "nrf_gpio.h"
#include "GPIOTE.h"
void Delay(uint32_t temp)
{
for(; temp!= 0; temp--);
}
void GPIOTE_TASK_Init(void)
{
NVIC_EnableIRQ(GPIOTE_IRQn);//中断嵌套设置
//绑定两个GPIOTE
NRF_GPIOTE->CONFIG[0] = (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)//设置极性,翻转
| (GPIOTE0 << GPIOTE_CONFIG_PSEL_Pos) //绑定管脚
| (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);//设置模式
//配置任务输出状态、绑定通道、任务模式(详细说明请参看青风教程)
NRF_GPIOTE->CONFIG[1] = (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)//输出低电平
| (GPIOTE1<< GPIOTE_CONFIG_PSEL_Pos)
| (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);配置任务输出状态、绑定通道、任务模式(详细说明请参看青风教程)
}
主函数 main 中只需要初始化即可。
#include "nrf52.h"
#include "nrf_gpio.h"
#include "GPIOTE.h"
#include "led.h"
#include "nrf_delay.h"
int main(void)
{
/*初始化输出任务*/
GPIOTE_TASK_Init();
while(1)
{
//触发输出任务模式
NRF_GPIOTE->TASKS_OUT[0]=1;//由config寄存器里的极性配置觉得输出的信号
NRF_GPIOTE->TASKS_OUT[1]=1;
nrf_delay_ms(500);
}
}
和前面操作类似,这里使用 SDK 库函数的 API 进行组件方式的任务配置。
nrf_drv_gpiote_out_init
,类似 nrfx_gpiote_out_init
。nrf_drv_gpiote_out_task_enabel
, 类似 nrfx_gpiote_out_task_enabel
#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "app_error.h"
#include "nrf_delay.h"
#define GPIOTE0 19
#define GPIOTE1 20
void GPIOTE_TASK_Init(void)
{
ret_code_t err_code;
//初始化GPIOTE程序模块
err_code = nrf_drv_gpiote_init();
APP_ERROR_CHECK(err_code);
//定义GPIOTE输出初始化结构体,主要是配置为翻转模式
nrf_drv_gpiote_out_config_t config1 = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);//
//绑定GPIOTE输出引脚
err_code = nrf_drv_gpiote_out_init(GPIOTE0, &config1);
APP_ERROR_CHECK(err_code);
//配置为引脚LED_3所在GPIOTE通道的任务模式
nrf_drv_gpiote_out_task_enable(GPIOTE0);
//定义GPIOTE输出初始化结构体,主要是配置为低电平模式
nrf_drv_gpiote_out_config_t config2 = GPIOTE_CONFIG_OUT_TASK_LOW;
//绑定GPIOTE输出引脚
err_code = nrf_drv_gpiote_out_init(GPIOTE1, &config2);
APP_ERROR_CHECK(err_code);
//配置为引脚LED_4所在GPIOTE通道的任务模式
nrf_drv_gpiote_out_task_enable(GPIOTE1);
}
int main(void)
{
GPIOTE_TASK_Init();
while(true)
{
//触发输出,即指示灯D3,D4翻转状态
nrf_drv_gpiote_out_task_trigger(GPIOTE0);
nrf_drv_gpiote_out_task_trigger(GPIOTE1);
nrf_delay_ms(500);
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。