nRF52832——PPI 模块的应用


引言

nRF52832 处理器是 cortex m4 内核,内部设置了 PPI 方式。PPI 和 DMA 功能有些类似,也是用于不同外设之间进行互连而不需要 CPU 进行参与。PPI 主要的连接对象是任务和事件。

原理分析

PPI 的结构

PPI 称为可编程外设接口,PPI 可以使不同外设自动通过任务和事件来连接,不需要 CPU 参与,可以有效的降低功耗,提高处理器处理效率。

PPI 的两端一端连接的是事件端点(EEP),一端连接的是任务端点(TEP)。因此 PPI 可以通过一个外设上发生的事件自动的触发另一个外设上的任务。外设事件需要通过与事件相关的寄存器地址连接到一个事件端点(EEP),另一端外设任务事件通过此任务相关的任务寄存器地址连接到一个任务端点(TEP),当两端连接好后就可以通过 PPI 进行自动触发了。

连通事件端点(EEP)和任务端点(TEP)的通道称为 PPI 通道。一般有两种方法打开和关闭 PPI 通道:

  • 通过 Groups 的 ENABLE 和 DISABLE 任务来使能或者关闭 PPI 通道组中的 PPI 通道。在触发这些任务前,必须配置哪些 PPI 通道属于哪个 PPI 组。
  • 使用 CHEN,CHENSET 和 CHENSET 和 CHENCLR 寄存器配置单独打开或关掉 PPI 通道;

PPI 任务(比如 CHG0EN)可以像其他任务一样被 PPI 触发,也就是说 PPI 任务可以作为 PPI 通道上的一个 TEP。其中一个事件可使用多个通道来触发多个任务。同样的,一个任务可以被多个事件触发。PPI 的内部结构图如下图

在这里插入图片描述
除了完全可编程的外围互连之外,PPI 系统还具有一组通道其事件终点(EEP)和任务终点(TEP)在硬件中是固定。这些固定信道可以与普通 PPI 信道相同的方式单独启用,禁用或添加到 PPI 信道组。如下表所示,PPI 一共是 32 个通道,编号为 0~31,其中通道 20~31 通道,一共 12 个为固定通道,也称为预编程通道;通道 0~19 通道,一共 20 个为可编程通道,可编程通道可以通过程序配置事件终点和任务终点。

instancechannelnumber of channelsnumber of groups
PPI0-19206
PPI(fixed)20-3112

下表中为预编程通道,这些通道不能通过 CPU 进行配置,但是可以添加到组(Groups),可以像其他通用的 PPI 通道一样使能打开或者关闭。预编程通道通道事件终点(EEP)和任务终点(TEP)是固定配置的硬件外设事件或者任务。
在这里插入图片描述
在每个 PPI 通道上,信号与 16 MHz 时钟同步,以避免任何内部违反设置和保持时序。 因此, 与 16 MHz 时钟同步的事件将延迟一个时钟周期,而其他异步事件将延迟最多一个 16 MHz 时钟周 期。请注意快捷方式(在每个外设中的 SHORTS 寄存器中定义)不受此 16 MHz 同步的影响,因此不会延迟。

fork 从任务机制

fork 机制也称为从任务机制。每个任务终点 TEP 都实现了一个 fork 机制,可以在触发任务终点 TEP 中指定的任务的同时触发第二个任务。第二个任务配置在 FORK 寄存器的任务端点寄存器中,例如 FORK.TEP [0]与 PPI 通道 CH [0]相关联。

group 分组机制

PPI 通道可以进行分组,多个 PPI 通道可以分为一组,那么该组内的 PPI 通道就可以统一进行管理,同时打开或者关闭 group 内所有的 PPI 通道。 当一个通道属于两个组 m 和 n,并且 CHG [m].EN 和 CHG [n].DIS 同时发生时(m 和 n 可以相等或不同),该通道上的 EN 具有优先权。PPI 任务(例如,CHG [0].EN)可以像任何其他任务一样通过 PPI 触发,这意味着它们可以作为 TEP 连接到 PPI 通道。一个事件可以通过使用多个通道触发多个任务,并且一个任务可以以相同的方式由多个事件触发。

PPI 之 GPIOTE 应用

寄存器方式

PPI 作为触发通道,两端分别连接任务和事件,通过任务来触发事件的发生,可以不通过 CPU 进行处理,大大的节省了系统资源。本例我们来演示 GPIOTE 的 PPI 应用。通过 GPIOTE 事件来触发GPIOTE 任务。下表为 PPI 的寄存器列表

