使用 STM32WL 系列 Sub-GHz 无线驱动程序的应用示例

介绍

STM32WL 系列器件包括内置的低于1GHz无线外设 ( Sub-GHz 指的是低于 1GHz 的无线电频段 ),能够支持LoRa(仅限STM32WLE5/55器件)、(G)FSK、(G)MSK和BPSK调制方案。与此无线外设的通信是通过使用设备参考手册第5.8节中概述的命令的内部SPI接口完成的。虽然该RF接口的抽象层是在低于1GHz Phy中间件中定义的(在STM32CubeWL MCU Package中可得),但将该中间件添加到使用STM32CubeMX的项目中需要在其他几个外设和库之间进行高级配置。这会导致一个更大、更复杂的项目,消耗更多的设备内存,并导致抽象层低效率。对于要求功耗低的简单应用,将RF接口驱动程序与低于1GHzPhy中间件隔离并直接利用它可能是有益的。

低于1GHz Phy中间件由高层(radio.c) 和低层(radio_driver.c) 组成。高级驱动程序提供了许多有用的函数,这些函数抽象了低层无线功能,例如RadioInit()RadioSetTxConfig()RadioSend() 。然而,尽管这些函数很方便,但它们的代价是效率低下,比如冗余的函数调用和过度依赖诸如音序器和定时器服务器之类的实用程序。低层驱动程序简单地实现参考手册中概述的 SUBGHZSPI 命令,并提供低于1GHz无线寄存器的定义。以牺牲一些质量属性(如可维护性和可移植性)为代价,使用该驱动程序进行编码直接允许程序员对其应用程序进行更大的控制。在本教程中,将演示如何将这个低层与低于 1GHz Phy 中间件隔离开来,并直接添加到 STM32CubeIDE 项目中。

要求

要准确地跟随教程,需要以下项目。

操作过程

创建和配置项目

第一步是建立一个可以添加驱动程序的项目。在这里,将从头创建一个新的 STM32 项目,并启用 SubGHz 外设。也可以使用已正确配置的现有项目。

  1. 启动STM32CubeIDE应用程序并打开(或创建)所需的工作空间。要创建一个新项目,请通过选择 File > New > STM32 Project,启动 STM32 项目 向导。

  2. 使用Board Selector 选择器选择 NUCLEO-WL55JC1 评估板。单击 Next

  3. 给项目起一个合适的名字(例如,“radioDriverExample”),取消选中 Enable Multi Cpus Configuration 选项。单击 Finish

  4. 将出现一个弹出窗口,询问是否应该将外设初始化为其默认模式。单击 Yes

  5. 向导将生成一个扩展名为“.ioc”的设备配置文件。该文件可以在 STM32CubeIDE 中打开。在 Pinout & Configuration 选择器中,在Connectivity 类别下选择SUBGHZ外设。选中 “Activated

  6. 在 SUBGHZ 配置部分中,在 Parameter Settings 选择器下将波特率预调量值 ( Baudrate Prescaler Value ) 更改为 “4”。

NVIC 设置 选择器中,选中复选框以启用 SUBGHZ Radio Interrupt

  1. 切换到 “Clock Configuration ”选择器。将 MSI RC 值改为“48000”。
    image

  2. 最后,切换到 Project Manager 选择器并打开 Code Generator 选项。勾选 “Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”
    image

  3. 保存修改后的.ioc文件,并在询问 “Do you want to generate code?” 时单击 Yes 。如果提示是否要打开 C/ c++ 透视图,再点击 Yes

添加SubGHz Radio Driver

  1. 作为第一步,从ST的网站下载STM32CubeWL MCU Package,并将其内容提取到你选择的位置。

添加BSP驱动程序

