Trouble with nrf_802154_transmit_raw after SDK upgrade

Hi,

I just noticed that there's another issue with my project after upgrading from SDK 2.3.0 to 3.1.1. 

I am using the 802.15.4 radio for low level packet communications with nrf_802154_transmit_raw() to send packets. I overrode the weak definitions of nrf_802154_received_raw(), nrf_802154_transmitted_raw(), and nrf_802154_transmit_failed() to handle packet reception and TX completion. See attached radio_interface.c file.

After the upgrade I noticed that every time I transmit, I get a "Transmit failure" message in my log. So my call to nrf_802154_transmit_raw(txdata, NULL) is returning false every time. I confirmed that this was not happening before the SDK upgrade.

I am still getting calls to nrf_802154_transmitted_raw() for every attempted transmit, and I am generally not getting calls to nrf_802154_transmit_failed(). So it seems like maybe my transmits are working even though the transmit call returns a fail.

I just tried sending a reset command to one of my wireless nodes to see if transmit packets are actually working, and it does look like they are - though apparently something went off the rails when that device replied, as I got an "Illegal use of the EPSR" usage failure and my processor rebooted.

Note that this is on a Fanstel module with a FEM, so I'm using MPSL because I believe it's required for FEM control. I attached my prj.conf in case that's helpful.

I tried comparing my project to the only sample I can find that uses the raw interface, but I haven't found the problem yet: \ncs\v3.1.1\nrf\samples\peripheral\802154_phy_test\

Thanks,

Glen

Log snippet showing the usage fault:

U/A to transmit to 837632Transmit failure
Forward
Rx Data
Data from 1067402[01:19:54.213,500] <err> os: ***** USAGE FAULT *****
[01:19:54.219,085] <err> os: Illegal use of the EPSR
[01:19:54.224,945] <err> os: r0/a1: 0x00000000 r1/a2: 0x00000020 r2/a3: 0
x00000001
[01:19:54.233,612] <err> os: r3/a4: 0x00000000 r12/ip: 0x20003640 r14/lr: 0
x0001e455
[01:19:54.242,309] <err> os: xpsr: 0x00000000
[01:19:54.247,528] <err> os: Faulting instruction address (r15/pc): 0x0001e468
[01:19:54.255,432] <err> os: >>> ZEPHYR FATAL ERROR 35: Unknown error on CPU 0
[01:19:54.263,336] <err> os: Current thread: 0x20003870 (main)
[01:19:54.269,836] <err> os: Halting system
à*** Booting nRF Connect SDK v3.1.1-e2a97fe2578a ***
*** Using Zephyr OS v4.1.99-ff8f0c579eeb ***

4111.prj.conf

//============================================================================
//  Title   : radio_interface
//  Desc    : Nordic radio 802.15.4 interface functions
//  2022-8-5   Glen     Created
//============================================================================
// Documentation on the 802.15.4 driver here:
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/nrfxlib/nrf_802154/README.html
//
// Sample code used for low level radio functions, rx pool, and how to receive
// are in the SDK here: \nRF_Connect\v2.0.0\nrf\samples\peripheral\802154_phy_test\src
//
//== Includes ================================================================
#include "radio_interface.h"
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/printk.h>
#include <hal/nrf_radio.h>
#include <string.h>
#include <stdio.h>
#include "radio.h"
#include "iomapping.h"

//== Definitions and Types ===================================================
//#define NO_TRANSMIT_FCC_VERSION                  1
#define RF_FCS_SIZE                                (2u)
#define POWER_OUTPUT_LEVEL_INTO_AMPLIFIER          0      // dBm
#define POWER_INCREASE_AMPLIFIER                   22     // dBm

typedef uint16_t ptt_pkt_len_t;

struct rf_rx_pkt_s {
	uint8_t *rf_buf; /**< pointer to buffer inside radio driver with a received packet */
	uint8_t *data; /**< pointer to payload */
	ptt_pkt_len_t length; /**< size of payload of received packet */
	int8_t rssi; /**< RSSI */
	uint8_t lqi; /**< LQI */
};

//== Global Variables ========================================================
static volatile bool m_tx_complete = true;

//== Function prototypes =====================================================

//============================================================================

//----------------------------------------------------------------------------
// Name  : radio_interface_config
// Desc  : Meant to reset the radio but there isn't an obvious nrf function to
//         that and it might not even make sense for an on-processor radio
// Ins   : none
// Outs  : none
//----------------------------------------------------------------------------
void radio_interface_config(void)
{
   // Clear the TX in progress flag
   m_tx_complete = true;
}

//----------------------------------------------------------------------------
// Name  : radio_interface_receive
// Desc  : Start the radio to receive packets
// Ins   : none
// Outs  : none
//----------------------------------------------------------------------------
void radio_interface_receive(void)
{
  nrf_802154_receive();
}