在这里插入图片描述
其中下面几个寄存器详细进行介绍:

  • CHEN 寄存器:该寄存器用于使能或者禁止 PPI 通道,一共是 32 个通道,一个位设置对应一个 PPI 通道:
    在这里插入图片描述

  • CHENSET 寄存器:该寄存器为可读可写寄存器,用于使能 PPI 通道。一共是 32 个通道,一个位设置对应一个 PPI 通道,写 1 使能 PPI 通道,写 0 无效:
    在这里插入图片描述

  • CHENCLR 寄存器:该寄存器为可读可写寄存器,用于禁止 PPI 通道。一共是 32 个通道,一个位设置对应一个 PPI 通道,写 1 禁止 PPI 通道,写 0 无效:
    在这里插入图片描述
    在这里插入图片描述

  • CH[n[.EEP 寄存器:对该寄存器赋值事件寄存器地址,由于一共只有 20 个可编程通道,一个位设置对应一个 PPI 通道。
    在这里插入图片描述

  • CH[n].TEP 寄存器:对该寄存器赋值任务寄存器地址,由于一共只有 20 个可编程通道,一个位设置对应一个 PPI 通道。
    在这里插入图片描述

  • FORK[n].TEP 寄存器:该寄存器是从级任务的端点寄存器,赋值外设寄存器的地址。一共是 32 个通道,一个位设置对应一个 PPI 通道,写 1 禁止 PPI 通道,写 0 无效:
    在这里插入图片描述

  • CHG[m].TEP 寄存器:该寄存器是把 PPI 通道绑定到通道组 group 上。通道组 group 一共是 5 组绑定 32 个通道,一个位设置对应一个 PPI 通道,写 1 绑定该 PPI 通道组,写 0 无效解除通道组对该 PPI 的绑定:
    在这里插入图片描述

寄存器的详细介绍完后,我们来使用寄存器方式进行编程。首先来配置 GPIOTE 的任务和事件。 GPIOTE 的任务和事件在前面的 GPIOTE 的章节中详细讲述过。本实验需要把按键 1 绑定到 GPIOTE 通道 0 上作为事件,把 LED1 灯绑定到 GPIOTE 通道 1 上作任务。具体代码如下:

/**
 * 初始GPIO端口,设置 PIN_IN 为输入管教, PIN_OUT 为输出管脚,
 */
static void gpiote_init(void)
{
	 nrf_gpio_cfg_input(BSP_BUTTON_0,NRF_GPIO_PIN_PULLUP);//设置管脚位上拉输入

    NRF_GPIOTE->CONFIG[0] =  
                    (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)//绑定GPIOTE通道0
                    | (BSP_BUTTON_0<< GPIOTE_CONFIG_PSEL_Pos)  // 配置输入事件状态 
                    | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);//事件模式

    NRF_GPIOTE->CONFIG[1] =  
                    (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)//绑定GPIOTE通道1
                    | (BSP_LED_0 << GPIOTE_CONFIG_PSEL_Pos) // 配置任务输出状态
                    | (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);//任务模式			
}

配置完了 GPIOTE 任务和事件,再来设置 PPI 的端点。PPI 的 CH[n].EEP 寄存器作为通道 n 的事件终点接到 GPIOTE 的输入事件上;CH[n].TEP 寄存器作为通道 n 的任务终点接到 GPIOTE 的输出任务上。 然后使能 PPI 的通道,通过配置 CHEN 寄存器实现。这时候,当发送了 GPIOTE 的输入事件,也就是按键按下后,会触发另一端的输出任务,输出的任务为翻转电平,会实现 LED 灯的亮灭控制。 具体代码如下所示:

void ppi_init(void)
{
    // 配置PPI的端口,通道0一端接按键任务,另外一端接输出事件
    NRF_PPI->CH[0].EEP = (uint32_t)(&NRF_GPIOTE->EVENTS_IN[0]);//事件
    NRF_PPI->CH[0].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[1]);//任务
 
    // 使能PPI的通道0
    NRF_PPI->CHEN = (PPI_CHEN_CH0_Enabled << PPI_CHEN_CH0_Pos);//使能第0通道
}

最后主函数整体如下:


#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "app_error.h"
#include "boards.h"


#include "nrf_drv_ppi.h"


nrf_ppi_channel_t my_ppi_channel;


/**
 * 初始GPIO端口,设置 PIN_IN 为输入管教, PIN_OUT 为输出管脚,
 */
static void gpiote_init(void)
{
	 nrf_gpio_cfg_input(BSP_BUTTON_0,NRF_GPIO_PIN_PULLUP);//设置管脚位上拉输入

    NRF_GPIOTE->CONFIG[0] =  
                    (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)//绑定GPIOTE通道0
                    | (BSP_BUTTON_0<< GPIOTE_CONFIG_PSEL_Pos)  // 配置输入事件状态 
                    | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);//事件模式

    NRF_GPIOTE->CONFIG[1] =  
                    (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)//绑定GPIOTE通道1
                    | (BSP_LED_0 << GPIOTE_CONFIG_PSEL_Pos) // 配置任务输出状态
                    | (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);//任务模式			
}

void ppi_init(void)
{
    // 配置PPI的端口,通道0一端接按键任务,另外一端接输出事件
    NRF_PPI->CH[0].EEP = (uint32_t)(&NRF_GPIOTE->EVENTS_IN[0]);//事件
    NRF_PPI->CH[0].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[1]);//任务
 
    // 使能PPI的通道0
    NRF_PPI->CHEN = (PPI_CHEN_CH0_Enabled << PPI_CHEN_CH0_Pos);//使能第0通道
}
/**
 * 主函数,配置PPI的通道
 */