由于低于1GHz中间件与硬件无关,因此需要一个Board Support Package (BSP) 来允许驱动程序控制RF开关。

  1. 在项目资源管理器中,右键单击“驱动程序”目录,选择Import . … 在出现的向导中,在General 类别中选择File System ,如 下所示。单击Next。

  2. 用STM32CubeWL MCU包中驱动程序目录的位置填充From 目录字段(举例, /STM32Cube_FW_WL_V1.1.0/Drivers)。使用文件资源管理器窗口选择Drivers/BSP/STM32WLxx_Nucleo目录下的所有.h和.c文件。单击Finish
    image

  3. 在项目资源管理器中找到名为stm32wlxx_nucleo_conf_template.h 的导入文件,并将其重命名为stm32wlxx_nucleo_conf.h 。项目结构现在应该出现如图所示。
    image

  4. 为了让编译器定位这些新的头文件,必须将包含它们的目录添加到include目录列表中。右键单击“STM32WLxx_Nucleo”目录,选择Add/remove include directory 。将弹出一个窗口,询问应该修改哪些配置。确保选中DebugRelease 选项,然后单击OK
    image

添加所需的实用程序

就目前而言,低层无线驱动程序仍然对实用程序函数有一些较小的依赖。这些依赖关系可以很容易地通过对驱动程序文件的一些修改来消除,但是在项目中包含这些文件也同样容易。

  1. 选择 File > New > Source Folder。将文件夹命名为“实用程序”并单击Finish
    image

  2. 右键单击项目资源管理器中的实用程序文件夹并选择Import. …在出现的向导中,在General 类别中选择File System 。单击 N ext

  3. 用STM32CubeWL MCU包中实用程序目录的位置填充From 目录字段(举例, /STM32Cube_FW_WL_V1.1.0/Utilities)。使用文件资源管理器窗口选择以下文件:

  • Utilities/conf/utilities_conf_template.h
  • Utiliites/misc/stm32_mem.c
  • Utilities/misc/stm32_mem.h

如下面的两张图所示。

  1. 在项目浏览器中找到导入的名为“utilites_conf_template .h 的文件,将其重命名为“utilites_conf .h 。项目结构现在应该出现如图所示。
    image

  2. Utilities/conf and Utilites/misc 目录重复步骤4,将它们添加到包含路径列表中。

无线驱动程序

处理好依赖关系后,就可以添加无线驱动程序本身了!

  1. 在驱动程序目录中创建一个名为“Radio”的新文件夹(或你喜欢的任何名称)。在这个文件夹中,从STM32CubeWL MCU包中复制以下文件:
  • <extraction_location>/STM32Cube_FW_WL_V1.1.0/Middlewares/Third_Party/SubGHz_Phy/stm32_radio_driver/radio_driver.c
  • <extraction_location>/STM32Cube_FW_WL_V1.1.0/Middlewares/Third_Party/SubGHz_Phy/stm32_radio_driver/radio_driver.h
  • <extraction_location>/STM32Cube_FW_WL_V1.1.0/Middlewares/Third_Party/SubGHz_Phy/Conf/radio_conf_template.h
  • <extraction_location>/STM32Cube_FW_WL_V1.1.0/Projects/NUCLEO-WL55JC/Applications/SubGHz_Phy/SubGHz_Phy_PingPong/SubGHz_Phy/Target/radio_board_if.c
  • <extraction_location>/STM32Cube_FW_WL_V1.1.0/Projects/NUCLEO-WL55JC/Applications/SubGHz_Phy/SubGHz_Phy_PingPong/SubGHz_Phy/Target/radio_board_if.h
  1. 将文件radio_conf_template.h 重命名为radio_conf.h 。项目结构现在应该如图所示。

  2. 打开radio_conf.h 文件,注释掉mw_log_conf.hutilites_def .hsys_debug.h include 指令。为subghz.h 添加一个 include 指令。
    image

  3. 类似地,打开文件radio_driver.c 并注释掉mw_log_conf.h 的include指令。

  4. Drivers/Radio 目录重复步骤4,将其添加到包含路径列表中。

  5. 将文件 <extraction_location>/STM32Cube_FW_WL_V1.1.0/Projects/NUCLEO-WL55JC/Applications/SubGHz_Phy/SubGHz_Phy_PingPong/Core/Inc/platform.h 复制到Core/Inc 目录下。

  6. 打开platform.h ,注释掉stm32wlxx_ll_gpio.h include 指令。

  7. 最后,要开始使用无线驱动程序,请在适当的用户代码部分的 main.c 顶部添加 #include radio_driver.h 行。
    image

应用程序示例

