nRF52832学习记录(一、外设初识之 GPIOTE)
..添加GPIO和GPIOTE寄存器表 (对于应用的理解对着寄存器查看会比较明了,这个不管是在哪款芯片上都是如此
2021/9/27
- 1
- 2
这些年蓝牙5.0的应用越来越多,最近也是想着把以前Enocean的低功耗设备有过的产品,用蓝牙做一套匹配的版本,使得产品线更加丰富,我们这里选择的是主流的 nRF52832 芯片。
学习的时候当然还是得参照很多前人总结的文章。
nRF52832 的寄存器分为下面的三种类型:
- Task :任务寄存器,可以由程序或事件触发。
- Event:事件寄存器,事件可以产生中断或触发任务。
- Register:普通寄存器,和一般单片机的寄存器一样。
1、nRF52832的IO口
32个IO口中
P0.02~ P0.05 和 P0.28~ P0.31可配置为 ADC 采样引脚,其他的IO口可以任意分配到各个外设。
P0.09 和 P0.10 默认分配到 NFC 功能外设,
如果需要设置为普通IO或者映射到其他外设需要添加宏
CONFIG_NFCT_PINS_AS_GPIOS
P0.21默认作为复位引脚
串口接收尽量带上上拉电阻
2、GPIO 和 GPIOTE
在学习的第一步,GPIO 和 GPIOTE我就疑惑了,这个与以前用过的单片机和 STM32 不类似啊,为什么对于IO口需要分GPIO和GPIOTE呢?
这里在网上找了一些相关的说明:
下面的部分转至:nRF5芯片外设GPIO和GPIOTE介绍 和 nRF5芯片外设GPIO和GPIOTE介绍
GPIO和GPIOTE都属于芯片外设,但两者功能完全不一样,使用过程中不要将两者混淆。GPIO就大家通常理解的普通IO口,用来对IO口进行读写等操作。因此,如果你需要读某个IO口状态,或者将某个IO口置1,那么请使用nrf_gpio.h里面的API,比如
nrf_gpio_cfg_input 用来将IO设为输入模式,可以配置为没有pull,有上拉电阻,有下拉电阻,悬浮等4种状态
nrf_gpio_pin_set 用来输出1到IO,输出模式下驱动力灵活可配,可以配置为普通驱动力(2mA),高驱动力(10mA),甚至断开状态(跟开漏输出很像)。
除此之外,GPIO模块还有2个非常重要的功能:
- sense功能。当系统进入sleep模式(也称system OFF模式),只能通过IO口唤醒复位。当某个IO口使能了sense功能,那么它就可以用来唤醒sleep模式了。Sense使能的时候,可以配置高电平唤醒或者低电平唤醒。一般使用nrf_gpio_cfg_sense_input这个函数来使能IO口的sense功能。
- port event功能。通俗来说,port event其实就是IO口中断,而且32个IO口共用同一个中断标志位:port event,检测port event只需要内部低频时钟在工作,因此功耗非常低:0.2微安左右,但内部低频时钟只能用来检测低精度的中断事件,也就是说IO口的中断脉冲要比较宽,比如像按键这种事件,就可以用port event来检测,功能达到了功耗又低,两全其美。这里特别说明一下,port event状态(一个中断flag)是跟随IO口电平的,比如检测高电平有效,那么只要IO口电平一直为高,那么port event一直有效,该中断标志位无法通过软件清除。这会产生两个副作用:一是不断进入port event中断例程,二是前面也提到,port event是被32个IO口共用的,因此只要其中一个IO口一直有效,别的IO产生的port event就会被忽略。为此,在处理port event中断的时候,nRF5 SDK app_button模块将每个port event的极性设为toggle,也就是每进入一次port event handler,nRF5 SDK都会把port event的极性翻转一次,比如将检测为高有效变成检测为低有效,这就相当于清除了port event中断flag,从而避开上述描述的两个副作用场景。由于GPIO模块不能处理中断,所以port event中断实际是交给GPIOTE模块来一起处理的。
nRF52832 GPIO寄存器
GPIOTE,全称GPIO Tasks and Events,GPIOTE首先是一个外设模块,因此它遵守芯片外设最基本规则:每一个时刻每一个GPIO口只能被一个外设使用,因此当某一个IO口被用做GPIOTE了,那么它就不能再作为普通GPIO来使用了,也就是上面提到的GPIO API将变得无效,此时必须使用nrfx_gpiote.h(老版本为nrf_drv_gpiote.h)里面的API。Nordic将状态机引入到每一个外设,也就是说,每一个外设都有自己的输入(task),输出(event)和状态。GPIOTE的作用就是让GPIO也具有task和event的功能,也就是说,对GPIOTE来说,将某一个IO口置1,其实是触发TASKS_SET;检测某一个IO口上升沿,其实是等待EVENTS_IN。让IO口支持task和event机制,将为后面的PPI自动化操作打下基础,关于PPI详细说明,请参考如何理解nRF芯片的外设PP1
前面也提到过,处理IO口中断,必须通过GPIOTE模块来做,GPIOTE支持两种类型中断:
高精度的EVENTS_IN中断以及
低精度的EVENTS_PORT中断(就是前面GPIO章节提到的Port event)。
EVENTS_PORT主要用来检测IO口高电平或者低电平,而EVENTS_IN用来检测沿,即上升沿,下降沿或者双沿。EVENTS_IN可以清0,EVENTS_PORT无法清0,两者都是在GPIOTE_irq_handler里面处理。
EVENTS_IN和EVENTS_PORT两者初始化区别如下所示:
nrf_drv_gpiote_in_config_t config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(false);
//false表示低精度低功耗的Port event,每个IO口都可以作为port event,52832总共有32个portevent
err_code = nrf_drv_gpiote_in_init(pin_no, &config, gpiote_event_handler);
nrf_drv_gpiote_in_config_t config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
//true表示高精度高功耗的IN event,52832总共有8个IN event。注:这里检测的是双沿
err_code = nrf_drv_gpiote_in_init(pin_no, &config, gpiote_event_handler);
- 1
- 2
- 3
- 4
- 5
- 6
如上所述,IN event中断和Port event中断,两者本质上是一样的,唯一的区别是:IN event中断每个中断口是相互独立的,而Port event中断所有IO口共用同一个中断标志位。
IN event需要高频时钟,所以功耗比较高,在精度可以接受的情况下,优先推荐使用port event。
SDK自带GPIOTE应用例程,感兴趣的读者请参考Keil5工程:SDK安装目录\examples\peripheral\pin_change_int\pca10040\blank\arm5_no_packs
SDK也自带Sense例子,有兴趣的读者请参考Keil5工程:SDK安装目录\examples\peripheral\ram_retention\pca10040\blank\arm5_no_packs
关于Port event中断使用例子,可以参考Nordic的app_button模块,比如ble_app_hrs就会用到这个模块,大家可以去看一下app_button是如何使用port event中断的。
nRF52832 GPIOTE寄存器
有了上面的基础,然后再去做52832的GPIO口实验,就知道所以然了。
GPIOTE事件模式中断使用方式(寄存器)
void EXIT_KEY_Init(void)
{
nrf_gpio_cfg_input(KEY_0,NRF_GPIO_PIN_PULLUP);//设置管脚位上拉输入
nrf_gpio_cfg_input(KEY_1,NRF_GPIO_PIN_PULLUP);//设置管脚位上拉输入
NVIC_EnableIRQ(GPIOTE_IRQn);//中断嵌套设置
//绑定两个通道,配置输入事件,这里是绑定为通道0 IN[0]
NRF_GPIOTE->CONFIG[0] = (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)//设置触发级性下降沿
| (13 << GPIOTE_CONFIG_PSEL_Pos) //p0.13
| (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);//配置GPIOTE模式
NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN0_Set << GPIOTE_INTENSET_IN0_Pos;// 使能中断类型
...
}
void GPIOTE_IRQHandler(void)
{
if((NRF_GPIOTE->EVENTS_IN[0] ==1) && (NRF_GPIOTE->INTENSET & GPIOTE_INTENSET_IN0_Msk ))
{
NRF_GPIOTE->EVENTS_IN[0] = 0; //中断事件清零.
... //中断后执行的操作
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
中断的配置,里面的这些值都可以通过Demo找到示意,这里只放一下 NRF_GPIOTE_Type 这个结构体的说明,在 nrf52.h 这个文件中
typedef struct { /*!< GPIOTE Structure */
__O uint32_t TASKS_OUT[8]; /*!< Description collection[0]: Task for writing to pin specified
in CONFIG[0].PSEL. Action on pin is configured in CONFIG[0].POLARITY. */
__I uint32_t RESERVED0[4];
__O uint32_t TASKS_SET[8]; /*!< Description collection[0]: Task for writing to pin specified
in CONFIG[0].PSEL. Action on pin is to set it high. */
__I uint32_t RESERVED1[4];
__O uint32_t TASKS_CLR[8]; /*!< Description collection[0]: Task for writing to pin specified
in CONFIG[0].PSEL. Action on pin is to set it low. */
__I uint32_t RESERVED2[32];
__IO uint32_t EVENTS_IN[8]; /*!< Description collection[0]: Event generated from pin specified
in CONFIG[0].PSEL */
__I uint32_t RESERVED3[23];
__IO uint32_t EVENTS_PORT; /*!< Event generated from multiple input GPIO pins with SENSE mechanism
enabled */
__I uint32_t RESERVED4[97];
__IO uint32_t INTENSET; /*!< Enable interrupt */
__IO uint32_t INTENCLR; /*!< Disable interrupt */
__I uint32_t RESERVED5[129];
__IO uint32_t CONFIG[8]; /*!< Description collection[0]: Configuration for OUT[n], SET[n]
and CLR[n] tasks and IN[n] event */
} NRF_GPIOTE_Type;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
GPIOTE事件模式中断使用方式(库函数)
库函数使用需要带上 sdk_config.h 文件,这个文件是库函数使用的配置文件
/**
GPIOTE中断处理
*/
void xx(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
// 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);
}
}
/**
PIN_IN 16
PIN_OUT 13
在 pca10040.h 里面定义的
*/
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;
err_code = nrf_drv_gpiote_in_init(PIN_IN, &in_config, xx);//设置GPIOTE输入,极性,模式
APP_ERROR_CHECK(err_code);
//使能GPIOTE
nrf_drv_gpiote_in_event_enable(PIN_IN, true);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
GPIOTE PORT模式使用方式(库函数)
void pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
if(pin == BUTTON_1){
...//按键一按下的事情
}
else if(pin == BUTTON_2){
...
}
else if(pin == BUTTON_3){
...
}
else if(pin == BUTTON_4) {
...
}
}
static void gpio_init(void)
{
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配置
//当函数参数是false的时候,选择PORT事件,当为ture时候,选择IN事件
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, pin_handler);
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_in_event_enable(BSP_BUTTON_0, true);
err_code = nrf_drv_gpiote_in_init(BSP_BUTTON_1, &in_config, pin_handler);
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_in_event_enable(BSP_BUTTON_1, true);
err_code = nrf_drv_gpiote_in_init(BSP_BUTTON_2, &in_config, pin_handler);
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_in_event_enable(BSP_BUTTON_2, true);
err_code = nrf_drv_gpiote_in_init(BSP_BUTTON_3, &in_config, pin_handler);
APP_ERROR_CHECK(err_code);
rf_drv_gpiote_in_event_enable(BSP_BUTTON_3, true);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
GPIOTE 任务模式OUT使用方式(寄存器)
void GPIOTE_TASK_Init(void)
{
NVIC_EnableIRQ(GPIOTE_IRQn);
//绑定两个GPIOTE
NRF_GPIOTE->CONFIG[0] = (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)//GPIOTE_CONFIG_POLARITY_Toggle 翻转
| (15 << GPIOTE_CONFIG_PSEL_Pos) //p0.15
| (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);//设置模式
NRF_GPIOTE->CONFIG[1] = (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)//GPIOTE_CONFIG_POLARITY_HiToLo 输出低电平
| (16<< GPIOTE_CONFIG_PSEL_Pos) //p0.16
| (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);
}
...
while(1)
{
NRF_GPIOTE->TASKS_OUT[0]=1; //触发
NRF_GPIOTE->TASKS_OUT[1]=1;
...//delay();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
GPIOTE 任务模式OUT使用方式(库函数)
void GPIOTE_TASK_Init(void)
{
ret_code_t err_code;//uint32_t
//初始化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(15, &config1);//p0.15
APP_ERROR_CHECK(err_code);
//配置为引脚LED(P0.15)所在GPIOTE通道的任务模式
nrf_drv_gpiote_out_task_enable(15);
//定义GPIOTE输出初始化结构体,主要是配置为低电平模式
nrf_drv_gpiote_out_config_t config2 = GPIOTE_CONFIG_OUT_TASK_LOW;
err_code = nrf_drv_gpiote_out_init(16, &config2);
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_out_task_enable(16);
}
int main(void)
{
GPIOTE_TASK_Init();
while(1)
{
nrf_drv_gpiote_out_task_trigger(15);
nrf_drv_gpiote_out_task_trigger(16);
nrf_delay_ms(500);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
文章来源: blog.csdn.net,作者:矜辰所致,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/weixin_42328389/article/details/119991733
- 点赞
- 收藏
- 关注作者
评论(0)