int main(void)
{    
    gpiote_init();
    ppi_init();
    while (true)
    {
        // 循环等待
    }
}

把该例子程序编译后下载到 nrf52832 开发板内。按下按键 1 后,触发 GPIOTE 事件,事件通过 PPI 触发 GPIOTE 任务,可以使得 LED1 灯进行翻转。

库函数方式

函数介绍

SDK 的库函数内提供了 PPI 的编程组件库,本节将通过 PPI 的库函数 API 来实现一个 GPIOTE 的应用。PPI 的编程组件库函数主要是使用如下几个函数,这些函数可以方便的配置 PPI 的应用。

  • nrf_drv_ppi_init() 该函数主要是用于初始化 PPI 模块,判断 PPI 当前的状态。
    在这里插入图片描述
    在这里插入图片描述

  • nrfx_ppi_channel_alloc() 用于分配未使用的 PPI 通道
    在这里插入图片描述

  • nrfx_ppi_channel_enable() 该函数用于使能 PPI 通道,开启 PPI
    在这里插入图片描述

  • nrfx_ppi_channel_assign() 该函数主要用于分配 EEP 事件终点和 TEP 任务终点
    在这里插入图片描述

工程使用

创建如下工程结构:
在这里插入图片描述
主函数 main.c 文件,sdk_config.h 配置文件这两个文件需要我们编写和修改的。而 nrfx_ppi.c 文件和 nrf_drv_ppi 则是需要我们添加的库文件。
添加头文件包含路径:

在这里插入图片描述
工程搭建完毕后,首先我们需要来修改 sdk_config.h 配置文件,库函数的使用是需要对库功能进 行使能的,因此需要在 sdk_config.h 配置文件中,设置对应模块的使能选项。
在这里插入图片描述

主函数操作如下:


#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "app_error.h"
#include "boards.h"


#include "nrf_drv_ppi.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

nrf_ppi_channel_t my_ppi_channel;


/**
 * 初始GPIO端口,设置 PIN_IN 为输入管教, PIN_OUT 为输出管脚,
 */
static void gpiote_init(void)
{
    ret_code_t err_code;
    //初始化GPIOTE
    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_out_config_t out_config =  GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
    //绑定输出端口
    err_code = nrf_drv_gpiote_out_init(PIN_OUT, &out_config);
    APP_ERROR_CHECK(err_code);
    //配置为输出任务模式使能
    nrf_drv_gpiote_out_task_enable(PIN_OUT); 

    nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO (true);
    in_config.pull = NRF_GPIO_PIN_PULLUP;

    //绑定输入端口
    err_code = nrf_drv_gpiote_in_init(PIN_IN, &in_config, NULL);
    APP_ERROR_CHECK(err_code);
    //配置输入事件使能
    nrf_drv_gpiote_in_event_enable(PIN_IN, true);
}

void ppi_init(void)
{
    ret_code_t err_code;
    
    //初始化PPI的模块
    err_code = nrf_drv_ppi_init();
    APP_ERROR_CHECK(err_code);

    //配置PPI的频道
    err_code = nrfx_ppi_channel_alloc(&my_ppi_channel);

    APP_ERROR_CHECK(err_code);
    //设置PPI通道my_ppi_channel的EEP和TEP 两端对应的硬件
    err_code = nrfx_ppi_channel_assign(my_ppi_channel,
                nrfx_gpiote_in_event_addr_get(PIN_IN),
                nrfx_gpiote_out_task_addr_get(PIN_OUT));

    APP_ERROR_CHECK(err_code);
    //使能PPI通道
    err_code = nrfx_ppi_channel_enable(my_ppi_channel);
    APP_ERROR_CHECK(err_code);	
}
/**
 * 主函数,配置PPI的通道
 */
int main(void)
{    
    gpiote_init();
    ppi_init();
    while (true)
    {
        // Do nothing.
    }
}

把该例子程序编译后下载到 nrf52832 开发板内。按下按键 1,触发 GPIOTE 事件,事件通过PPI 触发 GPIOTE 任务,可以使得 LED1 灯进行翻转。

fork 从任务应用

PPI fork 从任务寄存器应用

fork 机制也称为从任务机制。每个 TEP 都实现了一个 fork 机制,可以在触发 TEP 中指定的任务的同时触发第二个任务。第二个任务配置在 FORK 寄存器组的任务端点寄存器中。如下图所示,因此我们可以采用一个事件触发两个任务,一个主任务,一个从任务。
在这里插入图片描述

当某个事件触发任务 CH[n].TEP 任务的时候,如果配置 FORK[n].TEP 终点连接一个从任务,那么也可以同时别触发。类似前面 GPIOTE 增加 PPI 的方式,只不过在这里 PPI 下的 FORK 也添加了任务配置。具体代码如下:


#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "app_error.h"
#include "boards.h"


#include "nrf_drv_ppi.h"