//----------------------------------------------------------------------------
// Name  : radio_interface_init
// Desc  : Configure the radio
// Ins   : none
// Outs  : none
//----------------------------------------------------------------------------
int radio_interface_init(void)
{
  /* nrf radio driver initialization */
   nrf_802154_init();

   uint8_t panId[] = { 0x00, 0x01 };
   nrf_802154_pan_id_set(&panId[0]);
   nrf_802154_channel_set(25);
   nrf_802154_promiscuous_set(false);
   return 0;
}

//----------------------------------------------------------------------------
// Name  : radio_interface_channel_set
// Desc  : Send the channel to the radio driver
// Ins   : none
// Outs  : none
//----------------------------------------------------------------------------
void radio_interface_channel_set(uint8_t channel)
{
   nrf_802154_channel_set(channel);
}

//----------------------------------------------------------------------------
// Name  : nrf_802154_received_raw
// Desc  : Received a packet from the nrf radio. Save off needed data and call 
//         rf_process_rx_packets to handle it
// Ins   : none
// Outs  : none
//----------------------------------------------------------------------------
void nrf_802154_received_raw(uint8_t *data, int8_t power, uint8_t lqi)
{
	struct rf_rx_pkt_s rx_pkt;
	bool pkt_placed = false;

	if (data == NULL)
   {
      return;
   }
   
   rx_pkt.rf_buf = data;
   rx_pkt.data = &data[1];
   rx_pkt.length = data[0] - RF_FCS_SIZE;

   rx_pkt.lqi = lqi; // It looks like we don't need these ED conversions after all
   pkt_placed = true;

   // Convert the RSSI from dBm to a custom scale
   rx_pkt.rssi = radio_interface_signal_strength_lookup(power);

   radio_interrupt(true, false, rx_pkt.data, rx_pkt.length, rx_pkt.rssi, rx_pkt.lqi, 0);
   nrf_802154_buffer_free_raw(rx_pkt.rf_buf);
}

//----------------------------------------------------------------------------
// Name  : nrf_802154_transmitted_raw
// Desc  : Override the WEAK defined library "tx done" function so that I can
//         add a call to the radio_interrupt in radio.c for status tracking
//         If ACK was requested for the transmitted frame, this function is 
//         called after a proper ACK is received. If ACK was not requested, 
//         this function is called just after transmission has ended.
// Ins   : none
// Outs  : none
//----------------------------------------------------------------------------
void nrf_802154_transmitted_raw(uint8_t * p_frame, const nrf_802154_transmit_done_metadata_t * p_metadata)
{
    (void)p_frame;

    uint8_t * p_ack = p_metadata->data.transmitted.p_ack;

    if (p_ack != NULL)
    {
        nrf_802154_buffer_free_raw(p_ack);
    }

    radio_interrupt(false, true, NULL, 0, 0, 0, 0);   // Call the radio TX complete interrupt, no failure
    //printk("TXDONE m_tx_complete = true\r\n");
    m_tx_complete = true;
}

//----------------------------------------------------------------------------
// Name  : nrf_802154_transmit_failed
// Desc  : Notifies that a frame was not transmitted due to a busy channel.
// Ins   : p_frame      Pointer to a buffer that contains PHR and PSDU of the frame that was not transmitted
// Ins   : error        Reason of the failure.
// Ins   : p_metadata   Pointer to a metadata structure describing frame passed in @p p_frame.
// Outs  : none
//----------------------------------------------------------------------------
void nrf_802154_transmit_failed(uint8_t * p_frame, nrf_802154_tx_error_t error, const nrf_802154_transmit_done_metadata_t * p_metadata)
{
   radio_interrupt(false, true, NULL, 0, 0, 0, 1);   // Call the radio TX complete interrupt, with a failure
   m_tx_complete = true;
   //printk("TXFAIL m_tx_complete = true\r\n");
   printk("\r\nrf_802154_transmit_failed\r\n");
}

//----------------------------------------------------------------------------
// Name  : radio_interface_transmit_packet
// Desc  : Send a packet out
// Ins   : A pointer to the data to send
// Outs  : none
//----------------------------------------------------------------------------
void radio_interface_transmit_packet(uint8_t *txdata)
{
   bool txstatus; 

   // The library is aware of the gain added by the PA, so you have to set the final output
   // power level you want, not just the level you want to go into the amplifier like it used to be
   nrf_802154_tx_power_set(POWER_OUTPUT_LEVEL_INTO_AMPLIFIER + POWER_INCREASE_AMPLIFIER);

   //printk("PRE-TX Set m_tx_complete false, Was %d\r\n", m_tx_complete);
   m_tx_complete = false;  // Set a flag that will be cleared when the transmission is either complete (plus acknowledgment) or it has failed      

   txstatus = nrf_802154_transmit_raw(txdata, NULL);

   if (txstatus == false)  // Failure to transmit 
   {
      printk("\r\nTransmit failure\r\n");
   }
}


