将内部 STM32WL Sub-GHz 无线电接口信号映射到 GPIO 引脚

简介

STM32WL系列MCU上的其他外设不同,用户无法通过一组内存映射寄存器与Sub-GHz无线电进行交互。而是可以使用专用的内部SPI接口(称为SUBGHZSPI)与射频子系统进行通信,如图1中突出显示的部分所示。因此,无线电系统的固件可以采用与外部收发器的固件相同的设计方式。同样,这样也可大大简化基于标准MCU和外部收发器的现有应用的迁移过程。然而,当在固件开发过程中出现需要调试系统的问题时,客户可能会想知道是否可以使用外部工具(例如逻辑分析仪或示波器)来检查这些内部通信信号以及具体的操作。答案就是配置一些额外的GPIO引脚即可。


1 :STM32WL55/54xx块状图中专门用于 Sub-GHz无线电系统的内部SPI接口。源自DS13293中的图1。

配置 GPIO 引脚

出于调试目的,到底有哪些内部信号可以变成外部信号?图2显示了 Sub-GHz无线电块状图的右侧中最重要的信号。它们分别是SUBGHZSPI(NSS、SCK、MISO和MOSI)信号、BUSY信号、中断(IRQ0、IRQ1和IRQ2)信号和HSERDY信号。图上未显示但也存在的信号包括NRESET、SMPSRDY和LDORDY信号。请参阅参考手册,了解有关这些信号功能的详细文档。


2 : Sub-GHz无线电块状图。见RM0453中的图9。

下面的表1列出了所有可用信号、它们对应的GPIO引脚,以及激活这些信号以用于STM32WL55JC器件内部无线电接口所需的替代功能值。我们选择这台器件进行演示的原因是,它可以在Nucleo-WL55JC评估板上使用(在撰写本文时,这是唯一可用的STM32WL Nucleo板)。如需了解这些配置的来源,请参阅该器件规格书中的表20。当然,如果你使用的是其他器件,请务必参考规格书。

1 :与STM32WL Sub-GHz无线电外设交互使用的内部信号,以及用于将它们变成外部信号的GPIO引脚和替代功能组合。

信号 引脚 替代功能
DEBUG_SUBGHZSPI_NSSOUT PA4 AF13
DEBUG_SUBGHZSPI_SCKOUT PA5 AF13
DEBUG_SUBGHZSPI_MISOOUT PA6 AF13
DEBUG_SUBGHZSPI_MOSIOUT PA7 AF13
DEBUG_RF_HSE32RDY PA10 AF13
DEBUG_RF_NRESET PA11 AF13
DEBUG_RF_SMPSRDY PB2 AF13
DEBUG_RF_LDORDY PB4 AF13
RF_BUSY PA12 AF6
RF_IRQ0 PB3 AF6
RF_IRQ1 PB5 AF6
RF_IRQ2 PB8 AF6

注意:PB3引脚上还有另一个名为DEBUG_RF_DTB1 的DEBUG_RF信号,其替代功能值为AF13。该信号未包含在表1中,原因如下。1) 该信号与RF_IRQ0 冲突;2)(据我所知)该信号未被记录。

调试GPIO引脚的配置与其他GPIO引脚相同。最简单、最常见的配置方法是使用HAL库,如列表1所示。由于这段代码旨在用作参考,因此配置了表1中的所有引脚。在某些应用中可能无法实现这一点,因为其中的一些引脚被用于其他目的,可能会导致冲突。你可以配置哪些引脚以及在代码中的哪个位置进行配置,这完全取决于你的应用特定的调试需求。

列表 1 :使用HAL库配置GPIO引脚,以映射内部 Sub-GHz无线电接口信号的值。

GPIO_InitTypeDef GPIO_InitStruct = {0};

// Enable GPIO Clocks
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();

// DEBUG_SUBGHZSPI_{NSSOUT, SCKOUT, MSIOOUT, MOSIOUT} pins
GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF13_DEBUG_SUBGHZSPI;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// DEBUG_RF_{HSE32RDY, NRESET} pins
GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_11;
GPIO_InitStruct.Alternate = GPIO_AF13_DEBUG_RF;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// DEBUG_RF_{SMPSRDY, LDORDY} pins
GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_4;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