nrf_ppi_channel_t my_ppi_channel;


/**
 * 初始GPIO端口,设置 PIN_IN 为输入管教, PIN_OUT 为输出管脚,
 */
static void gpiote_init(void)
{
	nrf_gpio_cfg_input(BUTTON_1,NRF_GPIO_PIN_PULLUP);//设置管脚位上拉输入
	
	//配置一个GPIOTE输入任务
	NRF_GPIOTE->CONFIG[0] =  
					(GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)//绑定通道0
						| (BUTTON_1<< GPIOTE_CONFIG_PSEL_Pos)  // 配置事件输入
						| (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);//设置实际模式
	
	//配置一个GPIOTE输出						 
	NRF_GPIOTE->CONFIG[1] =  
					(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)//绑定通道1
						| (LED_1 << GPIOTE_CONFIG_PSEL_Pos) // 配置任务输出状态
						| (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);//任务模式

	//配置一个GPIOTE输出作为分支端	
    NRF_GPIOTE->CONFIG[2] =  
					(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)//绑定通道2
						| (LED_2 << GPIOTE_CONFIG_PSEL_Pos) // 配置任务输出状态
						| (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);//任务模式

}

void ppi_init(void)
{

    // 配置PPI一端接输入事件0,一端接输出任务1
    NRF_PPI->CH[0].EEP = (uint32_t)(&NRF_GPIOTE->EVENTS_IN[0]);
    NRF_PPI->CH[0].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[1]);
	//输出端接通道0的fork分支端
    NRF_PPI->FORK[0].TEP= (uint32_t)(&NRF_GPIOTE->TASKS_OUT[2]); 
    // 使能通道0
    NRF_PPI->CHEN = (PPI_CHEN_CH0_Enabled << PPI_CHEN_CH0_Pos);


}
/**
 * 主函数,配置PPI的通道

 */
int main(void)
{    
	gpiote_init();
    ppi_init();
    while (true)
    {
        // Do nothing.
    }
}

把该例子程序编译后下载到青风 nrf52832 开发板内。按下按键 1,触发了 GPIOTE 事件,事件通过PPI 触发 GPIOTE 主任务和从任务。就可以使得 LED1 灯进行翻转,同时 LED2 灯也会进行翻转。

PPI fork 从任务库函数应用

库函数下 FORK 从任务的编程,需要使用到库函数 API,介绍如下

  • nrfx_ppi_channel_fork_assign() 分配从任务 fork 端点到 PPI 通道上。
    在这里插入图片描述

fork 方式只是比 PPI 正常方式多了一个 fork 的从任务注册,其他都没什么变化。主函数代码操作如下:


#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "app_error.h"
#include "boards.h"
#include "nrf_drv_ppi.h"


nrf_ppi_channel_t my_ppi_channel;


/**
 * 初始GPIO端口,设置 PIN_IN 为输入管教, PIN_OUT 为输出管脚,
 */
static void gpiote_init(void)
{
    ret_code_t err_code;

    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);
    //配置一个GPIOTE输出
    nrf_drv_gpiote_out_config_t out_config =  GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
    err_code = nrf_drv_gpiote_out_init(BSP_LED_0, &out_config);
    APP_ERROR_CHECK(err_code);
	
    nrf_drv_gpiote_out_task_enable(BSP_LED_0); 
	
    //配置一个GPIOTE输出作为分支端	
    nrf_drv_gpiote_out_config_t out_config2 =  GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
    err_code = nrf_drv_gpiote_out_init(BSP_LED_1, &out_config2);
    APP_ERROR_CHECK(err_code);
	
    nrf_drv_gpiote_out_task_enable(BSP_LED_1); 
	
    //配置一个GPIOTE输入任务
    nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO (true);
    in_config.pull = NRF_GPIO_PIN_PULLUP;
    err_code = nrf_drv_gpiote_in_init(BSP_BUTTON_0, &in_config, NULL);
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_in_event_enable(BSP_BUTTON_0, true);
			

}

void ppi_init(void)
{
    ret_code_t err_code;
    
    //初始化PPI的模块
    err_code = nrf_drv_ppi_init();
    APP_ERROR_CHECK(err_code);

    //配置PPI的频道
    err_code = nrfx_ppi_channel_alloc(&my_ppi_channel);

    APP_ERROR_CHECK(err_code);
    //设置PPI通道my_ppi_channel的EEP和TEP 两端对应的硬件
    err_code = nrfx_ppi_channel_assign(my_ppi_channel,
                                nrfx_gpiote_in_event_addr_get(BSP_BUTTON_0),
                                nrfx_gpiote_out_task_addr_get(BSP_LED_0));
    APP_ERROR_CHECK(err_code);
        
    //配置PPI通道0的分支任务端点
    err_code = nrfx_ppi_channel_fork_assign(my_ppi_channel,
                                                    nrf_drv_gpiote_out_task_addr_get(BSP_LED_1));
    //使能PPI通道
    err_code = nrfx_ppi_channel_enable(my_ppi_channel);
    APP_ERROR_CHECK(err_code);	

}
/**
 * 主函数,配置PPI的通道
 */