作为以独立方式使用低于1GHz Phy驱动程序的示例,我们创建了两个示例程序(可在GitHub Repository上获得)。这些示例复制了STM32CubeWL MCU Package中SubGHz_Phy_PingPong示例的高级功能。也就是说,它们都实现了图1所示的状态机。这两个示例之间的唯一区别是一个使用LoRa调制解调器,而另一个使用FSK调制解调器。

1: 低层无线驱动乒乓样例项目有限状态机

两个NUCLEO-WL55JC1板需要运行这些示例,其中一个将充当主机,而另一个将充当从机。最初,两个板都处于主状态,以随机间隔发送“PING”消息并等待响应。最终,两个板同步,因此只有一个设备发送“PING”消息,另一个设备发送“PONG”消息作为响应。要执行该应用程序,请按照前一节提供的步骤创建一个项目,该项目包含低于1GHz 无线驱动程序。然后,只需将项目的main.c 文件的内容替换为GitHub Repository中的一个文件的内容,具体取决于你希望在示例中使用哪种调制方案。最后,构建项目并使用它对两个Nucleo板进行编程。



注意,这些示例与SubGHz_Phy_PingPong示例兼容。也就是说,一块板可以用上述应用程序编程,另一块板可以用SubGHz_Phy_PingPong应用程序编程,它们将按预期一起工作。然而,为了利用GFSK调制,必须首先对SubGHz_Phy_PingPong示例进行稍微修改。打开subghz_phy_app.h 文件,修改第一个define指令如下:

#define USE_MODEM_LORA  0 //1
#define USE_MODEM_FSK   1 //0

#define REGION_US915 //REGION_EU868

然后,在radio.c 中找到RadioRandom() 函数,注释掉RadioSetModem(MODEM_LORA); 这一行不仅不需要获得随机数,还会擦除之前初始化步骤中设置的无线配置。因此,在这种情况下,它被认为是一个bug,不应该被包括在内。SubGHz_Phy_PingPong示例现在准备编译并烧写到NUCLEO-WL55JC1板之一。另一个板应该根据上述说明使用GitHub Repository中的main_gfsk.c 文件的内容进行编程。



在初始化和执行图1所示的有限状态机之前,通过调用清单1中定义的radioInit() 函数来初始化无线。该函数使用与SubGHz_Phy_PingPong示例相同的无线配置,但有一个例外。在参考手册第6.1节的末尾,它说:

SMPS需要时钟才能正常工作。如果由于任何原因这个时钟停止,设备可能会被破坏。为了避免这种情况,使用时钟检测,当出现时钟故障时,关闭SMPS并启用LDO。SMPS时钟检测通过低于1GHz无线SUBGHZ_SMPSC0R.CLKDE使能。缺省情况下,SMPS时钟检测功能处于关闭状态,开启SMPS前必须开启时钟检测功能。

尽管有这个警告,低于1GHz Phy中间件的高层和低层都没有启用SMPS时钟检测。因为DCDC_ENABLE 是在radio_config.h 中定义的,所以SUBGRF_SetRegulatorMode() 函数将启用SMPS降压转换器。因此,在此函数调用之前,手动启用SMPS时钟检测。

清单 1: main_gfsk.c 中的 radioInit() 定义

