准备工作,先把作业例程解压到sdk包的example目录下
一 . 从blink例程开始
工程文件目录:....\nRF5_SDK_15.2.0_9412b96\examples\peripheral\hy-demo\1.1blinky\dev0628\s132\arm5_no_packs
嵌入式开发板第一件事,当然是点灯了,52832代码组织上有BSP底层软件,也有板子的资源定义文件,所以我们要根据原理图来自己定义资源文件。
我们开发板硬件连接, 先解决指示灯和按键。
另外定义个板载资源文件dev0628.h,和pca10040.h文件放一起,然后修改boards.h
dev0628.h主要内容定义LED 和BUTTON
// LEDs definitions for DEV0628
#define LEDS_NUMBER 3
#define LED_START 5
#define LED_1 5
#define LED_2 7
#define LED_3 9
#define LED_STOP 9
#define LEDS_ACTIVE_STATE 0
#define LEDS_INV_MASK LEDS_MASK
#define LEDS_LIST { LED_1, LED_2, LED_3 }
#define BSP_LED_0 LED_1
#define BSP_LED_1 LED_2
#define BSP_LED_2 LED_3
#define BUTTONS_NUMBER 3
#define BUTTON_START 2
#define BUTTON_1 2
#define BUTTON_2 3
#define BUTTON_3 4
#define BUTTON_STOP 4
#define BUTTON_PULL NRF_GPIO_PIN_PULLUP
#define BUTTONS_ACTIVE_STATE 0
#define BUTTONS_LIST { BUTTON_1, BUTTON_2, BUTTON_3 }
#define BSP_BUTTON_0 BUTTON_1
#define BSP_BUTTON_1 BUTTON_2
#define BSP_BUTTON_2 BUTTON_3
boards.h增加编译选择开关
#elif defined(BOARD_DEV0628)
#include "dev0628.h"
工程设置的编译开关板子选择改一下:
主程序代码就是原来的blinky例程基本没变
int main(void)
{
/* Configure board. */
bsp_board_init(BSP_INIT_LEDS);
/* Toggle LEDs. */
while (true)
{
for (int i = 0; i < LEDS_NUMBER; i++)
{
bsp_board_led_invert(i);
nrf_delay_ms(500);
}
}
}
涉及到api:
void bsp_board_init(uint32_t init_flags)
调用标志BSP_INIT_LEDS,BSP_INIT_BUTTONS,
本例程调用的是
bsp_board_leds_init();
驱动初始化调用的
void nrf_gpio_cfg_output(uint32_t pin_number);
主循环里点灯调用的bsp的led驱动接口
void bsp_board_led_invert(uint32_t led_idx);
这个在调取LEDS的列表后,实际调用的IO驱动
void nrf_gpio_pin_toggle(uint32_t pin_number)
延时用到了sdk的延时函数
void nrf_delay_ms(uint32_t ms_time)
注意,要先烧录s132的softdevice,选择softdevice,然后load烧录
然后再选择芯片,再load应用固件
烧录成功后,LED1, LED2正常,LED3并不会亮,也就是P9没有起作用,这就是我们下一个话题,怎么让P9/P10起作用,作为普通GPIO来使用。
二,打开上面一个例程,做出必要的修改
52832的NFC功能是定义在P9/P10,而且是默认的配置, 所以9/10脚必须改变下设置,才能当作普通GPIO使用。
打开的方法:
1. #include "system_nrf52.h" 主程序把这个头文件加上
2. 头文件里要有一句
//#define CONFIG_NFCT_PINS_AS_GPIOS
注意,修改system_nrf532.h时有可能要在资源管理器里修改文件默认属性为可写,才能够添加配置项。
详见第二个工程例程 nRF5_SDK_15.2.0_9412b96\examples\hy-demo\1.2blinky -3leds\dev0628\s132\arm5_no_packs
编译下载后,三个灯都会依次点亮。
三,接下来,我们要搞定调试打印输出
JLINK有个非常有用的功能,就是可以通过RTT库来输出调试信息,然后用JLINK配套的工具RTT VIEWER来查看,就可以省掉一个串口输出。
viewer打开长这个样子
记得选择正确的芯片
当然,我们的开发板在烧录口也引入了一路串口,所以插上板子后会识别到一个串口,这个串口的调试信息打印我们在后续的例程讲解,这一节先通过RTT输出调试信息。
好,现在来看代码,要完成以下几个步骤:
1)copy RTT库文件
2)main.c增加头文件
#include "SEGGER_RTT.h" // added for RTT include
3)工程结构里加入文件
4) 编译环境设置添加RTT目录的路径
5)接下来 在main.c 使用RTT功能前初始化
SEGGER_RTT_Init();
就可以在想要的位置调用格式化打印
窗口可以指定,默认为0
SEGGER_RTT_printf(0,"52832 BLINK-RTT demo start. \r\n");
编译,下载,观察。
可以观察到除了闪灯之外, RTT VIEWER能观察到打印输出
二 . 下一步预备知识 : 要处理按键中断,就必须先了解52832的GPIOTE机制
可以参考文章https://blog.csdn.net/qq_33575901/article/details/90041554
GPIOTE原理
1.概念
1)nRF52832的GPIO只能作为通用的输入输出使能,它作为输入时无法产生中断的,这时候就需要通过GPIOTE实现这种效果了;
2)GPIOTE(GPIO任务和事件),是在GPIO的基础上引入任务和事件;
3)通过任务和事件来操作IO,任务是针对输出的,它可以让IO输出不同的动作,而事件针对输入,IO的状态变化会置位事件寄存器,从而产生事件中断。
2.原理
1)nRF52832的GPIOTE共有8个通道,每个通道都可以分配给一个引脚,分配的引脚可以配置为任务模式或事件模式;
2)GPIOTE每个通道都有SET、CLR和OUT任务,其中SET任务让引脚输出高电平,CLR任务让引脚输出低电平,OUT任务可以通过配置对引脚执行置位、清零和翻转操作;
3)GPIOTE每个通道的事件都可以配置为三种输入状态:上升沿、下降沿和任意电平跳变;
4)任务和事件通过CONFIG[n](n=0~7)寄存器配置,每个CONFIG[n]寄存器可配置一个对应编号OUT[n](n=0~7)任务寄存器和IN[n]事件寄存器;
5)当通过CONFIG寄存器配置某个引脚由SET、CLR和OUT任务寄存器或IN事件寄存器控制后,该引脚只能被GPIOTE模块写操作,正常的GPIO写入无效;
6)当在同一个GPIOTE通道中同时触发了有冲突的任务,这些任务的执行级别由高到低依次为:OUT-->CLR-->SET;
7)GPIOTE除了8个通道外,还包含一个PORT事件,PORT事件是多个引脚通过GPIO DETECT信号产生的事件。
GPIOTE寄存器(n=0~7)
1.OUT任务寄存器
OUT[n]:对CONFIG[n].PSEL指定的引脚进行写操作,引脚动作由CONFIG[n].POLARITY中的配置决定
2.TASKS_SET任务寄存器
TASKS_SET[n]:对CONFIG[n].PSEL指定的引脚进行写操作,引脚动作为输出高电平
3.TASKS_CLR任务寄存器
TASKS_CLR[n]:对CONFIG[n].PSEL指定的引脚进行写操作,引脚动作为输出低电平
4.EVENT_IN事件寄存器
EVENT_IN[n]:CONFIG[n].PSEL指定的引脚产生的事件
5.通用寄存器
1)INTENSET:使能中断
2)INTENCLR:禁止中断
3)CONFIG[n]:OUT[n],SET[n],CLR[n]和IN[n]的配置
我们来用GPIOTE的机制来控制blink。
主要的API接口:
3.相关库函数使用
//头文件:nrf_drv_gpiote.h
1)ret_code_t nrf_drv_gpiote_init(void)
功能:初始化GPIOTE模块
2)ret_code_t nrf_drv_gpiote_out_init(nrf_drv_gpiote_pin_t pin,nrf_drv_gpiote_out_config_t const *p_config)
功能:初始化GPIOTE输出引脚,输出引脚初始化设置中会设定引脚的动作(低电平到高电平、高电平到低电平或者翻转状态)和初始化状态(高电平或低电平)
3)void nrf_drv_gpiote_out_uninit(nrf_drv_gpiote_pin_t pin)
功能:释放GPIOTE输出引脚
4)void nrfx_gpiote_out_task_enable(nrfx_drv_gpiote_pin_t pin)
功能:使能GPIOTE输出引脚的任务模式
5)void nrfx_gpiote_out_task_disable(nrfx_drv_gpiote_pin_t pin)
功能:禁止GPIOTE输出引脚的任务模式
6)触发任务驱动引脚函数
1>nrf_drv_gpiote_set_task_trigger(nrf_drv_gpiote_pin_t pin)
功能:收到触发GPIOTE SET任务,触发后,对应的引脚输出高电平
2>nrf_drv_gpiote_clr_task_trigger(nrf_drv_gpiote_pin_t pin)
功能:收到触发GPIOTE CLR任务,触发后,对应的引脚输出低电平
3>nrf_drv_gpiote_out_task_trigger(nrf_drv_gpiote_pin_t pin)
功能:收到触发GPIOTE OUT任务,触发后,对应的引脚输出状态翻转
7)ret_code_t nrf_drv_gpiote_in_init(nrf_drv_gpiote_pin_t pin,nrf_drv_gpiote_in_config_t const *p_config,nrf_drv_gpiote_evt_handler_t evt_handler)
功能:初始化GPIOTE输入引脚,
8)void nrfx_gpiote_in_event_enable(nrfx_drv_gpiote_pin_t pin,bool int_enable)
功能:使能GPIOTE输入引脚
先初始化LED1对应的通道
static void led_blinking_setup()
{
ret_code_t err_code;
// init the GPIOTE module
err_code = nrf_drv_gpiote_init();
APP_ERROR_CHECK(err_code);
// define the GPIOTE task for output
nrf_drv_gpiote_out_config_t config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
// define the GPIOTE out PINNUMBER
err_code = nrf_drv_gpiote_out_init(LED1, &config);
APP_ERROR_CHECK(err_code);
// enable the GPIO out task model
nrf_drv_gpiote_out_task_enable(LED1);
}
/**
* @brief Function for application main entry.
*/
主循环就是触发GPIOTE任务,然后延时
int main(void)
{
ret_code_t err_code;
// Setup GPIOTE task for pin toggle.
led_blinking_setup();
while (true)
{
// Do Nothing - GPIO can be toggled without software intervention.
nrf_drv_gpiote_out_task_trigger(LED1);
nrf_delay_ms(500);
}
}
问题? GPIOTE只有通道0-7,7以外的怎么用?
可以使用Port event
Port event是所有GPIO DETECT信号的组合事件,每个GPIO都可以触发GPIOTE Port event。
但因为事件公用,所以不能同时触发多个GPIO。
处理技巧
5.3.1 如果不在乎功耗,那么很简单,配置p_config->hi_accuracy = true使用pin event
5.3.2 如果功耗非常重要,并且没有多个GPIO同时中断的场景,那么配置p_config->hi_accuracy = false使用port event
5.3.3 如果功耗非常重要 , 并且需要多个GPIO同时中断,那么配置p_config->hi_accuracy = false使用port event,并且p_config->sense配置为NRF_GPIOTE_POLARITY_TOGGLE, 这样配置如果按键按下触发port event 中断后改变按键GPIO sense设置就不会持续触发DETECT信号,这时有其他GPIO中断就可以触发Port Event中断。
我们在下面一节的按键中断处理中来体会port event的用处。