int main(void)
{    
    gpiote_init();
    ppi_init();
    while (true)
    {
        // Do nothing.
    }
}

把该例子程序编译后下载到 nrf52832 开发板内。按下按键 1,触发了 GPIOTE 事件,事件通过PPI 触发 GPIOTE 主任务和从任务。就可以使得 LED1 灯进行翻转,同时 LED2 灯也会进行翻转。

PPI 之 group 分组应用

PPI group 分组原理

PPI 通道可以进行分组,多个 PPI 通道可以分为一组。PPI 具有 6 个 group 组,每个组都可以包 含多个 PPI 通道。如下图
在这里插入图片描述

图中,m=5。一共 6 个组,每个组都可以把 0~n,n=31 个 PPI 通道包含到其中。那么包含到一个 group 组内的 PPI 通道就可以进行统一的管理与操作。比如打开或者关掉 PPI 通道。通过寄存器 CHG [n] .EN 和 CHG [n] .DIS 来打开或者关掉包含在组里的所有 PPI 通道。

PPI 组事件 CHG [0] .EN 也可以像任何其他任务一样通过 PPI 触发,这意味着它们可以作为 TEP 连接到 PPI 通道,因此可以通过 PPI 事件来管理一个 group 组。

PPI group 分组寄存器应用

这里使用按键方式管理 group 组进行设置来示范。

第一种方式验证按键扫描方式来使能 group 组和禁止 group 组的方式。第二种方式采用 PPI 通道触发组事件 CHG [0] .EN 来使能 group 组和禁止 group 组。

代码工程如下:
在这里插入图片描述
主函数文件配置代码如下所示:首先配置 GPIOTE 事件和任务,由于要使用到 PPI 组,这里我 们配置两组 GPIOTE 事件和任务。按键 1 和按键 2 分别配置为 GPIOTE 输入事件,LED1 和 LED2 则配置为 GPIOTE 输出任务。具体配置如下所示

/**
 * 初始GPIO端口,设置 PIN_IN 为输入管教, PIN_OUT 为输出管脚,
 */
static void gpiote_init(void)
{
	nrf_gpio_cfg_input(KEY_1,NRF_GPIO_PIN_PULLUP);//设置管脚位上拉输入
	nrf_gpio_cfg_input(KEY_2,NRF_GPIO_PIN_PULLUP);//设置管脚位上拉输入

	//任务输入1,KEY1
	NRF_GPIOTE->CONFIG[0] =  
					(GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)//绑定GPIOTE通道0
						| (KEY_1  << GPIOTE_CONFIG_PSEL_Pos)  // 配置任务输入状态
						| (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);//事件模式	
	//任务输出1,LED0
    NRF_GPIOTE->CONFIG[1] =  
					(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)//绑定GPIOTE通道1
						| (LED_0 << GPIOTE_CONFIG_PSEL_Pos) // 配置任务输出状态
						| (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);//任务模式
		
	//任务输入2,KEY2
	NRF_GPIOTE->CONFIG[2] =  
					(GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)//绑定GPIOTE通道2
						| (KEY_2  << GPIOTE_CONFIG_PSEL_Pos)   // 配置任务输入状态
						| (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);//事件模式
	//任务输出2,LED1
    NRF_GPIOTE->CONFIG[3] =  
					(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)//绑定GPIOTE通道3
						| (LED_1<< GPIOTE_CONFIG_PSEL_Pos) // 配置任务输出状态
						| (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);//任务模式
}

再来分配 PPI 的组,本例使用两个 PPI 通道,分别为 PPI 通道 0 和 PPI 通道 1。PPI 通道 0 一端 EEP 事件终点接 GPIOTE 事件 0,另一端 TEP 任务终点接 GPIOTE 任务 1。PPI 通道 1 一端 EEP 事 件终点接 GPIOTE 事件 2,另一端 TEP 任务终点接 GPIOTE 任务 3。最后把 PPI 通道 0 和 PPI 通道 1 同时配置到 group0 上去,通过 CHG[0]赋值 0x03,绑定 PPI 通道 0 和 PPI 通道 1 到 group0 组。具 体代码如下所示:

void ppi_init(void)
{
	// 配置PPI通道0,一端接GPIOTE事件0,一端接GPIOTE任务1
	NRF_PPI->CH[0].EEP = (uint32_t)(&NRF_GPIOTE->EVENTS_IN[0]);
	NRF_PPI->CH[0].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[1]);

	// 配置PPI通道1,一端接GPIOTE事件2,一端接GPIOTE任务3
	NRF_PPI->CH[1].EEP = (uint32_t)(&NRF_GPIOTE->EVENTS_IN[2]);
	NRF_PPI->CH[1].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[3]);
	
	//把通道0和通道1 绑定到PPI group0之上
	NRF_PPI->CHG[0]=0x03;
}

