大家好,欢迎来到IT知识分享网。
nRF52832——GPIO端口的应用
nRF52832 GPIO 端口资源描述
GPIO 称为输入输出端口,根据 nRF52832 封装最大具有 32 个 I/O 口,可以通过 P0 这样的端口访问和控制多达 32 个端口。而且每个端口都可以单独访问。特点如下:
- 最大 32 个 GPIO,分别为 P0.0~P0.31
- 具有 8 个带有模拟通道的 GPIO 口,可以用于 SAADC、COMP 和 LPCOMP 输入
- 可以配置的输入驱动强度;内部具有上拉和下拉电阻
- 可以从所有的引脚上的高电平或者低电平触发唤醒
- 任何引脚的状态变化都可以触发中断
- PPI 任务/事件系统可以使用所有引脚
- 可以通过 PPI 和 GPIOTE 通道控制一个或多个 GPIO 输出
- 所有引脚都可以单独映射到外设接口上,以实现布局灵活性
- 在 SENSE 信号上捕获的 GPIO 状态变化可以由 LATCH 寄存器存储
GPIO 端口外设最多可以实现 32 个引脚。这些引脚中的每一个都可以在 PIN_CNF[n] 寄存器中单独配置。可以通过这些寄存器配置以下参数:
- 方向
- 驱动强度
- 启用上拉和下拉电阻
- 引脚感应
- 输入缓冲区断开
- 模拟输入(适用于所选引脚)
- 输入模式
- 输出模式
- 复用模式
- 模拟通道模式
上图中的 Sense 寄存器可以捕捉 GPIO 端口状态,如果选择 LDETECT 模式,则可以把相关状态存储在 LATCH 寄存器内,结构如下:
当在任何这样配置的引脚上检测到正确的电平时,感测机制将 DETECT 信号设置为高电平。每个引脚都有一个单独的 DETECT 信号,DETECTMODE 寄存器定义的默认功能是将来自 GPIO 端口中所有引脚的 DETECT 信号被合并到一个普通的 DETECT 信号。如果在启用传感机制时满足 PIN_CNF 寄存器配置的感测条件,则检测将立即变为高电平。如果在启用传感机制之前 DETECT 信号为低,这将触发 PORT 事件。PORT 事件在后面 GPIOE 中再详细讲解。
nRF52832 GPIO 寄存器介绍
实际 nRF52832 中所有 GPIO 模块包含的寄存器都差不多,如下表:
寄存器名称 | 地址偏移 | R/W | 功能描述 |
---|---|---|---|
OUT | 0x504 | 读写 | 设置端口输出 |
OUTSET | 0x508 | 读写 | 置位端口输出高电平,写 0 无效 |
OUTCLR | 0x50C | 读写 | 置位端口输出低电平,写 0 无效 |
IN | 0x510 | 只读 | 设置端口输入 |
DIR | 0x514 | 读写 | 设置端口方向 |
DIRSET | 0x518 | 读写 | 置位端口为输入,写 0 无效 |
DIRCLR | 0x51C | 读写 | 置位端口为输出,写 0 无效 |
LATCH | 0x520 | 读写 | 传感锁存寄存器:指示哪些 GPIO 引脚符合 PIN_CNF[n].SENSE 寄存器中设置的条件 |
DETECTMODE | 0x524 | 读写 | 传感模式选择 |
PIN_CNF[n]n=0~31 | 0x700~0x77C | 读写 | 对应端口号0到31的端口设置 |
GPIO 通过寄存器配置可以配置为输出模式和输入模式。这些都可以通过官方提供的一个库(对寄存器进行了封装),我们可以很方便的进行调用操作,下面会结合寄存器和官方函数进行说明。
GPIO 端口状态的设置
输入模式:NRF_GPIO_PORT_DIR_INPUT
,输入模式可以分为上拉和下拉模式。
输出模式:NRF_GPIO_PORT_DIR_OUTPUT
,输出模式寄存器为推挽输出。
可以设置输出模式的寄存器有三个:DIR
、DIRSET
和 PIN_CNF
。其中 DIR 和 DIRSET 仅仅是进行输出设置,不配置其他参数。而 PIN_CNF 还配置了端口的其他参数。
可以设置输入模式的寄存器有三个:DIR
、DIRCLR
和 PIN_CNF
。同样,DIR 和 DIRCLR 仅仅是配置端口输出状态,不配做其他参数。同时输入模式提供寄存器 IN 读取 IO 口输入电平状态。
- DIR 寄存器:配置 IO 端口输入输出方向:
- DIRSET 寄存器:配置 IO 端口为输出管脚
- DIRCLR 寄存器:配置 IO 端口为输入管脚
- PIN_CNF 寄存器:配置 IO 端口状态
关于 PIN_CNF 配置寄存器的说明: - 0-bit 为 DIR 方向位,设置 IO 管脚为输入引脚或者输出引脚。当为 0 时设置引脚为输入引脚。当设置为 1 时为输出管脚。函数对应
nrf_gpio_port_dir_t
- 1-bit 为 INPUT 输入位,如果设置了 IO 端口为输入端口,该位用于设置输入是否接入缓冲,复位默认为 1,所有默认都是端口连接输入缓冲的。对应结构体
nrf_gpio_pin_input_t
- 2~3-bit PULL 为输入是否开上拉电阻。当设置为 0 时没有上下拉,设置为 1 时为开下拉电阻,设置为 3 时为开上拉。
nrf_gpio_pin_pull_t
为对应的结构体。 - 8~9-bit 为 DRIVE 驱动位,该位为设置 IO 端口输出的驱动强度,对应输出 IO 端口输出强度可编程配置。
- 16~17-bit SENSE 位,该位为感应设置位,可以设置感应外部信号,常与 GPIOE 唤醒中使用。结构体
nrf_gpio_pin_sense_t
。
使用 PIN_CNF[n] 寄存器进行操作数配置的代码示例如下:
__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) //GPIO端口状态配置 {
//配置端口 NRF_GPIO_Type * reg = nrf_gpio_pin_port_decode(&pin_number); //配置对应的端口的状态 reg->PIN_CNF[pin_number] = ((uint32_t)dir << GPIO_PIN_CNF_DIR_Pos)//方向 | ((uint32_t)input << GPIO_PIN_CNF_INPUT_Pos)//输入缓冲 | ((uint32_t)pull << GPIO_PIN_CNF_PULL_Pos)//上拉配置 | ((uint32_t)drive << GPIO_PIN_CNF_DRIVE_Pos)//驱动能力配置 | ((uint32_t)sense << GPIO_PIN_CNF_SENSE_Pos)//感应能力 }
GPIO 输出配置函数:
__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); }
GPIO 输入配置函数:
__STATIC_INLINE void nrf_gpio_cfg_input(uint32_t pin_number, nrf_gpio_pin_pull_t pull_config) {
nrf_gpio_cfg(pin_number, NRF_GPIO_PIN_DIR_INPUT, NRF_GPIO_PIN_INPUT_CONNECT, pull_config, NRF_GPIO_PIN_S0S1, NRF_GPIO_PIN_NOSENSE); }
GPIO 输出设置
GPIO 可以设置输出高电平和低电平,对应输出状态的配置,可以看到 GPIO 寄存器列表中分配三个寄存器:
- OUT 寄存器
- OUTSET 寄存器
- OUTCLR 寄存器
这三个寄存器都是可读可写寄存器,32位,每一位代表一个 IO 端口,按位进行配置。
nRF52832 GPIO 输出应用
点亮第一个 LED 灯
硬件部分
Keil 工程搭建
led.c 的实例代码如下:
void LED_Init(void) {
nrf_gpio_cfg_output(LED_0); nrf_gpio_cfg_output(LED_1); nrf_gpio_cfg_output(LED_2); nrf_gpio_cfg_output(LED_3);//配置 IO 端口为输出状态 } //操作LED1 点亮,LED1 输出低电平 void LED1_Open(void) {
nrf_gpio_pin_clear(LED_0); } //操作LED1 灭,LED1 输出高电平 void LED1_Close(void) {
nrf_gpio_pin_set(LED_0); } //LED0 状态翻转 void LED1_Toggle(void) {
nrf_gpio_pin_toggle(LED_0); }
上面的代码分别调用了以下的库函数,其实这些库函数内部也是操作对应的寄存器,为了方便我们的使用将这部分寄存器配置过程进行了封装。这里我们可以具体看一下。
nrf_gpio_pin_clear(uint32_t pin_number); nrf_gpio_pin_set(uint32_t pin_number); nrf_gpio_pin_toggle(uint32_t pin_number);
nrf_gpio_pin_clear()
函数内部会发现是如下的操作:
p_reg->OUTCLR = clr_mask;
也就是说要 IO 管脚输出为 0,实际上是直接设置 OUTCLR 寄存器为高。同理,nrf_gpio_pin_set()
函数就是设置 OUTSET 寄存器;nrf_gpio_pin_toggle()
就是设置 OUTCLR 和 OUTSET 寄存器。
对于主函数调用的编写就很简单了。只需要调用对应的操作函数即可完成对应的操作调用。
#include "nrf_gpio.h" #include "nrf_delay.h" #include "led.h" int main(void) {
//初始化 LED 灯 LED_Init(); while(true) {
LED1_Open(); nrf_delay_ms(500);//延迟500ms LED1_Close(); nrf_delay_ms(500);//延迟500ms } }
编译代码后,使用 Keil 把工程目标文件下载到 nRF52832 开发板上。LED 灯就会开始闪烁。
蜂鸣器驱动
硬件设计
蜂鸣器常用分为:
- 有源蜂鸣器:内部带有震荡源,只要通电就会发出响声
- 无源蜂鸣器:内部不带震荡源,使用直流信号无法响动,必须使用 2K-5K 的方波驱动使其响动。
这里的“源”并不是指电源,而是指震荡源。所以有源蜂鸣器比无源蜂鸣器的成本要高很多,因为里面多了一个振荡电路。
程序编写
通过上面的分析,有源蜂鸣器的驱动实际上非常简单,类似前面的 LED 驱动,只需要使得 GPIO 端口输出高电平就可以使得蜂鸣器鸣叫。
实现要求的配置代码如下:
- 首先采用函数
nrf_gpio_cfg_output
设置 GPIO 为普通驱动能力的输出 GPIO - 打开蜂鸣器采用组件库函数
nrf_gpio_pin_set
函数,关闭蜂鸣器采用nrf_gpio_pin_clear
函数 - 主函数循环开关蜂鸣器,中间添加延迟操作
具体代码如下:
#define BEEP 12 //定义蜂鸣器的管脚 //初始化蜂鸣器 void BEEP_Init(void) {
//配置蜂鸣器驱动 GPIO 为输出模式 nrf_gpio_cfg_output(BEEP); } //蜂鸣器开启 void BEEP_Open(void) {
nrf_gpio_pin_set(BEEP); } //蜂鸣器停止 void BEEP_Close(void) {
nrf_gpio_pin_clear(BEEP); }
main.c 主函数调用方式如下:
#include <stdbool.h> #include <stdint.h> #include "nrf_gpio.h" #include "nrf_delay.h" #include "led.h" int main() {
//初始化LED灯 LED_Init(); BEEP_Init(); //循环打开和关闭蜂鸣器 while(true) {
LED1_Toggle(); BEEP_Open(); nrf_delay_ms(800); BEEP_Close(); LED1_Toggle(); nrf_delay_ms(800); } return 0; }
测试验证
编译代码后,将 Keil 工程目标文件下载到 nRF52832 开发板上,同时把 P19 上的跳线帽 4-6 进行短接。运行后,LED 闪烁并且蜂鸣器会跟随响动。
nRF52832 GPIO 输入应用
GPIO 输入扫描流程
GPIO 的输入配置首先需要配置 PIN_CNF[n] 寄存器的是三个域,如 GPIO 结构体如下图:
- PIN_CNF[n].DIR:该位决定 IO 端口方向为输入
- PIN_CNF[n].INPUT:该位决定是否接入缓冲。输入缓冲的作用和连接一个电阻类似,降低电压波动幅度过大对输入管脚的影响。
- PIN_CNF[n].PULL:该位决定是否进行上下拉,上下拉的作用主要就是用于输入端口的电平维持,保存信号稳定,方便处理器进行检测。
在 nrf52xx 的组件库中,提供了一个 API 函数 nrf_gpio_cfg_input
来完成 IO 口输入状态的设置,该函数就是调用前面讲解 nrf_gpio_cfg
函数,代码如下:
__STATIC_INLINE void nrf_gpio_cfg_input(uint32_t pin_number, nrf_gpio_pin_pull_t pull_config) {
nrf_gpio_cfg(pin_number, //配置IO端口号 NRF_GPIO_PIN_DIR_INPUT,//配置为输入 NRF_GPIO_PIN_INPUT_CONNECT,//默认上接输入缓冲 pull_config, NRF_GPIO_PIN_S0S1, NRF_GPIO_PIN_NOSENSE); }
if(nrf_gpio_pin_read(13)==0)
这样可以判断 13 管脚是否输入为低电平。
机械按键输入扫描
硬件设计
程序的编写
- 初始化开发板上的按键
- 扫描判断按键是否有按下,按键扫描是通过 MCU 不停判断端口的状态来实现
#include "key.h" void KEY_Init() {
nrf_gpio_cfg_input(16, NRF_GPIO_PIN_PULLUP);//设置管脚上拉输入,按键按下接通 GND,输入拉低 nrf_gpio_cfg_input(17, NRF_GPIO_PIN_PULLUP);//同16管脚相同 } //循环等待延迟函数 void Delay(uint32_t temp) {
for(; temp !=0; temp--) } //按键按下判断函数 uint8_t KEY1_Down() {
if(nrf_gpio_pin_read(KEY_1) == 0) {
//延时消抖 Delay(10000); if(nrf_gpio_pin_read(KEY_1) == 0) {
//等待按键释放,只识别单次按下,不区分长短按键 while(nrf_gpio_pin_read(KEY_1)==0); return 0; } else return 1; } else return 1; }
主函数的操作就很简单了。下面 main.c
:
#include "nrf52.h" #include "nrf_gpio.h" #include "led.h" #include "key.h" int main() {
LED_Init();//LED初始化 KEY_Init();//按键初始化 while(1) {
if(KEY1_Down() == 0)//判断按键是否按下 {
LED_Toggle();//按键按下,LED状态翻转 } } return 0; }
电容触摸按键的应用
硬件设计
电容触摸按键可以穿透绝缘材料 20mm 以上,准确无误地侦测到手指的有效触摸。并保证了产品的灵敏度、稳定性、可靠性等不会因环境条件的改变或长期使用而发生变化,并具有防水和强抗 干扰能力,超强防护,超强适应温度范围。电容式触摸按键没有任何机械部件,不会磨损,无限寿命,减少后期维护成本。电容式触摸按键面板图案、按键大小、形状任意设计,字符、商标、透视窗LED 透光等任意搭配,外型美观、时尚,不褪色、不变形、经久耐用。因此在一下家电、手持设备上为了防止进水、腐蚀等情况发生,常常采用电容触摸按键。
管脚 | 名称 | 功能 |
---|---|---|
1 | OUT | 触摸芯片输出端,接处理器的输入端 |
2 | GND | 接地端 |
3 | SO | 接触摸 TOUCH 信号 |
4 | SLH | 高低电平选择输出:SHL=0 高电平输出,SHL=1 低电平输出 |
5 | VDD | 电源,范围 2.0-5.5 |
6 | STG | 触发模式选择:STG=0 直接模式,STG=1 触发模式 |
管脚 1 接 TK 通过 P19 的管脚 2 和管脚 4 相连,连接芯片的 P0.12 端;VDD 接 3.3v 电源;STG 接高电平,设置为触发模式;SDH 接低电平,设置为高电平输出。触 摸板 TK1 要接电容 C28 进行灵敏度调节,电容范围为 0~50p。
程序编写
电容触摸按键的应用和机械按键类似,只要检测到 GPIO 管脚上有高电平输入节课认为是有按键按下事件产生。因此我们可以再按键扫描的代码中添加这部分操作,原理和机械按键类似。具体如下:
nrf_gpio_cfg_input(12, NRF_GPIO_PIN_PULLUP);//初始化 TOUCH 的管脚为输入 //电容按键检测 uint8_t TCH_Down() {
//检测是否按键有按下 if(nrf_gpio_pin_read(TCH) == 1) {
//延时消抖 Delay(10000); if(nrf_gpio_pin_read(TCH) == 1) {
//等待按键释放 while(nrf_gpio_pin_read(TCH) == 1); return 0; } else return 1; } else return 1; }
main.c
操作内容跟之前一样。
#include "nrf52.h" #include "nrf_gpio.h" #include "led.h" #include "key.h" int main() {
LED_Init();//LED初始化 KEY_Init();//按键初始化 while(1) {
if(TCH_Down() == 0)//判断按键是否按下 {
LED_Toggle();//按键按下,LED状态翻转 } } return 0; }
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/130581.html