//----------------------------------------------------------------------------
// Name  : radio_interface_get_tx_busy
// Desc  : Returns 1 if the radio has not yet completed its transmit (including receiving the hardware ACK if requested)
// Ins   : none
// Outs  : 1 for busy, 0 for not busy
//----------------------------------------------------------------------------
uint8_t radio_interface_get_tx_busy(void)
{
   if (m_tx_complete == false)
   {
      return 1;
   }
   else
   {
      return 0;
   }
}

Parents Reply Children
  • It looks like that change happened only with the latest 3.2 version.

    I asked Google's AI about the issue and it said "Between these versions, Nordic moved more logic into the MPSL (Multi-Protocol Service Layer). In 2.3.0, the 802.15.4 driver was "lighter" and did more direct register access. In 3.1.1, every radio action must be negotiated with the Arbiter."

    Is this true, and if so how does this arbiter work? I've tried putting in yields and sleeps thinking that maybe I just needed to let some driver level stuff run, but no luck so far.

    Is there some way for me to verify that the driver is ready for a new transmit packet or something I need to do do service the radio to let it finish whatever it's doing?

  • A bit more information - Inside the nrf_802154_transmit_raw() library function it calls nrf_802154_request_transmit() with the first parameter "term_lvl" set to NRF_802154_TERM_NONE. I tried changing it to NRF_802154_TERM_802154 which aborts any pending 802.15.4 operations and now I'm not getting any 'false' return values from nrf_802154_transmit_raw. So that seems like a band-aid way to fix my problem, but I'm hoping you can provide a better way to understand what the radio state machine is getting hung up on and how to get the radio ready to transmit. 

    Ultimately, this ends up calling nrf_802154_core_transmit in nrf_802154_core.c, which calls current_operation_terminate to terminate any current operation. 

    I'm not doing rapid-fire transmits so I don't think this issue is related to sending too many packets too quickly. My transmits are in response to received packets from remote devices, so I am trying to send as quickly as possible after a receive, but I'm processing the received packet and preparing my response packet in the main loop so it's not like I'm trying to transmit from an ISR or anything.

  • Glen M said:
    I asked Google's AI about the issue and it said "Between these versions, Nordic moved more logic into the MPSL (Multi-Protocol Service Layer). In 2.3.0, the 802.15.4 driver was "lighter" and did more direct register access. In 3.1.1, every radio action must be negotiated with the Arbiter."

    If so, atleast I wasn't aware of this change. Might be worth asking it from where it gets this idea. You can read more on the arbiter here. It might be that there has been some changes to MPSL that is stricter now, so that nothing has really "changed" enough to warrant a changelog, but something that previously was maybe close to problematic now gives errors. 

    You can try running it again with CONFIG_IEEE802154_DRIVER_LOG_LEVEL_DBG=y or nrf_802154_state_get() to get more info on what happens there. 

    Could you also try to not use NULL with nrf_802154_transmit_raw() to see if disabling cca improves things? Or maybe try ending nrf_802154_transmitted_raw() with nrf_802154_receive() to make sure it starts in rx mode for cca.

    Regards,

    Elfving

  • I made some progress - I had my DCDC regulator turned off and it seems like that might be needed to power everything properly. I turned that on in the board files and added CONFIG_NRFX_POWER=y

    Now I'm not getting the false results from the TX call, so that's great, but I am still occasionally hitting

    nrf_802154_transmit_failed() for maybe 10-15% of my packets.

    nrf_802154_state_get() does not exist that I can find, and on that 802154 driver log level I have it included but it's giving me a wiggly line saying "CONFIG_IEEE802154_DRIVER_LOG_LEVEL_DBG was assigned the value y, but got the value n. Missing dependencies:
    <choice IEEE802154_DRIVER_LOG_LEVEL_CHOICE>"  How can I fix that and get the rest of the way to the bottom of what's going on.

    Also, I still have to have 
    CONFIG_NRF_802154_TEMPERATURE_UPDATE=n or I get a kernel panic at startup. I don't need the temperature, but that seems like a red flag that something else might be up or that I'm missing something I need to get the driver working properly. Any thoughts on that?


  • Does explicitly enabling CONFIG_IEEE802154_DRIVER_LOG_LEVEL_DBG and CONFIG_IEEE802154_DRIVER_LOG_LEVEL_CHOICE help?

    Glen M said:
    Also, I still have to have CONFIG_NRF_802154_TEMPERATURE_UPDATE=n or I get a kernel panic at startup. I don't need the temperature, but that seems like a red flag that something else might be up or that I'm missing something I need to get the driver working properly. Any thoughts on that?

    Oh. Yeah I agree that sounds odd. Could you get me the log of that?

    I think I'll have to run this by the relevant R&D team.

    Regards,

    Elfving

Related