如果采用按键扫描的方式对 PPI 组进行管理,则需要在主函数中,通过判断按键是否按下来使 能组或者禁止组。因此参考按键扫描的方式在按键 3 按下后,使能 PPI 组;在按键 4 按下后关闭 PPI 组。

整体 main.c 代码如下:


#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "app_error.h"
#include "led.h"
#include "key.h"

#include "nrf_drv_ppi.h"



nrf_ppi_channel_t my_ppi_channel;


/**
 * 初始GPIO端口,设置 PIN_IN 为输入管教, PIN_OUT 为输出管脚,
 */
static void gpiote_init(void)
{
	nrf_gpio_cfg_input(KEY_1,NRF_GPIO_PIN_PULLUP);//设置管脚位上拉输入
	nrf_gpio_cfg_input(KEY_2,NRF_GPIO_PIN_PULLUP);//设置管脚位上拉输入

	//任务输入1,KEY1
	NRF_GPIOTE->CONFIG[0] =  
					(GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)//绑定GPIOTE通道0
						| (KEY_1  << GPIOTE_CONFIG_PSEL_Pos)  // 配置任务输入状态
						| (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);//事件模式	
	//任务输出1,LED0
    NRF_GPIOTE->CONFIG[1] =  
					(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)//绑定GPIOTE通道1
						| (LED_0 << GPIOTE_CONFIG_PSEL_Pos) // 配置任务输出状态
						| (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);//任务模式
		
	//任务输入2,KEY2
	NRF_GPIOTE->CONFIG[2] =  
					(GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)//绑定GPIOTE通道2
						| (KEY_2  << GPIOTE_CONFIG_PSEL_Pos)   // 配置任务输入状态
						| (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);//事件模式
	//任务输出2,LED1
    NRF_GPIOTE->CONFIG[3] =  
					(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)//绑定GPIOTE通道3
						| (LED_1<< GPIOTE_CONFIG_PSEL_Pos) // 配置任务输出状态
						| (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);//任务模式
}

void ppi_init(void)
{
	// 配置PPI通道0,一端接GPIOTE事件0,一端接GPIOTE任务1
	NRF_PPI->CH[0].EEP = (uint32_t)(&NRF_GPIOTE->EVENTS_IN[0]);
	NRF_PPI->CH[0].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[1]);

	// 配置PPI通道1,一端接GPIOTE事件2,一端接GPIOTE任务3
	NRF_PPI->CH[1].EEP = (uint32_t)(&NRF_GPIOTE->EVENTS_IN[2]);
	NRF_PPI->CH[1].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[3]);
	
	//把通道0和通道1 绑定到PPI group0之上
	NRF_PPI->CHG[0]=0x03;
}


/**
 * 主函数,配置PPI的通道
 */
int main(void)
{    
	gpiote_init();
    ppi_init();
	KEY_Init();
	LED_Init();
	LED3_Close();
	LED4_Close();
    
	while (true)
    {
		//判定按键是否按下
        if( KEY3_Down()== 0)
		{
			LED4_Close();
			NRF_PPI->TASKS_CHG[0].EN = 1;//使能PPI group0
			LED3_Toggle();
		} 
		
		//判定按键是否按下
		if( KEY4_Down()== 0)
		{ 
			LED3_Close();
			NRF_PPI->TASKS_CHG[0].DIS = 1;//关闭PPI group0
			LED4_Toggle();
		}
	}
			
}


把该例子程序编译后下载到 nrf52832 开发板内。默认 PPI group 组是关闭的,此时按下按 键 1 或者按键 2,LED1 灯或者 LED2 灯不会发生变化。如果按下按键 3,使能了 PPI group 组,此 时再按下按键 1,可以使得 LED1 灯进行翻转;按下按键 2,可以使得 LED2 灯进行翻转。我们如果 想关闭 PPI group 组,按下按键 4 则可以实现。

PPI group 分组库函数应用

库函数介绍

  • nrfx_ppi_channel_include_in_group() 把指定的 PPI 通道包含到通道组中。
    在这里插入图片描述

  • nrfx_ppi_group_alloc() 用于分配一个未被使用的 PPI 组,并且分配对应的 PPI 通道组指针。
    在这里插入图片描述
    在这里插入图片描述

  • nrfx_ppi_group_enable() 用于使能 PPI 通道组,可以统一的打开包含的 PPI 通道。
    在这里插入图片描述

  • nrfx_ppi_group_disable() 用于关闭 PPI 通道组,可以统一的禁止包含的 PPI 通道。
    在这里插入图片描述

库函数使用

工程参考前面 PPI 的工程使用,sdk_config.h 配置一样。

首先配置两个 GPIOTE 输入任务和两个 GPIOTE 输出事件。两个输入任务分别接按键 1 和按键 2,两个输出事件分别接 LED1 和 LED2 灯。


nrf_ppi_channel_t my_ppi_channel1;
nrf_ppi_channel_t my_ppi_channel2;
nrf_ppi_channel_group_t qf_ppi_group;
/**
 * 初始GPIO端口,设置 PIN_IN 为输入管教, PIN_OUT 为输出管脚,
 */