void radioInit(void)
{
  // Initialize the hardware (SPI bus, TCXO control, RF switch)
  SUBGRF_Init(RadioOnDioIrq);

  // Use DCDC converter if `DCDC_ENABLE` is defined in radio_conf.h
  // "By default, the SMPS clock detection is disabled and must be enabled before enabling the SMPS." (6.1 in RM0453)
  SUBGRF_WriteRegister(SUBGHZ_SMPSC0R, (SUBGRF_ReadRegister(SUBGHZ_SMPSC0R) | SMPS_CLK_DET_ENABLE));
  SUBGRF_SetRegulatorMode();

  // Use the whole 256-byte buffer for both TX and RX
  SUBGRF_SetBufferBaseAddress(0x00, 0x00);

  SUBGRF_SetRfFrequency(RF_FREQUENCY);
  SUBGRF_SetRfTxPower(TX_OUTPUT_POWER);
  SUBGRF_SetStopRxTimerOnPreambleDetect(false);

  SUBGRF_SetPacketType(PACKET_TYPE_GFSK);

  ModulationParams_t modulationParams;
  modulationParams.PacketType = PACKET_TYPE_GFSK;
  modulationParams.Params.Gfsk.Bandwidth = SUBGRF_GetFskBandwidthRegValue(FSK_BANDWIDTH);
  modulationParams.Params.Gfsk.BitRate = FSK_DATARATE;
  modulationParams.Params.Gfsk.Fdev = FSK_FDEV;
  modulationParams.Params.Gfsk.ModulationShaping = MOD_SHAPING_G_BT_1;
  SUBGRF_SetModulationParams(&modulationParams);

  packetParams.PacketType = PACKET_TYPE_GFSK;
  packetParams.Params.Gfsk.AddrComp = RADIO_ADDRESSCOMP_FILT_OFF;
  packetParams.Params.Gfsk.CrcLength = RADIO_CRC_2_BYTES_CCIT;
  packetParams.Params.Gfsk.DcFree = RADIO_DC_FREEWHITENING;
  packetParams.Params.Gfsk.HeaderType = RADIO_PACKET_VARIABLE_LENGTH;
  packetParams.Params.Gfsk.PayloadLength = 0xFF;
  packetParams.Params.Gfsk.PreambleLength = (FSK_PREAMBLE_LENGTH << 3); // bytes to bits
  packetParams.Params.Gfsk.PreambleMinDetect = RADIO_PREAMBLE_DETECTOR_08_BITS;
  packetParams.Params.Gfsk.SyncWordLength = (FSK_SYNCWORD_LENGTH << 3); // bytes to bits
  SUBGRF_SetPacketParams(&packetParams);

  SUBGRF_SetSyncWord((uint8_t[]){0xC1, 0x94, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00});
  SUBGRF_SetWhiteningSeed(0x01FF);
}

其他利用 Sub-GHz 无线驱动程序的函数是状态输入函数。作为使用这些驱动程序函数接收和发送数据包的示例,清单2和3分别提供了enterMasterRx() 和enterMasterTx()函数。 有关SUBGRF_SetDioIrqParams() 函数使用的详细说明,请参见 STM32WL 系列中的 Sub-GHz 无线电中断

清单 2: main_gfsk.c 中的enterMasterRx() 定义

void enterMasterRx(pingPongFSM_t *const fsm)
{
  HAL_UART_Transmit(&huart2, "Master Rx start\r\n", 17, HAL_MAX_DELAY);
  SUBGRF_SetDioIrqParams( IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT | IRQ_CRC_ERROR,
                          IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT | IRQ_CRC_ERROR,
                          IRQ_RADIO_NONE,
                          IRQ_RADIO_NONE );
  SUBGRF_SetSwitch(RFO_LP, RFSWITCH_RX);
  packetParams.Params.Gfsk.PayloadLength = 0xFF;
  SUBGRF_SetPacketParams(&packetParams);
  SUBGRF_SetRx(fsm->rxTimeout << 6);
}

清单 3: main_gfsk.c 中的enterMasterTx() 定义


void enterMasterTx(pingPongFSM_t *const fsm)
{
  HAL_Delay(fsm->rxMargin);

  HAL_UART_Transmit(&huart2, "...PING\r\n", 9, HAL_MAX_DELAY);
  HAL_UART_Transmit(&huart2, "Master Tx start\r\n", 17, HAL_MAX_DELAY);
  SUBGRF_SetDioIrqParams( IRQ_TX_DONE | IRQ_RX_TX_TIMEOUT,
                          IRQ_TX_DONE | IRQ_RX_TX_TIMEOUT,
                          IRQ_RADIO_NONE,
                          IRQ_RADIO_NONE );
  SUBGRF_SetSwitch(RFO_LP, RFSWITCH_TX);
  // Workaround 5.1 in DS.SX1261-2.W.APP (before each packet transmission)
  SUBGRF_WriteRegister(0x0889, (SUBGRF_ReadRegister(0x0889) | 0x04));
  packetParams.Params.Gfsk.PayloadLength = 0x4;
  SUBGRF_SetPacketParams(&packetParams);
  SUBGRF_SendPayload((uint8_t *)"PING", 4, 0);
}

同样,请参阅GitHub Repository上的LoRa和GFSK调制方案的完整示例代码。