Nordic 的 NRF51 和 NRF52 系列芯片在 GPIO 的基础上引入了任务和事件(GPIOTE)
的概念。GPIOTE 能让我们更方便地去操作 GPIO,同时,他还能有效地减少程序的参与、
降低 CPU 的负担。
说到 GPIOTE ,就需要先了解一下 nRF52832 的寄存器类型,和一般的单片机有所差别,
nRF52832 的寄存器分为下面的三种类型。
Task 和 event 使得操作片上外设十分方便简洁,只需进行少量的配置,即可轻松运用各
种外设。同时,Task 和 event 能有效减少 CPU 的占用时间,降低 CPU 的负荷。
Task 和 Event 更多的是用来和 PPI(可编程外设互连)配合使用,通过 PPI 将某个 Event
和 Task 连接起来,连接后,该 Event 即可触发对应的 Task 执行相应的功能。
nRF52832 的 GPIOTE 共有 8 个通道,每个通道都可以选择一个管脚,选择的管脚可以
配置为 Task mode 或 Event mode。需要注意的是:不能将某个管脚同时分配给多个 GPIOTE
通道,否则会导致无法预料的错误。
GPIOTE 每个通道可以使用的 Task 有 3 个:
GPIOTE 每个通道的事件可以由以下的输入状态产生:
Tasks 和 Events 通过 CONFIGn 寄存器配置,每个 CONFIG[n]寄存器对应一组OUT[n] 任务寄存器和 IN[n]事件寄存器。OUT[n]用于写引脚,IN[n]由引脚状态变化触发。
当把某个引脚分配给OUT[n]任务或IN[n]事件后,该引脚就只能被GPIOTE模块写操作,
正常的 GPIO 写入无效。
一旦配置 OUT[n]任务或 IN[n]事件控制某个引脚,那么该管脚的输出值只能通过 GPIOTE 模块操作,使用 GPIO 的寄存器操作会被忽略。
当 GPIOTE 通道被配置用于操作一个任务引脚时,CONFIG[n]寄存器中的 OUTINIT 决定了该引脚的初始值。可以通过配置 OUTINIT 来设置引脚初始化状态为高电平或是低电平。
当在同一个 GPIOTE 通道中同时触发了有冲突的任务(即同一个时钟周期内),那么任务按照下表所示的优先级执行。
优先级 | 任务 |
---|---|
1 | OUT |
2 | CLEAR |
3 | SET |
GPIOTE 除了 8 个通道外,还包含一个 PORT 事件。PORT 事件由使用 GPIO DETECT 信号的多个引脚触发,PORT 中的任意一个引脚上的上升沿都会触发 PORT 事件。即使外设处于空闲状态,PORT 事件也能使能,此功能不需要任何时钟或其他电源相关的外设。当CPU 和所有外设处于 IDLE 状态时,PORT 事件可以将 CPU 从 WFI 或 WFE 类型休眠中,以及 CPU 空闲模式下唤醒,即在系统 ON 模式下实现最低的功耗。
可以设置一个或多个 GPIO DETECT 用来产生 PORT 事件,PORT 事件可以作为唤醒源,也可以作为中断源产生中断。
GPIOTE 基址:0x40006000
GPIOTE 输出一般不单独使用,因为单独使用时,操作和普通 GPIO 差不多,同样需要程序来操作他,不同的是普通 GPIO 通过 GPIO 的寄存器操作,GPIOTE 通过任务寄存器触发,单独使用时和普通 GPIO 一样同样需要软件干预,体现不了他的优势。GPIOTE 应用的时候一般和 PPI(可编程外设互联)一起用,通过其他外设的事件来触发 GPIOTE 的任务,如使用定时器比较事件等等,这样,整个过程就可以由硬件自动完成,无需软件干预,从而简化程序的流程、降低 CPU 的负担。
GPIOTE 输出的操作流程如下图所示,其中初始化 GPIOTE 模块和初始化 GPIOTE 输出引脚是必须要有的,接下来可以选择是否使能该引脚的 GPIOTE 功能,使能之后,只能通过 GPIOTE 任务触发输出,如果不使能 GPIOTE 功能,则通过写 GPIO 寄存器控制其输出。
Nordic 的 SDK 是模块化设计的,使用 GPIOTE 模块时,先要初始化 GPIOTE 模块,初
始化很简单,调用初始化函数 nrf_drv_gpiote_init 即可。
需要注意的是:GPIOTE 模块在一个应用程序中和很多其他的程序模块共享资源,所以
在一个应用程序中 GPIOTE 模块只能初始化一次。
初始化 GPIOTE 输出引脚通过调用 nrf_drv_gpiote_out_init 函数来完成,该函数接收两个输入参数:引脚号和 GPIOTE 输出初始化结构体,引脚号指定将要配置的引脚的编号(0~31),GPIOTE 输出初始化结构体 nrf_drv_gpiote_out_config_t 包含下面的三项内容:
初始化GPIOTE输出引脚(配置为任务引脚)为指定的引脚分配了GPIOTE通道并且配置了引脚的初始值和动作,但是并没有配置该引脚的模式即没有配置 CONFIG[n].MODE,使能任务触发就是设置 CONFIG[n].MODE = 3,即将其配置为任务模式。
使能任务触通过调用 nrf_drv_gpiote_out_task_enable 函数实现,只有使能任务触发后,我们才能通过触发 GPIOTE 的任务让引脚执行动作。
可以通过下面是三个函数触发 GPIOTE 任务,驱动引脚动作:
#define LED_1 17
/**********************************************************************
* 描 述 : main 函数
* 入 参 : 无
* 返回值 : 无
*********************************************************************/
int main(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 config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
//初始化 GPIOTE 输出引脚
err_code = nrf_drv_gpiote_out_init(LED_1, &config);
APP_ERROR_CHECK(err_code);
//使能引脚 LED_1(P0.17)所在 GPIOTE 通道的任务触发
nrf_drv_gpiote_out_task_enable(LED_1);
while (true)
{
//任务触发驱动引脚 P0.17 状态翻转,即指示灯 D1 翻转状态
nrf_drv_gpiote_out_task_trigger(LED_1);
//任务触发驱动引脚 P0.17 输出高电平,即指示灯 D1 熄灭
//nrf_drv_gpiote_clr_task_trigger(LED_1);
//任务触发驱动引脚 P0.17 输出低电平,即指示灯 D1 电亮
//nrf_drv_gpiote_set_task_trigger(LED_1);
nrf_delay_ms(150);
}
}
GPIOTE 输入的操作流程如下图所示,包含初始化 GPIOTE 程序模块、初始化 GPIOTE
输入引脚、使能事件模式。
和 GPIOTE 输入时一样,调用初始化函数 nrf_drv_gpiote_init 即可。
初始化 GPIOTE 输入引脚通过调用 nrf_drv_gpiote_in_init 函数来完成,该函数接收三个
输入参数:引脚号、GPIOTE 输入初始化结构体和事件句柄,相对于 GPIOTE 输出引脚的初
始化,函数参数多了一个事件句柄,当 GPIOTE 检测到引脚电平变化后,会产生事件,这
时会自动调用这个注册的事件句柄来处理事件。
引 脚 号 指 定 将 要 配 置 的 引 脚 的 编 号 (0~31) , GPIOTE 输 入 初 始 化 结 构 体
nrf_drv_gpiote_in_config_t 包含下面的 4 项内容:
初始化 GPIOTE 输入引脚时为指定的引脚分配了 GPIOTE 通道,但是并没有配置该引脚的模式即没有配置 CONFIG[n].MODE,使能事件模式就是设置 CONFIG[n].MODE = 1,即将其配置为事件模式,当其检测到引脚电平变化时(初始化 GPIOTE 输入引脚时 Sense 项配置了哪种变化可以产生事件)即产生事件。
#define LED_1 17
#define BUTTON_0 13
void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
nrf_drv_gpiote_out_toggle(LED_1);
}
/**********************************************************************
* 描 述 : main 函数
* 入 参 : 无
* 返回值 : 无
**********************************************************************/
int main(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 out_config =
GPIOTE_CONFIG_OUT_SIMPLE(true);
//初始化 GPIOTE 输出引脚
err_code = nrf_drv_gpiote_out_init(LED_1, &out_config);
APP_ERROR_CHECK(err_code);
//高电平到低电平变化产生事件
nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(true);
//低电平到高电平变化产生事件
//nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
//任意电平变化产生事件
//nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
//开启 P0.13 引脚的上拉电阻
in_config.pull = NRF_GPIO_PIN_PULLUP;
//配置该引脚为 GPIOTE 输入
err_code = nrf_drv_gpiote_in_init(BUTTON_0, &in_config, in_pin_handler);
APP_ERROR_CHECK(err_code);
//使能该引脚所在 GPIOTE 通道的事件模式
nrf_drv_gpiote_in_event_enable(BUTTON_0, true);
while (true)
{
}
}
//高电平到低电平变化产生事件
nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(true);
//低电平到高电平变化产生事件
nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
//任意电平变化产生事件
nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);