static void gpiote_init(void)
{
    ret_code_t err_code;

    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);
       
    
    //配置输出任务1
    nrf_drv_gpiote_out_config_t out_config1 =  GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);

    err_code = nrf_drv_gpiote_out_init(LED_1, &out_config1);
    APP_ERROR_CHECK(err_code);
	
    nrf_drv_gpiote_out_task_enable(LED_1); 

    //配置输出任务2
    nrf_drv_gpiote_out_config_t out_config2 =  GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);

    err_code = nrf_drv_gpiote_out_init(LED_2, &out_config2);
    APP_ERROR_CHECK(err_code);
	
    nrf_drv_gpiote_out_task_enable(LED_2); 
	    
    //配置输入事件3
    nrf_drv_gpiote_in_config_t in_config1 = GPIOTE_CONFIG_IN_SENSE_HITOLO (true);
    in_config1.pull = NRF_GPIO_PIN_PULLUP;

    err_code = nrf_drv_gpiote_in_init(BSP_BUTTON_0, &in_config1, NULL);
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_in_event_enable(BSP_BUTTON_0, true);

    //配置输入事件4
    nrf_drv_gpiote_in_config_t in_config2 = GPIOTE_CONFIG_IN_SENSE_HITOLO (true);
    in_config2.pull = NRF_GPIO_PIN_PULLUP;

    err_code = nrf_drv_gpiote_in_init(BSP_BUTTON_1, &in_config2, NULL);
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_in_event_enable(BSP_BUTTON_1, true);
}

然后使用库函数将 PPI 通道包含到 group 组中,在 PPI 初始化函数中配置 PPI 通道到 group 组。


void ppi_init(void)
{
    ret_code_t err_code;
    
    //初始化PPI的模块
    err_code = nrf_drv_ppi_init();
    APP_ERROR_CHECK(err_code);

    //配置PPI的频道
    err_code = nrfx_ppi_channel_alloc(&my_ppi_channel1);
    APP_ERROR_CHECK(err_code);
    
    //设置PPI通道my_ppi_channel的EEP和TEP 两端对应 输出任务1和输入事件3
    err_code = nrfx_ppi_channel_assign(my_ppi_channel1,
                                        nrfx_gpiote_in_event_addr_get(BSP_BUTTON_0),
                                        nrfx_gpiote_out_task_addr_get(LED_1));
    APP_ERROR_CHECK(err_code);
        
        
        
    //配置PPI的频道
    err_code = nrfx_ppi_channel_alloc(&my_ppi_channel2);
    APP_ERROR_CHECK(err_code);
        
    //设置PPI通道my_ppi_channel的EEP和TEP 两端对应 输出任务2和输入事件4
    err_code = nrfx_ppi_channel_assign(my_ppi_channel2,
                                        nrfx_gpiote_in_event_addr_get(BSP_BUTTON_1),
                                        nrfx_gpiote_out_task_addr_get(LED_2));
    APP_ERROR_CHECK(err_code);
        
        
    //申请PPI组,分配的组号保存到my_ppi_group
    err_code = nrfx_ppi_group_alloc(&qf_ppi_group);
    APP_ERROR_CHECK(err_code);	
            
    //PPI通道my_ppi_channel加入到PPI组my_ppi_group
    err_code = nrfx_ppi_channel_include_in_group(my_ppi_channel1,qf_ppi_group);
    APP_ERROR_CHECK(err_code);
    
    //PPI通道my_ppi_channel2加入到PPI组my_ppi_group
    err_code = nrfx_ppi_channel_include_in_group(my_ppi_channel2,qf_ppi_group);
    APP_ERROR_CHECK(err_code);
}

整体 main.c 代码内容如下:


#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "app_error.h"
#include "boards.h"
#include "nrf_drv_ppi.h"


nrf_ppi_channel_t my_ppi_channel1;
nrf_ppi_channel_t my_ppi_channel2;
nrf_ppi_channel_group_t qf_ppi_group;
/**
 * 初始GPIO端口,设置 PIN_IN 为输入管教, PIN_OUT 为输出管脚,
 */
static void gpiote_init(void)
{
    ret_code_t err_code;

    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);
    
    //配置输出任务1
    nrf_drv_gpiote_out_config_t out_config1 =  GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);

    err_code = nrf_drv_gpiote_out_init(LED_1, &out_config1);
    APP_ERROR_CHECK(err_code);
	
    nrf_drv_gpiote_out_task_enable(LED_1); 

    //配置输出任务2
    nrf_drv_gpiote_out_config_t out_config2 =  GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);

    err_code = nrf_drv_gpiote_out_init(LED_2, &out_config2);
    APP_ERROR_CHECK(err_code);
	
    nrf_drv_gpiote_out_task_enable(LED_2); 
	    
    //配置输入事件3
    nrf_drv_gpiote_in_config_t in_config1 = GPIOTE_CONFIG_IN_SENSE_HITOLO (true);
    in_config1.pull = NRF_GPIO_PIN_PULLUP;

    err_code = nrf_drv_gpiote_in_init(BSP_BUTTON_0, &in_config1, NULL);
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_in_event_enable(BSP_BUTTON_0, true);

    //配置输入事件4
    nrf_drv_gpiote_in_config_t in_config2 = GPIOTE_CONFIG_IN_SENSE_HITOLO (true);
    in_config2.pull = NRF_GPIO_PIN_PULLUP;

    err_code = nrf_drv_gpiote_in_init(BSP_BUTTON_1, &in_config2, NULL);
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_in_event_enable(BSP_BUTTON_1, true);
}