// RF_BUSY pin
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Alternate = GPIO_AF6_RF_BUSY;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// RF_{IRQ0, IRQ1, IRQ2} pins
GPIO_InitStruct.Pin = GPIO_PIN_3 | GPIO_PIN_5 | GPIO_PIN_8;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

如果HAL库不可用(或者只是你不想用),则可使用列表2中提供的代码,该代码在功能上与列表1中的代码相同。不同之处在于前者直接配置了GPIO寄存器,而不依赖于硬件抽象。

列表 2 :通过直接修改寄存器来配置GPIO引脚,以映射内部 Sub-GHz无线电接口信号的值。

// Enable GPIO Clocks
SET_BIT(RCC->AHB2ENR, (RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOBEN)); // Enable IO port A and B clocks
__asm("nop"); // After the enable bit is set, there is a two clock cycles delay
__asm("nop"); // before the clock is active in the peripheral (7.2.21 in RM0453).

// Configure GPIO
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT4); // output push-pull
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT5); // output push-pull
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT6); // output push-pull
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT7); // output push-pull
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT10); // output push-pull
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT11); // output push-pull
CLEAR_BIT(GPIOB->OTYPER, GPIO_OTYPER_OT2); // output push-pull
CLEAR_BIT(GPIOB->OTYPER, GPIO_OTYPER_OT4); // output push-pull
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT12); // output push-pull
CLEAR_BIT(GPIOB->OTYPER, GPIO_OTYPER_OT3); // output push-pull
CLEAR_BIT(GPIOB->OTYPER, GPIO_OTYPER_OT5); // output push-pull
CLEAR_BIT(GPIOB->OTYPER, GPIO_OTYPER_OT8); // output push-pull

MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE4_Msk, (0b10 << GPIO_MODER_MODE4_Pos)); // Alternate function mode
MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE5_Msk, (0b10 << GPIO_MODER_MODE5_Pos)); // Alternate function mode
MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE6_Msk, (0b10 << GPIO_MODER_MODE6_Pos)); // Alternate function mode
MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE7_Msk, (0b10 << GPIO_MODER_MODE7_Pos)); // Alternate function mode
MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE10_Msk, (0b10 << GPIO_MODER_MODE10_Pos)); // Alternate function mode
MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE11_Msk, (0b10 << GPIO_MODER_MODE11_Pos)); // Alternate function mode
MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODE2_Msk, (0b10 << GPIO_MODER_MODE2_Pos)); // Alternate function mode
MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODE4_Msk, (0b10 << GPIO_MODER_MODE4_Pos)); // Alternate function mode
MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE12_Msk, (0b10 << GPIO_MODER_MODE12_Pos)); // Alternate function mode
MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODE3_Msk, (0b10 << GPIO_MODER_MODE3_Pos)); // Alternate function mode
MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODE5_Msk, (0b10 << GPIO_MODER_MODE5_Pos)); // Alternate function mode
MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODE8_Msk, (0b10 << GPIO_MODER_MODE8_Pos)); // Alternate function mode

MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD4_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD5_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD6_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD7_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD10_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD11_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOB->PUPDR, GPIO_PUPDR_PUPD2_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOB->PUPDR, GPIO_PUPDR_PUPD4_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD12_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOB->PUPDR, GPIO_PUPDR_PUPD3_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOB->PUPDR, GPIO_PUPDR_PUPD5_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOB->PUPDR, GPIO_PUPDR_PUPD8_Msk, 0); // no pull-up/down

MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED4_Msk, (0b11 << GPIO_OSPEEDR_OSPEED4_Pos)); // high output speed
MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED5_Msk, (0b11 << GPIO_OSPEEDR_OSPEED5_Pos)); // high output speed
MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED6_Msk, (0b11 << GPIO_OSPEEDR_OSPEED6_Pos)); // high output speed
MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED7_Msk, (0b11 << GPIO_OSPEEDR_OSPEED7_Pos)); // high output speed
MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED10_Msk, (0b11 << GPIO_OSPEEDR_OSPEED10_Pos)); // high output speed
MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED11_Msk, (0b11 << GPIO_OSPEEDR_OSPEED11_Pos)); // high output speed
MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED2_Msk, (0b11 << GPIO_OSPEEDR_OSPEED2_Pos)); // high output speed
MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED4_Msk, (0b11 << GPIO_OSPEEDR_OSPEED4_Pos)); // high output speed
MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED12_Msk, (0b11 << GPIO_OSPEEDR_OSPEED12_Pos)); // high output speed
MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED3_Msk, (0b11 << GPIO_OSPEEDR_OSPEED3_Pos)); // high output speed
MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED5_Msk, (0b11 << GPIO_OSPEEDR_OSPEED5_Pos)); // high output speed
MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED8_Msk, (0b11 << GPIO_OSPEEDR_OSPEED8_Pos)); // high output speed

MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFSEL4_Msk, (0xD << GPIO_AFRL_AFSEL4_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFSEL5_Msk, (0xD << GPIO_AFRL_AFSEL5_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFSEL6_Msk, (0xD << GPIO_AFRL_AFSEL6_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFSEL7_Msk, (0xD << GPIO_AFRL_AFSEL7_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOA->AFR[1], GPIO_AFRH_AFSEL10_Msk, (0xD << GPIO_AFRH_AFSEL10_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOA->AFR[1], GPIO_AFRH_AFSEL11_Msk, (0xD << GPIO_AFRH_AFSEL11_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOB->AFR[0], GPIO_AFRL_AFSEL2_Msk, (0xD << GPIO_AFRL_AFSEL2_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOB->AFR[0], GPIO_AFRL_AFSEL4_Msk, (0xD << GPIO_AFRL_AFSEL4_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOA->AFR[1], GPIO_AFRH_AFSEL12_Msk, (0x6 << GPIO_AFRH_AFSEL12_Pos)); // AF6 (RF) selected
MODIFY_REG(GPIOB->AFR[0], GPIO_AFRL_AFSEL3_Msk, (0x6 << GPIO_AFRL_AFSEL3_Pos)); // AF6 (RF) selected
MODIFY_REG(GPIOB->AFR[0], GPIO_AFRL_AFSEL5_Msk, (0x6 << GPIO_AFRL_AFSEL5_Pos)); // AF6 (RF) selected
MODIFY_REG(GPIOB->AFR[1], GPIO_AFRH_AFSEL8_Msk, (0x6 << GPIO_AFRH_AFSEL8_Pos)); // AF6 (RF) selected

使用Nucleo-WL55JC检查 Sub-GHz无线电接口信号

作为使用提供的代码使内部无线电接口信号变成可外部观察的示例,将修改、编译名为“SubGhz_Phy_PingPong”的示例项目(在STM32CubeWL MCU套装中提供)并将其加载到Nucleo-WL55JC评估板上。然后,使用逻辑分析仪来捕获和可视化信号活动。

首先,检查GPIO引脚是否有冲突。通过打开项目的STM32CubeMX器件配置文件(.ioc文件)并查看引脚视图,我们可以看到表1中概述的所有GPIO引脚都未使用。因此,我们可以安全地配置所有引脚,使其与相应的内部信号匹配,而不影响应用的功能。

接下来,必须将GPIO配置代码添加到项目源代码中。在这种情况下,最简单的方法是在无线电初始化例程之前,将列表1或列表2中的内容复制并粘贴到main.c 文件中。也就是说,在SystemClock_Config() 函数之后、/* USER CODE BEGIN SysInit *//* USER CODE END SysInit */ 注释之间的位置粘贴代码。然后,构建应用并将其烧写到Nucleo板上。


3 :当相应的GPIO引脚已适当配置后, Sub-GHz无线电接口信号在Nucleo-WL55JC板上的位置。

图3显示了Nucleo板上的插件头引脚与列表1中的信号引脚的对应关系。将Analog Discovery 2器件的数字I/O线路连接到这些引脚,并正确配置WaveForms应用,以在适当的时间帧内捕获信号值。图4显示了某次此类捕获的结果。在这种情况下,SPI总线上的活动似乎是IRQ0线路上的中断请求的结果。另请注意,当无线电进入待机模式时,SMPSRDY信号的变化,以及当无线电处理命令时,BUSY信号的变化。放大即可查看SPI协议解释程序的值,从而可以确定发送给无线电系统的确切命令及其响应。


4 :WaveForms中的逻辑分析仪窗口的截图,显示了内部无线电接口信号的时序图。