Sub-GHz Radio Interrupts on the STM32WL Series

The STM32WL series of microcontrollers includes an embedded sub-GHz radio based on the Semtech SX1261 and SX1262 transceivers. Just like the SX126x devices, the sub-GHz radio system is controlled by a SPI interface, a BUSY line, and three interrupt (IRQ) lines. The only difference is, as the radio is integrated into the STM32WL device, these signals (highlighted in Figure 1) are internally connected to the rest of the SoC.


Figure 1: Sub-GHz radio system interface signals (derived from Figure 9 in RM0453)

As such, the interrupt scheme of the STM32WL sub-GHz radio system is the same as that of the Semtech SX126x transceivers. That is, there are ten interrupt sources that can be enabled/disabled and mapped to any of the three IRQ lines. A description of these interrupt sources is provided in Table 1 below.

Table 1: Sub-GHz radio system interrupt sources (derived from Table 37 in RM0453)

Bit Source Description Packet Type Operation
0 TxDone Packet transmission finished LoRa and GFSK TX
1 RxDone Packet reception finished LoRa and GFSK RX
2 PreambleDetected Preamble detected LoRa and GFSK RX
3 SyncDetected Synchronization word valid GFSK RX
4 HeaderValid Header valid LoRa RX
5 HeaderErr Header CRC error LoRa RX
6 Err Preamble, syncword, address, CRC, or length error GFSK RX
6 CrcErr CRC error LoRa RX
7 CadDone Channel activity detection finished LoRa CAD
8 CadDetected Channel activity detected LoRa CAD
9 Timeout RX or TX timeout LoRa and GFSK RX & TX
15:10 N/A Reserved N/A N/A

The low-level driver of the Sub-GHz Phy Middleware will be utilized in this article to interface with the STM32WL. The provided code could also be applied to an application utilizing the complete Middleware (including the high-level driver), but doing so will likely not be necessary as most of the implementation details are abstracted by the higher layer. For more information about utilizing the low-level sub-GHz driver in an STM32WL project, please read Using the Low-Level Sub-GHz Radio Driver for the STM32WL Series.

The following code snippets demonstrate the basic procedure for utilizing STM32WL radio interrupts. The first step, shown in Listing 1, is to pass a pointer to a callback function as an argument to the driver initialization routine. This callback function will be executed every time an interrupt is issued by the sub-GHz radio system. To determine the source of the interrupt (see Table 1), this function must include a single parameter of type RadioIrqMasks_t, allowing a switch statement (or other conditional logic) to handle each individual interrupt source.

Listing 1: Template for setting and implementing the sub-GHz radio callback function

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

.
.
.

// Callback function - executed once for each (enabled) interrupt issued from sub-GHz radio
void RadioOnDioIrq(RadioIrqMasks_t radioIrq)
{
  switch (radioIrq)
  {
    case IRQ_TX_DONE:
      // do something
      break;
    case IRQ_RX_DONE:
      // do something
      break;
    case IRQ_RX_TX_TIMEOUT:
      // do something
      break;
    case IRQ_CRC_ERROR:
      // do something
      break;
    default:
      break;
  }
}

For an interrupt request to be generated by the sub-GHz radio subsystem, one or more interrupt sources must be globally enabled and mapped to any of the three IRQ lines. As shown in Listing 2, this is done by calling the SUBGRF_SetDioIrqParams() function. The first parameter of this function is a global interrupt enable mask, which enables/disabled the indicated interrupt source(s). The last three parameters are masks for the individual IRQ lines, which map the interrupt source(s) to the corresponding line. For example, in Listing 2 the TxDone, Timeout, and RxDone interrupts are globally enabled by the first argument. The second argument indicates that the IRQ1 line should transition from low to high when either the TxDone or Timeout interrupts are issued. Likewise, the IRQ2 line should transition from low to high when the RxDone interrupt is issued. There is no interrupt source mapped to the IRQ3 line in this example.

Listing 2: Enabling interrupts from the sub-GHz radio system

SUBGRF_SetDioIrqParams( IRQ_TX_DONE | IRQ_RX_TX_TIMEOUT | IRQ_RX_DONE,
                      IRQ_TX_DONE | IRQ_RX_TX_TIMEOUT,
                      IRQ_RX_DONE,
                      IRQ_RADIO_NONE );

NOTE: In the case that several interrupt sources mapped to the same IRQ line are triggered simultaneously, the callback function will be called multiple times to handle each source event.

Unfortunately, short of directly modifying the Sub-GHz Phy Middleware, there is no way to assign separate callback functions to the individual IRQ lines. That is, the same callback function will be executed regardless of which IRQ line a triggered interrupt is mapped to. It is for this reason that most applications typically only utilize the IRQ1 line. Should the situation arise that an application must determine which IRQ line was triggered, the code provided in Listing 3 may be used. It employs a workaround in which the internal IRQ lines are made external, allowing the GPIO IDR (Input Data Register) to be polled to get the status of each line. For more details about making the internal sub-GHz radio signals external, see Mapping the Internal STM32WL Sub-GHz Radio Interface Signals to GPIO Pins.

Listing 3: Route the IRQ[0:2] lines to GPIO pins and check their current state.

#define GPIO_PIN_RF_IRQ1 GPIO_PIN_3
#define GPIO_PIN_RF_IRQ2 GPIO_PIN_5
#define GPIO_PIN_RF_IRQ3 GPIO_PIN_8

/**************************** Initialization Code *****************************/
// Enable GPIOB Clock
__HAL_RCC_GPIOB_CLK_ENABLE();

// Configure RF_{IRQ0, IRQ1, IRQ2} pins
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_RF_IRQ1 | GPIO_PIN_RF_IRQ2 | GPIO_PIN_RF_IRQ3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF6_RF_BUSY;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);


/************ Conditional logic to be placed in callback function *************/
// Check status of each IRQ line
if (READ_BIT(GPIOB->IDR, GPIO_PIN_RF_IRQ1))
{
  // IRQ1 line triggered
}
else if (READ_BIT(GPIOB->IDR, GPIO_PIN_RF_IRQ2))
{
  // IRQ2 line triggered
}
else if (READ_BIT(GPIOB->IDR, GPIO_PIN_RF_IRQ3))
{
  // IRQ3 line triggered
}

For the most part, managing interrupts from the sub-GHz radio system on the STM32WL line of devices is identical to interfacing with an external RF transceiver. There is only a slight loss in flexibility stemming from the internalization of the radio control signals. There are several complete example applications which utilize the radio interrupts available from the STM32CubeWL MCU Package .