void ppi_init(void)
{
    ret_code_t err_code;
    
    //初始化PPI的模块
    err_code = nrf_drv_ppi_init();
    APP_ERROR_CHECK(err_code);

    //配置PPI的频道
    err_code = nrfx_ppi_channel_alloc(&my_ppi_channel1);
    APP_ERROR_CHECK(err_code);
    
    //设置PPI通道my_ppi_channel的EEP和TEP 两端对应 输出任务1和输入事件3
    err_code = nrfx_ppi_channel_assign(my_ppi_channel1,
                                        nrfx_gpiote_in_event_addr_get(BSP_BUTTON_0),
                                        nrfx_gpiote_out_task_addr_get(LED_1));
    APP_ERROR_CHECK(err_code);
        
    //配置PPI的频道
    err_code = nrfx_ppi_channel_alloc(&my_ppi_channel2);
    APP_ERROR_CHECK(err_code);
        
    //设置PPI通道my_ppi_channel的EEP和TEP 两端对应 输出任务2和输入事件4
    err_code = nrfx_ppi_channel_assign(my_ppi_channel2,
                                        nrfx_gpiote_in_event_addr_get(BSP_BUTTON_1),
                                        nrfx_gpiote_out_task_addr_get(LED_2));
    APP_ERROR_CHECK(err_code);
        
    //申请PPI组,分配的组号保存到my_ppi_group
    err_code = nrfx_ppi_group_alloc(&qf_ppi_group);
    APP_ERROR_CHECK(err_code);	
            
    //PPI通道my_ppi_channel加入到PPI组my_ppi_group
    err_code = nrfx_ppi_channel_include_in_group(my_ppi_channel1,qf_ppi_group);
    APP_ERROR_CHECK(err_code);
    
    //PPI通道my_ppi_channel2加入到PPI组my_ppi_group
    err_code = nrfx_ppi_channel_include_in_group(my_ppi_channel2,qf_ppi_group);
    APP_ERROR_CHECK(err_code);
}


void board_led_key_init(void)
{      
    nrf_gpio_cfg_output(LED_3); //配置P0.19为输出,驱动led3 
    nrf_gpio_cfg_output(LED_4);//配置P0.20为输出,驱动led4 
    nrf_gpio_pin_set(LED_3);//led3初始状态设置为熄灭  
    nrf_gpio_pin_set(LED_4);//led4初始状态设置为熄灭 
    nrf_gpio_cfg_input(BUTTON_3,NRF_GPIO_PIN_PULLUP);//配置P0.15为输入  
    nrf_gpio_cfg_input(BUTTON_4,NRF_GPIO_PIN_PULLUP);//配置P0.16为输入
}

/**
 * 主函数,配置PPI的通道
 */
int main(void)
{   
    ret_code_t err_code; 

    gpiote_init();
    ppi_init();
    board_led_key_init();

    while (true)
    {
        //检测按键S3是否按下
        if(nrf_gpio_pin_read(BUTTON_3) == 0)
        {
            //D3点亮,D4熄灭,指示:PPI组使能
            nrf_gpio_pin_clear(LED_3);
            nrf_gpio_pin_set(LED_4);

            while(nrf_gpio_pin_read(BUTTON_3) == 0){}//等待按键释放
            
            //使能PPI组my_ppi_group
            err_code = nrfx_ppi_group_enable(qf_ppi_group);
            APP_ERROR_CHECK(err_code);
        }

        //检测按键S4是否按下
        if(nrf_gpio_pin_read(BUTTON_4) == 0)
        {
            //D4点亮,D3熄灭,指示:PPI组禁止
            nrf_gpio_pin_clear(LED_4);
            nrf_gpio_pin_set(LED_3);
            
            while(nrf_gpio_pin_read(BUTTON_4) == 0){}//等待按键释放

            //禁止PPI组my_ppi_group
            err_code = nrfx_ppi_group_disable(qf_ppi_group);
            APP_ERROR_CHECK(err_code);
        }
    }
}


把该例子程序编译后下载到 nrf52832 开发板内。默认 PPI group 组是关闭的,此时按下 按键 1 或者按键 2,LED1 灯或者 LED2 灯不会发生变化。如果按下按键 3,使能了 PPI group 组,此时再按下按键 1,可以使得 LED1 灯进行翻转;按下按键 2,可以使得 LED2 灯进行翻转。如果按下按键 4,则禁止 PPI group 组,此时按下按键 1 或者按键 2,LED1 灯或者 LED2 灯不会发生变化。

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值