Force timer buffer into advertisement manufacturer data at time of transmit.

Is there a way to modify the PHY layer, or SoftDevice, so that a timestamp is immediately read from a timer and forced into the manufacturer data right before transmission? I am looking for ways to timestamp manufacturing data with a known and unchanging delay small delay. 

Ultimately I would want the advertising data to look directly at the hardware counter during it's transmission, so that whenever a transmit occurs it must send whatever contents are in the counter buffer.

The device must be a beacon (no connection is allowed for synchronization), and must asynchronously send timestamps without any delay through the application layers.

I am aware that the reception (RX) of this will have it's own delay but I am not worried about that right now. I just want to solve the first problem above first.

Thanks

Parents
  • Hello,

    Please correct me if I'm wrong, but I assume you are working with the nRF5 SDK since you mentioned using the SoftDevice. The buffer(s) containing the advertisement payload are kept in the application and cannot be altered by the SoftDevice. This means you also need to update the advertisement data from the application.

    The SoftDevice includes a feature called Radio Notification, which allows you to configure the SoftDevice to trigger a callback prior to every advertisement event. You could use this to update your payload with a new TIMER value.

    Best regards,

    Vidar

  • You may not be understanding.

    I update the manufacturing data in the advertisement payload every 0.25 seconds using the application. Here I can also update a time stamp, using the application.

    However there is a delay between application updates and radio transmission.

    What I want is to edit the radio code to pull a counter value right before each advertisement transmission. I am hoping for sub millisecond delays.

    I do not want call backs, and I do not want to involve the app with updating this part of the advertisement data. I want it to be autonomous outside of the application.

    i saw examples of synced timing. I don’t want this because I will never be opening a connection. This is a transmit only application.

    there must be a buffer somewhere that is copied to the radio modulator, and this is what I want access to. It is a hack but it what I want to do. 
    thanks

  • In case this of use, the data sent over-the-air is subtly different from the data stored in RAM:

    // BLE Advertising Packet
    // ======================
    // Every packet begins with an 8 bit preamble, an alternating binary sequence. This is followed by a 32 bit access
    // address (AA) which can be thought of as a unique identifier which defines a particular connection. When a
    // device (master or slave) transmits on an advertising channel it uses a fixed value of 0x8e89bed6 as the access
    // address.
    // Following the 32 bit access address are optional S0, LENGTH and S1 fields then a variable length Protocol Data
    // Unit (PDU) which contains the message data payload. Finally all packets end with a 24 bit CRC.
    // By default, the Nordic BLE address is derived from the NRF_FICR->DEVICEADDR[] and is 48 bits in length (6 bytes)
    // If S0, LENGTH or S1 are specified with zero length their fields will be omitted in memory, otherwise each
    // field will be represented as a separate byte, regardless of the number of bits in their on-air counterpart
    // For the field sizes defined in bits, the occupation in RAM will always be rounded up to the next full byte size
    // (for instance 3 bit length will allocate 1 byte in RAM, 9 bit length will allocate 2 bytes, etc.).
    // On-air packet layout (Note Header in RAM is 3 bytes if S0INC is '1')
    // +-------------------------------------------------------------------------------------------------------------------------+
    // |                                         BLE Advertising Packet - Legacy                                                 |
    // +-------------------------------------------------------------------------------------------------------------------------+
    // |                                         1MHz: 47 octets max total length                                                |
    // +--------+-------+------------------------------------------------------------------------------------------+------+------+
    // |        |Access |                                                                                          | Add  |      |
    // |Preamble|Address|                   Protocol Data Unit PDU                                                 | On   | CRCC | Tone Ext
    // +--------+-------+------------------------------------------------------------------------------------------+------+------+
    // |   1(2) |   4   |                                    2 - 39                                                | (0)  |  3   | 16 - 160uSec
    // |        |       +-----------------+------------------------------------------------------------------------+      |      |
    // |        |       |    Header       |                  Payload                                               |      |      |
    // |        |       +-----------------+------------------------------------------------------------------------+      |      |
    // |        |       |       2         |                  0 - 37                                                |      |      |
    // |        |       +-----+-----+-----+------------------------------------------------------------------------+      |      |
    // |        |       | Opt | Opt | Opt |  MAC Addr  |     AdvData List (example 3 fields)                       |      |      |
    // |        |       | S0  | Len | S1  +------------+-----------------------------------------------------------+      |      |
    // |        |       |-----+-----+-----|    6       |     0 - 31                                                |      |      |
    // |        |       | (1) | (1) | (1) |            +-------------------+-------------------+-------------------+      |      |
    // |        |       |     |     |     |            |    AdvData Flags  |    AdvData Name   |   (AdvData Data)  |      |      |
    // |        |       |     |     |     |            +-------------------+-------------------+-------------------+      |      |
    // |        |       |     |     |     |            |        3          |          6        |       0 - 22      |      |      |
    // |        |       |     |     |     |            +-----+------+------+-----+------+------+-----+------+------+      |      |
    // |        |       |     |     |     |            | Len | Type | Data | Len | Type | Data | Len | Type | Data |      |      |
    // |        |       |     |     |     |            +-----+------+------+-----+------+------+-----+------+------+      |      |
    // |        |       |     |     |     |            |  1  |  1   |  1   |  1  |  1   |  4   |  1  |  1   |  20  |      |      |
    // |        |       |     |     |     |            |     |      |      |     |      |      |     |      |      |      |      |
    // |  1(2)  |  4    | (1) | (1) | (1) |   6        |  1  |  1   |  1   |  1  |  1   |  4   |  1  |  1   |  20  | (0)  |  3   |
    // +--------+-------+-----+-----+-----+------------+-----+------+------+-----+------+------+-----+------+------+------+------+
    //         0x8E89BED6     |     |      \
    //                /       |     |       \
    //               /  RAM uses 2+ bytes    \
    //              / 1 byte | 2 bit | 6 bit  \
    //             /                           \
    //            /  OTA only 2 bytes for BLE   \
    //           |                         |     |
    //           Type RFU ChSet TxAdd RxAdd Length
    //    bits:    4   1    1     1     1     8

    If using bare-metal transmission of (say) an advertising packet when timestamp is required, this is an example of a test packet:

    void nrf_radio_advertise(void)
    {
        // The S0 length is one byte, and exactly maps to p_beacon_pdu[0]. The length field is 6 bits,
        // and uses 6 bits of p_beacon_pdu[1]. S1 field is 2 bits and uses 2 bits of p_beacon_pdu[2].
        // This means the PDU header takes 3 bytes in RAM, but only 2 bytes on air.
        // If S0, LENGTH or S1 are specified with zero length their fields will be omitted in memory, otherwise each
        // field will be represented as a separate byte, regardless of the number of bits in their on-air counterpart
        // ie if the S0INCL field in PCNF0 determines if S0 is present in RAM at all if its length is zero. If present, one byte is allocated in RAM
    
        m_adv_pdu[0]  = 0x40 | 0x02;    // S0:     Optional First header byte: TxAdd|RxAdd (0x40) + PDU Type
        m_adv_pdu[1]  = 0;              // Length: Optional Second header byte: LENGTH (will be updated at the end)
        m_adv_pdu[2]  = 0;              // S1:     Optional Third header byte
    
        m_adv_pdu[3]  = (NRF_FICR->DEVICEADDR[0]      ) & 0xFF; // DEVICEADDR[0] bits 31-0 of the BLE MAC address.
        m_adv_pdu[4]  = (NRF_FICR->DEVICEADDR[0] >>  8) & 0xFF;
        m_adv_pdu[5]  = (NRF_FICR->DEVICEADDR[0] >> 16) & 0xFF;
        m_adv_pdu[6]  = (NRF_FICR->DEVICEADDR[0] >> 24)       ;
        m_adv_pdu[7]  = (NRF_FICR->DEVICEADDR[1]      ) & 0xFF;
        m_adv_pdu[8]  = (NRF_FICR->DEVICEADDR[1] >>  8) & 0xFF;
    
        m_adv_pdu[9]  = 0x02;  // 2 bytes (3 Tot)
        m_adv_pdu[10] = 0x01;  // BLE_GAP_AD_TYPE_FLAGS
        m_adv_pdu[11] = 0x06;  // BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE
    
        m_adv_pdu[12] = 0x05;  // 5 bytes (6 Tot)
        m_adv_pdu[13] = 0x09;  // BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME
        m_adv_pdu[14] = 'T';
        m_adv_pdu[15] = 'E';
        m_adv_pdu[16] = 'S';
        m_adv_pdu[17] = 'T';
    
        m_adv_pdu[18] =  21;  // 21 bytes (22 Tot)
        m_adv_pdu[19] = 0xFF; // BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA  Manufacturer Specific Data
        m_adv_pdu[20] = 0x01; // Nordic is 0x59 0x00
        m_adv_pdu[21] = 0x02; // Timestamp starts here ..
        m_adv_pdu[22] = 0x03;
        m_adv_pdu[23] = 0x04;
        m_adv_pdu[24] = 0x05;// .. Timestamp ends here
        m_adv_pdu[25] = 0x06;
        m_adv_pdu[26] = 0x07;
        m_adv_pdu[27] = 0x08;
        m_adv_pdu[28] = 0x09;
        m_adv_pdu[29] = 0x0A;
        m_adv_pdu[30] = 0x0B;
        m_adv_pdu[31] = 0x0C;
        m_adv_pdu[32] = 0x0D;
        m_adv_pdu[33] = 0x0E;
        m_adv_pdu[34] = 0x0F;
        m_adv_pdu[35] = 0x10;
        m_adv_pdu[36] = 0x11;
        m_adv_pdu[37] = 0x12;
        m_adv_pdu[38] = 0x13;
        m_adv_pdu[39] = 0x14;

    Then the first event available on transmission is the READY (nRF52832) or better TXREADY on nRF52833:

    void RADIO_IRQHandler(void)
    {
       if (NRF_RADIO->EVENTS_TXREADY)
       {
          // Start of transmission - Update timestamp
          m_adv_pdu[21] = 0x12; // Dummy test timestamp, say 32-bit
          m_adv_pdu[22] = 0x34;
          m_adv_pdu[23] = 0x56;
          m_adv_pdu[24] = 0x78;
          NRF_RADIO->EVENTS_READY = 0;
       }
       if (NRF_RADIO->EVENTS_DISABLED)
       {
          // end of tx transmission
    

    Out of all the events, TXREADY is the first prior to sending the data:

    // RADIO Events:
    // ============
     //   Event                      IDE 1st Peripheral Base Address  Offset     Description
     //   =========================  ===============================  =======    ===============================================================================================
    //   {"EVENTS_READY",            &NRF_RADIO->EVENTS_READY,       0x100}, // RADIO has ramped up and is ready to be started
    //   {"EVENTS_ADDRESS",          &NRF_RADIO->EVENTS_ADDRESS,     0x104}, // Address sent or received
    //   {"EVENTS_PAYLOAD",          &NRF_RADIO->EVENTS_PAYLOAD,     0x108}, // Packet payload sent or received
    //   {"EVENTS_END",              &NRF_RADIO->EVENTS_END,         0x10C}, // Packet sent or received
    //   {"EVENTS_DISABLED",         &NRF_RADIO->EVENTS_DISABLED,    0x110}, // RADIO has been disabled
    //   {"EVENTS_DEVMATCH",         &NRF_RADIO->EVENTS_DEVMATCH,    0x114}, // A device address match occurred on the last received packet
    //   {"EVENTS_DEVMISS",          &NRF_RADIO->EVENTS_DEVMISS,     0x118}, // No device address match occurred on the last received packet
    //   {"EVENTS_RSSIEND",          &NRF_RADIO->EVENTS_RSSIEND,     0x11C}, // Sampling of receive signal strength complete
    //   {"EVENTS_BCMATCH",          &NRF_RADIO->EVENTS_BCMATCH,     0x128}, // Bit counter reached bit count value
    //   {"EVENTS_CRCOK",            &NRF_RADIO->EVENTS_CRCOK,       0x130}, // Packet received with CRC ok
    //   {"EVENTS_CRCERROR",         &NRF_RADIO->EVENTS_CRCERROR,    0x134}, // Packet received with CRC error
    //#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) //|| defined(TESTING_ON_nRF52833DK)
    //   {"EVENTS_FRAMESTART",       &NRF_RADIO->EVENTS_FRAMESTART,  0x138}, // IEEE 802.15.4 length field received
    //   {"EVENTS_EDEND",            &NRF_RADIO->EVENTS_EDEND,       0x13C}, // Sampling of energy detection complete. A new ED sample is ready for readout from the RADIO.EDSAMPLE register.
    //   {"EVENTS_EDSTOPPED",        &NRF_RADIO->EVENTS_EDSTOPPED,   0x140}, // The sampling of energy detection has stopped
    //   {"EVENTS_CCAIDLE",          &NRF_RADIO->EVENTS_CCAIDLE,     0x144}, // Wireless medium in idle - clear to send
    //   {"EVENTS_CCABUSY",          &NRF_RADIO->EVENTS_CCABUSY,     0x148}, // Wireless medium busy - do not send
    //   {"EVENTS_CCASTOPPED",       &NRF_RADIO->EVENTS_CCASTOPPED,  0x14C}, // The CCA has stopped
    //   {"EVENTS_RATEBOOST",        &NRF_RADIO->EVENTS_RATEBOOST,   0x150}, // Ble_LR CI field received, receive mode is changed from Ble_LR125Kbit to Ble_LR500Kbit.
    //   {"EVENTS_TXREADY",          &NRF_RADIO->EVENTS_TXREADY,     0x154}, // RADIO has ramped up and is ready to be started TX path
    //   {"EVENTS_RXREADY",          &NRF_RADIO->EVENTS_RXREADY,     0x158}, // RADIO has ramped up and is ready to be started RX path
    //   {"EVENTS_MHRMATCH",         &NRF_RADIO->EVENTS_MHRMATCH,    0x15C}, // MAC header match found
    //   {"EVENTS_SYNC",             &NRF_RADIO->EVENTS_SYNC,        0x168}, // Preamble indicator.
    //   {"EVENTS_PHYEND",           &NRF_RADIO->EVENTS_PHYEND,      0x16C}, // Generated in Ble_LR125Kbit, Ble_LR500Kbit and Ieee802154_250Kbit modes when last bit is sent on air
    //   {"EVENTS_CTEPRESENT",       &NRF_RADIO->EVENTS_CTEPRESENT,  0x170}, // CTE is present (early warning right after receiving CTEInfo byte)

    Better still even with softdevice available FORK from the triggering timer is allowed:

    // Some of the PPI channels are pre-programmed. These channels cannot be configured by the CPU, but can
    // be added to groups and enabled and disabled like the general purpose PPI channels. The FORK TEP for
    // these channels are still programmable and can be used by the application.
    // Table 38: Pre-programmed channels
    // Channel EEP                        TEP
    // 20      TIMER0->EVENTS_COMPARE[0]  RADIO->TASKS_TXEN
    // 28      RTC0->EVENTS_COMPARE[0]    RADIO->TASKS_TXEN
    
    Example drive output pin high via FORK:
       NRF_PPI->FORK[20].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[3];
    and drive low after packet:
       NRF_PPI->CH[5].EEP = (uint32_t)&NRF_RADIO->EVENTS_PHYEND; NRF_PPI->CH[5].TEP   = (uint32_t)&NRF_GPIOTE->TASKS_CLR[2];
                                                                 NRF_PPI->FORK[5].TEP = (uint32_t)&NRF_GPIOTE->TASKS_CLR[3];
    

    I'm doing something similar, will probably use 1 node (Central) to transmit timesyncs which all the other BLE nodes use to correct their timestamps. Not using the published stuff. Ideally I'll make the update just before triggering the transmission, but for testing this code has about 100uS to change the data on the fly.

    // |<------------- TASKS_ENABLE
    // |          |<-- TASKS_START +- Stuff Timestamp
    // |  EVENTS  ^                |                                              |<---TASKS_END
    // |  READY   +---------------------------------------------------------------+
    // |          |                                                               |
    // +----------+                                                               +---------------
    // |
    // |  ADDRESS    +------------------------------------------------------------+
    // |             |                                                            |
    // +-------------+                                                            +---------------
    // |
    // |  PAYLOAD                                                             +---+
    // |                                                                      |   |
    // +----------------------------------------------------------------------+   +---------------
    // |
    // |  END                                                                    ++
    // |                                                                         ||
    // +-------------------------------------------------------------------------++---------------
    // |
    // |  PHYEND                                                                  +
    // |                                                                          |
    // +--------------------------------------------------------------------------+---------------

    Alternative is to report actual time to Central and let Central adjust differences for each peripheral without changing peripherals; might be simpler.

  • nataddrho said:

    I update the manufacturing data in the advertisement payload every 0.25 seconds using the application. Here I can also update a time stamp, using the application.

    However there is a delay between application updates and radio transmission.

    Yes, but I thought you could use the radion notification signal synchronize the advertisement update with the advertisement events to make the timing more deterministic.

    nataddrho said:
    I do not want call backs, and I do not want to involve the app with updating this part of the advertisement data. I want it to be autonomous outside of the application.

    Updating the data packet will require CPU involvment. 

    nataddrho said:
    there must be a buffer somewhere that is copied to the radio modulator, and this is what I want access to. It is a hack but it what I want to do. 

    If you are using the s132, you will need to disable MWU protection (see Memory isolation and runtime protection) to get write access to buffers kept in the Softdevice's RAM region. RE: Disable Softdevice sandboxing in fault handler  

  •   This is exactly the answer I was looking for. Thank you.

  • As Vidar says, the softdevice enables the MWU which appears to protect the RAM buffer if held in the Softdevice area; however if using the Softdevice there is a way to use DMA to write directly into that area without disabling the MWU. Put a fast SPI into loopback mode and point the Rx at the desired memory location pointed to by RADIO->PACKETPTR; desired data (the timestamp) is placed in the SPI Tx buffer.

    The packet RAM can be directly addressed by the RADIO PACKETPTR:


    Instead of using possible unknown pdu location
      // Update timestamp
      m_adv_pdu[21] = 0x12;
    
    Use instead the PACKETPTR
      // Update timestamp
      ((volatile uint8_t *)NRF_RADIO->PACKETPTR)[21] = 0x12;

    "When the SoftDevice is enabled, RAM up to APP_RAM_BASE will be used by the SoftDevice and will be write protected."

    "The MWU is only able to detect memory accesses in the Data RAM and Peripheral memory segments from the CPU. EasyDMA Peripherals accesses are not monitored by the MWU"

    Edit: Better still, trigger the SPI transfer from the timer FORK (or FORK the radio EVENTS_TXREADY); then there is no delay except for the 1uSec per byte SPI transfer, no cpu especially if the SPI TX points to the timestamp memory source location. Brilliant - we just invented true RAM-to-RAM DMA on the nRF52.

  • Couldn't resist trying this - it works! This test is without softdevice, uses timestamp to change name so easy to check on phone App. Point SPI tx to actual timestamp data for real, plus test with SD when get some time.

    // The MWU is only able to detect memory accesses in the Data RAM and Peripheral memory segments from the CPU. EasyDMA Peripherals accesses are not monitored by the MWU
    // When the SoftDevice is enabled, RAM up to APP_RAM_BASE will be used by the SoftDevice and will be write protected.
    // trigger the SPI transfer from the timer FORK (or the radio EVENTS_TXREADY); then there is no delay except for the 1uSec per byte SPI transfer,
    // no cpu especially if the SPI TX points to the timestamp memory source location. Brilliant - we just invented true RAM-to-RAM DMA on the nRF52
    void InitRadioProbes(void)
    {
       // Configuration               Direction                Input                          Pullup                 Drive Level        Sense Level
       // ==========================  =======================  =============================  =====================  =================  ====================
       nrf_gpio_cfg(RADIO_TEST_1_PIN, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL,   NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
       nrf_gpio_cfg(RADIO_TEST_2_PIN, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL,   NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
       nrf_gpio_cfg(LED_RED_PIN,      NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL,   NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
       nrf_gpio_cfg(LED_GREEN_PIN,    NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL,   NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
       nrf_gpio_cfg(LED_BLUE_PIN,     NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL,   NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
       // Configure GPIOTE to control output pin
       NRF_GPIOTE->CONFIG[0] = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos | GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos | RADIO_TEST_1_PIN << GPIOTE_CONFIG_PSEL_Pos;
       NRF_GPIOTE->CONFIG[1] = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos | GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos | RADIO_TEST_2_PIN << GPIOTE_CONFIG_PSEL_Pos;
       NRF_GPIOTE->CONFIG[2] = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos | GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos | LED_RED_PIN      << GPIOTE_CONFIG_PSEL_Pos;
       NRF_GPIOTE->CONFIG[3] = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos | GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos | LED_GREEN_PIN    << GPIOTE_CONFIG_PSEL_Pos;
       NRF_GPIOTE->CONFIG[4] = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos | GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos | LED_BLUE_PIN     << GPIOTE_CONFIG_PSEL_Pos;
       NRF_PPI->CH[0].EEP = (uint32_t)&NRF_RADIO->EVENTS_READY;    NRF_PPI->CH[0].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[0];
                                                                   NRF_PPI->FORK[0].TEP = (uint32_t)&NRF_SPIM0->TASKS_START;
       NRF_PPI->CH[1].EEP = (uint32_t)&NRF_RADIO->EVENTS_ADDRESS;  NRF_PPI->CH[1].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[1];
       NRF_PPI->CH[2].EEP = (uint32_t)&NRF_RADIO->EVENTS_PAYLOAD;  NRF_PPI->CH[2].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[2];
       NRF_PPI->CH[3].EEP = (uint32_t)&NRF_RADIO->EVENTS_END;      NRF_PPI->CH[3].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[3];
                                                                 NRF_PPI->FORK[3].TEP = (uint32_t)&NRF_SPIM0->TASKS_STOP;
    
       NRF_PPI->CH[4].EEP = (uint32_t)&NRF_RADIO->EVENTS_PHYEND; NRF_PPI->CH[4].TEP   = (uint32_t)&NRF_GPIOTE->TASKS_CLR[0];
                                                                 NRF_PPI->FORK[4].TEP = (uint32_t)&NRF_GPIOTE->TASKS_CLR[1];
    
       NRF_PPI->CH[5].EEP = (uint32_t)&NRF_RADIO->EVENTS_PHYEND; NRF_PPI->CH[5].TEP   = (uint32_t)&NRF_GPIOTE->TASKS_CLR[2];
                                                                 NRF_PPI->FORK[5].TEP = (uint32_t)&NRF_GPIOTE->TASKS_CLR[3];
    
       NRF_PPI->CHEN = PPI_CHEN_CH0_Msk | PPI_CHEN_CH1_Msk | PPI_CHEN_CH2_Msk | PPI_CHEN_CH3_Msk | PPI_CHEN_CH4_Msk | PPI_CHEN_CH5_Msk;
    }
    
    #define SCK_PIN  15  // spi clock
    #define MOSI_PIN 16  // spi mosi
    // Spoof actual timestamp data with a test buffer
    uint8_t TimestampTXBuf[4] = {'1','2','3','4'};
    void TestRamRamDMAviaSPI(void)
    {
      NRF_GPIO->PIN_CNF[SCK_PIN] =  0x301; // output, high drive high and low H0H1
      NRF_GPIO->PIN_CNF[MOSI_PIN] = 0x30D; // output, high drive high and low H0H1, input connected, pull-up
      NRF_SPIM0->PSEL.SCK  = SCK_PIN;
      NRF_SPIM0->PSEL.MOSI = MOSI_PIN;
      NRF_SPIM0->PSEL.MISO = MOSI_PIN; // Loopback
      NRF_SPIM0->CONFIG = 0;
      NRF_SPIM0->FREQUENCY = 0x80000000;  //8 Mbps
      NRF_SPIM0->ENABLE = 7;  //enable
      NRF_SPIM0->TXD.PTR = (uint32_t)TimestampTXBuf;
      NRF_SPIM0->TXD.MAXCNT = sizeof(TimestampTXBuf);
      NRF_SPIM0->RXD.PTR = (uint32_t)&((volatile uint8_t *)NRF_RADIO->PACKETPTR)[14];
      NRF_SPIM0->RXD.MAXCNT = sizeof(TimestampTXBuf);
      NRF_SPIM0->EVENTS_ENDTX = 0;
      NRF_SPIM0->EVENTS_ENDRX = 0;
    }
    
    {
      m_adv_pdu[0]  = 0x40 | 0x02;    // S0:     Optional First header byte: TxAdd|RxAdd (0x40) + PDU Type
      m_adv_pdu[1]  = 0;              // Length: Optional Second header byte: LENGTH (will be updated at the end)
      m_adv_pdu[2]  = 0;              // S1:     Optional Third header byte
    
      m_adv_pdu[3]  = (NRF_FICR->DEVICEADDR[0]      ) & 0xFF; // DEVICEADDR[0] bits 31-0 of the BLE MAC address.
      m_adv_pdu[4]  = (NRF_FICR->DEVICEADDR[0] >>  8) & 0xFF;
      m_adv_pdu[5]  = (NRF_FICR->DEVICEADDR[0] >> 16) & 0xFF;
      m_adv_pdu[6]  = (NRF_FICR->DEVICEADDR[0] >> 24)       ;
      m_adv_pdu[7]  = (NRF_FICR->DEVICEADDR[1]      ) & 0xFF;
      m_adv_pdu[8]  = (NRF_FICR->DEVICEADDR[1] >>  8) & 0xFF;
    
      m_adv_pdu[9]  = 0x02;  // 2 bytes (3 Tot)
      m_adv_pdu[10] = 0x01;  // BLE_GAP_AD_TYPE_FLAGS
      m_adv_pdu[11] = 0x06;  // BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE
    
      m_adv_pdu[12] = 0x05;  // 5 bytes (6 Tot)
      m_adv_pdu[13] = 0x09;  // BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME
      m_adv_pdu[14] = 'T';
      m_adv_pdu[15] = 'E';
      m_adv_pdu[16] = 'S';
      m_adv_pdu[17] = 'T';
    
      // Ensure packet pointer set before SPI init
      NRF_RADIO->PACKETPTR = (uint32_t) &m_adv_pdu[0];
      // Set up DMA transfer of Timestamp
      TestRamRamDMAviaSPI();
    }

Reply
  • Couldn't resist trying this - it works! This test is without softdevice, uses timestamp to change name so easy to check on phone App. Point SPI tx to actual timestamp data for real, plus test with SD when get some time.

    // The MWU is only able to detect memory accesses in the Data RAM and Peripheral memory segments from the CPU. EasyDMA Peripherals accesses are not monitored by the MWU
    // When the SoftDevice is enabled, RAM up to APP_RAM_BASE will be used by the SoftDevice and will be write protected.
    // trigger the SPI transfer from the timer FORK (or the radio EVENTS_TXREADY); then there is no delay except for the 1uSec per byte SPI transfer,
    // no cpu especially if the SPI TX points to the timestamp memory source location. Brilliant - we just invented true RAM-to-RAM DMA on the nRF52
    void InitRadioProbes(void)
    {
       // Configuration               Direction                Input                          Pullup                 Drive Level        Sense Level
       // ==========================  =======================  =============================  =====================  =================  ====================
       nrf_gpio_cfg(RADIO_TEST_1_PIN, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL,   NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
       nrf_gpio_cfg(RADIO_TEST_2_PIN, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL,   NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
       nrf_gpio_cfg(LED_RED_PIN,      NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL,   NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
       nrf_gpio_cfg(LED_GREEN_PIN,    NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL,   NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
       nrf_gpio_cfg(LED_BLUE_PIN,     NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL,   NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
       // Configure GPIOTE to control output pin
       NRF_GPIOTE->CONFIG[0] = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos | GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos | RADIO_TEST_1_PIN << GPIOTE_CONFIG_PSEL_Pos;
       NRF_GPIOTE->CONFIG[1] = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos | GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos | RADIO_TEST_2_PIN << GPIOTE_CONFIG_PSEL_Pos;
       NRF_GPIOTE->CONFIG[2] = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos | GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos | LED_RED_PIN      << GPIOTE_CONFIG_PSEL_Pos;
       NRF_GPIOTE->CONFIG[3] = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos | GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos | LED_GREEN_PIN    << GPIOTE_CONFIG_PSEL_Pos;
       NRF_GPIOTE->CONFIG[4] = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos | GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos | LED_BLUE_PIN     << GPIOTE_CONFIG_PSEL_Pos;
       NRF_PPI->CH[0].EEP = (uint32_t)&NRF_RADIO->EVENTS_READY;    NRF_PPI->CH[0].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[0];
                                                                   NRF_PPI->FORK[0].TEP = (uint32_t)&NRF_SPIM0->TASKS_START;
       NRF_PPI->CH[1].EEP = (uint32_t)&NRF_RADIO->EVENTS_ADDRESS;  NRF_PPI->CH[1].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[1];
       NRF_PPI->CH[2].EEP = (uint32_t)&NRF_RADIO->EVENTS_PAYLOAD;  NRF_PPI->CH[2].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[2];
       NRF_PPI->CH[3].EEP = (uint32_t)&NRF_RADIO->EVENTS_END;      NRF_PPI->CH[3].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[3];
                                                                 NRF_PPI->FORK[3].TEP = (uint32_t)&NRF_SPIM0->TASKS_STOP;
    
       NRF_PPI->CH[4].EEP = (uint32_t)&NRF_RADIO->EVENTS_PHYEND; NRF_PPI->CH[4].TEP   = (uint32_t)&NRF_GPIOTE->TASKS_CLR[0];
                                                                 NRF_PPI->FORK[4].TEP = (uint32_t)&NRF_GPIOTE->TASKS_CLR[1];
    
       NRF_PPI->CH[5].EEP = (uint32_t)&NRF_RADIO->EVENTS_PHYEND; NRF_PPI->CH[5].TEP   = (uint32_t)&NRF_GPIOTE->TASKS_CLR[2];
                                                                 NRF_PPI->FORK[5].TEP = (uint32_t)&NRF_GPIOTE->TASKS_CLR[3];
    
       NRF_PPI->CHEN = PPI_CHEN_CH0_Msk | PPI_CHEN_CH1_Msk | PPI_CHEN_CH2_Msk | PPI_CHEN_CH3_Msk | PPI_CHEN_CH4_Msk | PPI_CHEN_CH5_Msk;
    }
    
    #define SCK_PIN  15  // spi clock
    #define MOSI_PIN 16  // spi mosi
    // Spoof actual timestamp data with a test buffer
    uint8_t TimestampTXBuf[4] = {'1','2','3','4'};
    void TestRamRamDMAviaSPI(void)
    {
      NRF_GPIO->PIN_CNF[SCK_PIN] =  0x301; // output, high drive high and low H0H1
      NRF_GPIO->PIN_CNF[MOSI_PIN] = 0x30D; // output, high drive high and low H0H1, input connected, pull-up
      NRF_SPIM0->PSEL.SCK  = SCK_PIN;
      NRF_SPIM0->PSEL.MOSI = MOSI_PIN;
      NRF_SPIM0->PSEL.MISO = MOSI_PIN; // Loopback
      NRF_SPIM0->CONFIG = 0;
      NRF_SPIM0->FREQUENCY = 0x80000000;  //8 Mbps
      NRF_SPIM0->ENABLE = 7;  //enable
      NRF_SPIM0->TXD.PTR = (uint32_t)TimestampTXBuf;
      NRF_SPIM0->TXD.MAXCNT = sizeof(TimestampTXBuf);
      NRF_SPIM0->RXD.PTR = (uint32_t)&((volatile uint8_t *)NRF_RADIO->PACKETPTR)[14];
      NRF_SPIM0->RXD.MAXCNT = sizeof(TimestampTXBuf);
      NRF_SPIM0->EVENTS_ENDTX = 0;
      NRF_SPIM0->EVENTS_ENDRX = 0;
    }
    
    {
      m_adv_pdu[0]  = 0x40 | 0x02;    // S0:     Optional First header byte: TxAdd|RxAdd (0x40) + PDU Type
      m_adv_pdu[1]  = 0;              // Length: Optional Second header byte: LENGTH (will be updated at the end)
      m_adv_pdu[2]  = 0;              // S1:     Optional Third header byte
    
      m_adv_pdu[3]  = (NRF_FICR->DEVICEADDR[0]      ) & 0xFF; // DEVICEADDR[0] bits 31-0 of the BLE MAC address.
      m_adv_pdu[4]  = (NRF_FICR->DEVICEADDR[0] >>  8) & 0xFF;
      m_adv_pdu[5]  = (NRF_FICR->DEVICEADDR[0] >> 16) & 0xFF;
      m_adv_pdu[6]  = (NRF_FICR->DEVICEADDR[0] >> 24)       ;
      m_adv_pdu[7]  = (NRF_FICR->DEVICEADDR[1]      ) & 0xFF;
      m_adv_pdu[8]  = (NRF_FICR->DEVICEADDR[1] >>  8) & 0xFF;
    
      m_adv_pdu[9]  = 0x02;  // 2 bytes (3 Tot)
      m_adv_pdu[10] = 0x01;  // BLE_GAP_AD_TYPE_FLAGS
      m_adv_pdu[11] = 0x06;  // BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE
    
      m_adv_pdu[12] = 0x05;  // 5 bytes (6 Tot)
      m_adv_pdu[13] = 0x09;  // BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME
      m_adv_pdu[14] = 'T';
      m_adv_pdu[15] = 'E';
      m_adv_pdu[16] = 'S';
      m_adv_pdu[17] = 'T';
    
      // Ensure packet pointer set before SPI init
      NRF_RADIO->PACKETPTR = (uint32_t) &m_adv_pdu[0];
      // Set up DMA transfer of Timestamp
      TestRamRamDMAviaSPI();
    }

Children
No Data
Related