#ifndef __LED_H
#define __LED_H
#include "nrf52840.h"
#define LED_0 NRF_GPIO_PIN_MAP(0,13)
#define LED_1 NRF_GPIO_PIN_MAP(0,14)
#define LED_2 NRF_GPIO_PIN_MAP(0,15)
#define LED_3 NRF_GPIO_PIN_MAP(0,16)
void LED_Init(void);
void LED1_Open(void);
void LED1_Close(void);
void LED1_Toggle(void);
void LED2_Open(void);
void LED2_Close(void);
void LED2_Toggle(void);
void LED3_Open(void);
void LED3_Close(void);
void LED3_Toggle(void);
#endif /* __LED_H */
led.c文件
#include "nrf52840.h"
#include "nrf_gpio.h"
#include "led.h"
#include <stdio.h>
/*
#define ITM_PORT8(n) (*(volatile unsigned char *)(0xe0000000 + 4*(n)))
#define ITM_PORT16(n) (*(volatile unsigned short *)(0xe0000000 + 4*(n)))
#define ITM_PORT32(n) (*(volatile unsigned long *)(0xe0000000 + 4*(n)))
#define DEMCR (*(volatile unsigned long *)(0xE000EDFC))
#define TRCENA 0X01000000
int fputc(int ch, FILE *f)
{
if(DEMCR & TRCENA)
{
while(ITM_PORT32(0) == 0);
ITM_PORT8(0) = ch;
}
return ch;
}
*/
void LED_Init(void)
{
// Configure LED-pins as outputs
nrf_gpio_cfg_output(LED_0);
nrf_gpio_cfg_output(LED_1);
nrf_gpio_cfg_output(LED_2);
}
void LED1_Open(void)
{
nrf_gpio_pin_clear(LED_0);//设置低电平
}
void LED1_Close(void)
{
nrf_gpio_pin_set(LED_0);//高电平
}
void LED1_Toggle(void)
{
nrf_gpio_pin_toggle(LED_0);
}
void LED2_Open(void)
{
nrf_gpio_pin_clear(LED_1);
}
void LED2_Close(void)
{
nrf_gpio_pin_set(LED_1);
}
void LED2_Toggle(void)
{
nrf_gpio_pin_toggle(LED_1);
}
void LED3_Open(void)
{
nrf_gpio_pin_clear(LED_2);
}
void LED3_Close(void)
{
nrf_gpio_pin_set(LED_2);
}
void LED3_Toggle(void)
{
nrf_gpio_pin_toggle(LED_2);
}
二、代码解析(有些地方看文档不懂,是本人胡乱解析的,不对别喷,谢谢!)
1.NRF_GPIO_PIN_MAP是一个带参数的宏,在nrf_gpio.h中定义如下:
#define NRF_GPIO_PIN_MAP(port, pin) (((port) << 5) | ((pin) & 0x1F))
其用途是把端口号与引脚号映射成nrf_gpio函数能理解的值。在 Nordic 的 nRF 系列芯片里,
GPIO 引脚通常用一个 32 位整数来表示,这个整数融合了端口号和引脚号的信息。
(port) << 5) | ((pin) & 0x1F):使用按位或操作符将移位后的端口号和引脚号组合起来,从而得到一个唯一的整数值,该值可被nrf_gpio函数使用。为了以后看的时候省一点时间,下面用图表说明一下,比如端口号port=0xb(二进制为1011),存储形式如下:




