2. nRF SoC架构与GPIO外设驱动

nRF52/nRF53系列SoC是Nordic蓝牙传感终端的核心器件,其内部结构可以概括为“一颗Cortex-M内核 + 总线矩阵 + 多类外设 + 电源与时钟管理”的组合。理解这套架构,是写出可靠GPIO驱动、按键扫描和低功耗引脚管理代码的前提。本节以nRF52832(Cortex-M4F)和nRF5340(Cortex-M33)为主线,说明内核、内存映射、GPIO寄存器操作、按键与LED驱动、中断向量表配置以及低功耗引脚状态管理。

一、内核与总线结构

nRF52832采用ARM Cortex-M4F内核,主频64MHz,带单精度浮点单元(FPU)和DSP指令;nRF5340的应用核为Cortex-M33,主频128MHz,带TrustZone安全扩展,网络核为Cortex-M33。两者都遵循ARMv7-M/ARMv8-M的存储器映射规范:代码区从0x0000_0000开始,SRAM从0x2000_0000开始,外设寄存器区位于0x4000_0000以上。Nordic在此基础上把外设进一步细分,例如GPIO的P0口基地址为0x5000_0000,P1口为0x5000_0300,这种“外设分页”布局让寄存器访问更规整。

内核通过AHB总线矩阵访问Flash、SRAM和外设。GPIO属于低速外设,挂在APB总线上,因此对GPIO寄存器的读写虽然简单,但不应在极高频率的中断中做大量位操作。理解这一点,有助于在按键消抖和LED刷新时选择合适的实现方式。

nRF SoC 架构与 GPIO 寄存器映射关系 Cortex-M4F / M33 64/128 MHz · FPU · DSP NVIC · SysTick · MPU AHB 总线矩阵 Flash / SRAM / APB Flash 0x0000_0000 向量表 + 代码 + 常量 SRAM 0x2000_0000 栈 / 堆 / 全局变量 外设寄存器区 0x4000_0000 起 GPIO P0 基址 0x5000_0000 OUT / IN / DIR / PIN_CNF OUTSET / OUTCLR / IN GPIO P1 基址 0x5000_0300 结构同 P0 nRF5340 可用 GPIOTE 基址 0x4000_6000 CONFIG / EVENTS_IN 按键中断源 NVIC 中断使能 / 优先级 向量表偏移 VTOR GPIOTE_IRQn

二、GPIO寄存器操作

nRF的GPIO寄存器设计得相当直接。以P0口为例,常用寄存器包括:OUT(输出值)、OUTSET(置位)、OUTCLR(清零)、IN(输入值)、DIR(方向)、PIN_CNF[n](每引脚配置)。其中OUTSET和OUTCLR是“写1生效”的寄存器,写1对应位置1或清0,写0无影响,这种设计避免了读-改-写竞争,在中断和主循环同时操作LED时尤其有用。

配置一个LED引脚(假设P0.17,低电平点亮)的典型代码如下:

// 方向:输出
NRF_P0->DIRSET = (1UL << 17);
// 引脚配置:输出、无上拉、标准驱动、低电平有效
NRF_P0->PIN_CNF[17] = (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) |
                     (GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos) |
                     (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) |
                     (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) |
                     (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos);
// 点亮:输出低
NRF_P0->OUTCLR = (1UL << 17);
// 熄灭:输出高
NRF_P0->OUTSET = (1UL << 17);

按键通常配置为输入并启用上拉,按下时读到低电平。例如P0.13按键:

NRF_P0->PIN_CNF[13] = (GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
                      (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
                      (GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) |
                      (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos);
// 读取
if ((NRF_P0->IN & (1UL << 13)) == 0) { /* 按下 */ }

三、按键与LED驱动

实际工程中不建议在循环里直接读引脚做业务判断,而应把“读引脚”和“消抖”分开。一种常见做法是:用GPIOTE把按键边沿变成中断,在中断里记录时间戳并置标志,主循环以10ms周期扫描标志,连续两次读到相同电平才确认状态。这样既避免中断中做长延时,又能滤除机械抖动。

LED驱动则更简单,但要注意“低电平点亮”的极性约定。建议在代码中定义宏LED_ON()和LED_OFF(),把极性封装起来,避免在业务代码里到处写OUTCLR或OUTSET。若使用PWM调光,可把LED引脚交给PWM外设,此时GPIO的PIN_CNF要配置为外设连接而非普通输出。

四、中断向量表配置

Cortex-M的向量表默认位于Flash起始地址0x0000_0000,前16个为内核异常,之后是外设中断。nRF52832的GPIOTE中断编号为6,因此向量表中偏移0x40 + 6*4 = 0x58处存放GPIOTE_IRQHandler的地址。使用Nordic SDK或nRF Connect SDK时,向量表由启动文件自动生成,开发者只需实现同名中断服务函数。

若使用Bootloader或需要重定位向量表,可写SCB->VTOR = 新基址。注意新基址必须按向量表大小对齐(通常128字节或256字节)。在nRF5340的TrustZone场景中,安全区和非安全区各有自己的向量表,配置时需区分VTOR_S和VTOR_NS。

五、低功耗引脚状态管理

低功耗是蓝牙传感终端的生命线。引脚状态管理有三个要点:第一,未使用的引脚应配置为“输入+断开”或“输出+固定电平”,避免悬空输入导致漏电;第二,已使用但空闲的引脚,若外部有上拉/下拉,应关闭内部上拉/下拉以省电;第三,进入System OFF前,所有引脚应设置为已知状态,通常通过PIN_CNF把不用的引脚设为Input + Disconnect,把需要保持的引脚设为Output + 固定电平。

一个实用的检查清单如下:

  • 未使用引脚:DIR=Input,INPUT=Disconnect,PULL=Disabled。
  • 按键引脚:DIR=Input,INPUT=Connect,PULL=Pullup,SENSE=Disabled(若不用Sense唤醒)。
  • LED引脚:DIR=Output,INPUT=Disconnect,PULL=Disabled,输出电平与熄灭状态一致。
  • 进入System OFF前:调用nrf_gpio_range_cfg_input()或逐引脚写PIN_CNF,确保无浮空输入。
若使用nRF Connect SDK,可直接调用nrf_gpio_cfg_input()、nrf_gpio_cfg_output()和nrf_gpio_cfg_default(),它们内部已处理PIN_CNF的各个位域,减少手写寄存器出错概率。

把以上内容串起来:内核通过总线访问GPIO寄存器,GPIO寄存器控制引脚方向和电平,GPIOTE把引脚边沿变成中断,NVIC管理中断优先级,低功耗管理则要求在每个电源状态切换前把引脚配置到确定状态。掌握这条链路,就能在nRF SoC上写出既功能正确又省电的按键与LED驱动。