__STATIC_INLINE void nrf_gpio_cfg_output(uint32_t pin_number)
{
nrf_gpio_cfg(
pin_number,
NRF_GPIO_PIN_DIR_OUTPUT,
NRF_GPIO_PIN_INPUT_DISCONNECT,
NRF_GPIO_PIN_NOPULL,
NRF_GPIO_PIN_S0S1,
NRF_GPIO_PIN_NOSENSE);
}
这个函数传入要设置的引脚编号,再通过nrf_gpio_cfg函数对该引脚进行设置。
__STATIC_INLINE:这是一个预处理宏,其作用是将函数定义为静态内联函数。静态函数意味着该函数的作用域仅限于当前文件,内联函数则是建议编译器在调用该函数的地方直接嵌入函数体代码,从而避免函数调用带来的开销。
nrf_gpio_cfg(
pin_number,
NRF_GPIO_PIN_DIR_OUTPUT,
NRF_GPIO_PIN_INPUT_DISCONNECT,
NRF_GPIO_PIN_NOPULL,
NRF_GPIO_PIN_S0S1,
NRF_GPIO_PIN_NOSENSE)
参数含义是:nrf_gpio_cfg(引脚,方向,输入模式,上下拉,驱动能力,电平检测功能),从6个参数对此引脚进行设置。
3.nrf_gpio_cfg函数是nrf_gpio.h中定义的一个函数,其代码如下 :
__STATIC_INLINE void nrf_gpio_cfg(//引脚,方向,输入模式,上下拉,驱动能力,电平检测功能
uint32_t pin_number,
nrf_gpio_pin_dir_t dir,
nrf_gpio_pin_input_t input,
nrf_gpio_pin_pull_t pull,
nrf_gpio_pin_drive_t drive,
nrf_gpio_pin_sense_t sense)
{
NRF_GPIO_Type * reg = nrf_gpio_pin_port_decode(&pin_number);//得到pin_number所属的端口
reg->PIN_CNF[pin_number] = ((uint32_t)dir << GPIO_PIN_CNF_DIR_Pos)//第0位开始,(0)
| ((uint32_t)input << GPIO_PIN_CNF_INPUT_Pos)//第1位开始(1)
| ((uint32_t)pull << GPIO_PIN_CNF_PULL_Pos)//第2位开始 (3..2)
| ((uint32_t)drive << GPIO_PIN_CNF_DRIVE_Pos)//第8位开始(10..8)
| ((uint32_t)sense << GPIO_PIN_CNF_SENSE_Pos);//第16位开始(17..16)
}
其主要功能是对指定 GPIO(通用输入输出)引脚的各项配置参数进行设置。通过这个函数,
可以灵活地配置 GPIO 引脚的方向、输入模式、上拉 / 下拉电阻、驱动能力以及电平检测功能等。
参数说明:
uint32_t pin_number:要配置的 GPIO 引脚编号,是一个无符号 32 位整数。
nrf_gpio_pin_dir_t dir:指定 GPIO 引脚的方向,例如输入或输出模式。nrf_gpio_pin_dir_t 是一个自定义的枚举类型,用于表示不同的引脚方向。(枚举值为NRF_GPIO_PIN_DIR_INPUT,NRF_GPIO_PIN_DIR_OUTPUT)
nrf_gpio_pin_input_t input:设置引脚的输入模式,比如是否断开输入等。nrf_gpio_pin_input_t 是一个自定义的枚举类型。(枚举值为NRF_GPIO_PIN_INPUT_CONNECT ,NRF_GPIO_PIN_INPUT_DISCONNECT )
nrf_gpio_pin_pull_t pull:配置引脚的上拉或下拉电阻,例如是否启用上拉、下拉或不使用上拉 / 下拉电阻。nrf_gpio_pin_pull_t 是一个自定义的枚举类型。(枚举值为NRF_GPIO_PIN_NOPULL,NRF_GPIO_PIN_PULLDOWN,NRF_GPIO_PIN_PULLUP)
nrf_gpio_pin_drive_t drive:设置引脚的驱动能力,例如强驱动、弱驱动等。nrf_gpio_pin_drive_t 是一个自定义的枚举类型。枚举值如下:
NRF_GPIO_PIN_S0S1 = GPIO_PIN_CNF_DRIVE_S0S1 ,
NRF_GPIO_PIN_H0S1 = GPIO_PIN_CNF_DRIVE_H0S1 ,
NRF_GPIO_PIN_S0H1 = GPIO_PIN_CNF_DRIVE_S0H1 ,
NRF_GPIO_PIN_H0H1 = GPIO_PIN_CNF_DRIVE_H0H1 ,
NRF_GPIO_PIN_D0S1 = GPIO_PIN_CNF_DRIVE_D0S1 ,
NRF_GPIO_PIN_D0H1 = GPIO_PIN_CNF_DRIVE_D0H1 ,
NRF_GPIO_PIN_S0D1 = GPIO_PIN_CNF_DRIVE_S0D1 ,
NRF_GPIO_PIN_H0D1 = GPIO_PIN_CNF_DRIVE_H0D1 ,
NRF_GPIO_PIN_E0E1 = GPIO_PIN_CNF_DRIVE_E0E1
nrf_gpio_pin_sense_t sense:配置引脚的电平检测功能,例如是否检测高电平、低电平或不进行检测。nrf_gpio_pin_sense_t 是一个自定义的枚举类型。
枚举值如下:
NRF_GPIO_PIN_NOSENSE = GPIO_PIN_CNF_SENSE_Disabled ,
NRF_GPIO_PIN_SENSE_LOW = GPIO_PIN_CNF_SENSE_Low ,
NRF_GPIO_PIN_SENSE_HIGH = GPIO_PIN_CNF_SENSE_High
nrf_gpio_pin_port_decode 是一个函数,其功能是对 pin_number 所属的端口进行解码,返回引脚对应的端口。
reg 是一个指向 NRF_GPIO_Type 类型结构体的指针,该结构体代表 GPIO 端口的寄存器映射。
reg->PIN_CNF[pin_number]:这是 GPIO 端口的引脚配置寄存器,用于对指定引脚的各项配置进行设置。
((uint32_t)dir << GPIO_PIN_CNF_DIR_Pos):把 dir 的值左移 GPIO_PIN_CNF_DIR_Pos 位,从而将其放置到寄存器里对应的位置。
|:按位或运算符,用于把各个配置项组合到一起。
同样的操作也会对 input、pull、drive 和 sense 进行,最终将它们组合后写入到 PIN_CNF 寄存器。此函数的作用是依据传入的参数对指定 GPIO 引脚的配置寄存器进行设置,
从而实现对引脚的方向、输入模式、上下拉电阻、驱动能力以及电平检测功能等的配置。
下面对nrf_gpio_pin_port_decode这个函数进行说明,它 是nrf_gpio.h中定义的一个函数,其代码如下:
__STATIC_INLINE NRF_GPIO_Type * nrf_gpio_pin_port_decode(uint32_t * p_pin)//decode:解码,uint32_t * p_pin是一个指向无符号 32 位整数的指针。该指针指向要解析的 GPIO 引脚编号。
{ //此函数用于解码 GPIO 引脚所属的端口,引脚编号个人理解:00000000 00000000 00000000 00000000~00000000 00000000 00000000 00011111(0-31)
NRFX_ASSERT(nrf_gpio_pin_present_check(*p_pin));//断言
#if (GPIO_COUNT == 1)
return NRF_P0;
#else
if (*p_pin < P0_PIN_NUM)
{
return NRF_P0;
}
else
{
*p_pin = *p_pin & 0x1F;//0001 1111,比如:pin为34号引脚,已经超出P0拥有的引脚数0-31了,32,33,34,根当于P1口的0,1,2号引脚。
return NRF_P1;
}
#endif
}
其主要功能是根据传入的 GPIO 引脚编号,解析出该引脚所属的 GPIO 端口寄存器,并返回对应的寄存器指针。
NRFX_ASSERT:这是一个断言宏,用于在调试阶段进行错误检查。如果断言条件不满足,程序会触发断言失败,通常会终止程序运行并给出错误信息。
nrf_gpio_pin_present_check(*p_pin):调用 nrf_gpio_pin_present_check 函数,传入 *p_pin(即指针所指向的引脚编号)作为参数。该函数的作用是检查传入的引脚编号是否有效,如果无效则会触发断言失败。
#if (GPIO_COUNT == 1):这是一个条件编译指令,GPIO_COUNT 是一个宏定义,表示系统中 GPIO 端口的数量。如果 GPIO_COUNT 等于 1,说明系统中只有一个 GPIO 端口。
return NRF_P0;:如果只有一个 GPIO 端口,那么所有的引脚都属于该端口,直接返回 NRF_P0 指针,NRF_P0 通常是指向第一个 GPIO 端口寄存器的指针。
else:如果系统中有多个 GPIO 端口。
if (*p_pin < P0_PIN_NUM):检查传入的引脚编号是否小于 P0_PIN_NUM。P0_PIN_NUM 是一个宏定义,表示第一个 GPIO 端口(通常是 P0)的引脚数量。
return NRF_P0;:如果引脚编号小于 P0_PIN_NUM,说明该引脚属于第一个 GPIO 端口(P0),返回 NRF_P0 指针。
else:如果引脚编号大于等于 P0_PIN_NUM,说明该引脚属于第二个 GPIO 端口(通常是 P1)。
*p_pin = *p_pin & 0x1F;:将引脚编号与 0x1F(二进制 0001 1111)进行按位与操作。这一步的作用是将引脚编号映射到第二个 GPIO 端口的有效范围内,因为第二个 GPIO 端口的引脚编号通常是从 0 开始重新编号的。比如:pin为34号引脚,已经超出P0拥有的引脚数0-31了,32,33,34,相当于P1口的0,1,2号引脚。
return NRF_P1;:返回 NRF_P1 指针,NRF_P1 通常是指向第二个 GPIO 端口寄存器的指针。
总结
该函数通过检查传入的 GPIO 引脚编号,结合系统中 GPIO 端口的数量,判断该引脚所属的 GPIO 端口,并返回对应的寄存器指针。同时,在调试阶段会对引脚编号的有效性进行检查,确保程序的健壮性。
现在,再回到上一函数nrf_gpio_cfg,继续说明:
NRF_GPIO_Type * reg = nrf_gpio_pin_port_decode(&pin_number);//得到pin_number所属的端口寄存器指针。
NRF_GPIO_Type结构体定义如下 :
typedef struct { /*!< (@ 0x50000000) P0 Structure */
__IM uint32_t RESERVED[321];
__IOM uint32_t OUT; /*!< (@ 0x00000504) Write GPIO port */
__IOM uint32_t OUTSET; /*!< (@ 0x00000508) Set individual bits in GPIO port */
__IOM uint32_t OUTCLR; /*!< (@ 0x0000050C) Clear individual bits in GPIO port */
__IM uint32_t IN; /*!< (@ 0x00000510) Read GPIO port */
__IOM uint32_t DIR; /*!< (@ 0x00000514) Direction of GPIO pins */
__IOM uint32_t DIRSET; /*!< (@ 0x00000518) DIR set register */
__IOM uint32_t DIRCLR; /*!< (@ 0x0000051C) DIR clear register */
__IOM uint32_t LATCH; /*!< (@ 0x00000520) Latch register indicating what GPIO pins that
have met the criteria set in the PIN_CNF[n].SENSE
registers */
__IOM uint32_t DETECTMODE; /*!< (@ 0x00000524) Select between default DETECT signal behavior
and LDETECT mode */
__IM uint32_t RESERVED1[118];
__IOM uint32_t PIN_CNF[32]; /*!< (@ 0x00000700) Description collection: Configuration of GPIO
pins */
} NRF_GPIO_Type;
这个结构体的成员就是GPIO端口的寄存器,通过访问成员,可以设置相应寄存器。
其中PIN_CNF寄存器有32个,分别对应32个引脚,比如PIN_CNF[0]是个32位寄存器,用来配置第0位引脚,其内容如下:
所以GPIO_PIN_CNF_DIR_Pos=0,GPIO_PIN_CNF_INPUT_Pos=1,
GPIO_PIN_CNF_PULL_Pos=2,GPIO_PIN_CNF_DRIVE_Pos=8,
GPIO_PIN_CNF_SENSE_Pos=16
通过下面的移位,按位或运算,将各数据连接成到一个32位的整型数,写入PIN_CNF寄存器。
reg->PIN_CNF[pin_number] = ((uint32_t)dir << GPIO_PIN_CNF_DIR_Pos)//第0位开始,(0)
| ((uint32_t)input << GPIO_PIN_CNF_INPUT_Pos)//第1位开始(1)
| ((uint32_t)pull << GPIO_PIN_CNF_PULL_Pos)//第2位开始 (3..2)
| ((uint32_t)drive << GPIO_PIN_CNF_DRIVE_Pos)//第8位开始(10..8)
| ((uint32_t)sense << GPIO_PIN_CNF_SENSE_Pos);//第16位开始(17..16)
这样就完成了对相应引脚的各种状态的设置。
4. nrf_gpio_pin_set函数
这是nrf_gpio.h中定义的一个函数,其代码如下 :
__STATIC_INLINE void nrf_gpio_pin_set(uint32_t pin_number)
{
NRF_GPIO_Type * reg = nrf_gpio_pin_port_decode(&pin_number);
nrf_gpio_port_out_set(reg, 1UL << pin_number);
}
其中nrf_gpio_port_out_set也是nrf_gpio.h中定义的一个函数,其代码如下:
__STATIC_INLINE void nrf_gpio_port_out_set(NRF_GPIO_Type * p_reg, uint32_t set_mask)
{
p_reg->OUTSET = set_mask;
}
将OUTSET相应引脚位设为1.(高电平)
5.nrf_gpio_pin_clear函数
这是nrf_gpio.h中定义的一个函数,其代码如下:
__STATIC_INLINE void nrf_gpio_pin_clear(uint32_t pin_number)
{
NRF_GPIO_Type * reg = nrf_gpio_pin_port_decode(&pin_number);//解码引脚
nrf_gpio_port_out_clear(reg, 1UL << pin_number);
}
nrf_gpio_port_out_clear也是nrf_gpio.h中定义的一个函数,其代码如下:
__STATIC_INLINE void nrf_gpio_port_out_clear(NRF_GPIO_Type * p_reg, uint32_t clr_mask)
{
p_reg->OUTCLR = clr_mask;//OUTCLR::32位寄存器
}
将相应引脚位设为1,(低电平)。