This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Contiguous output from SPIM with EasyDMA on the nRF52840

Hi,

As background, what I am trying to implement is continuous uninterrupted SPI master output from the nRF52840, compatible with the MCP4822 two-channel 12-bit DAC.
The MCP4822 requires programming waveforms from the nRF52840 as shown in this photo.


i.e. it requires two bytes of data to be sent via SPI while the CSN signal is low (to program one channel), followed by another two bytes of data to be sent via SPI while the CS signal is low (to program one channel), and then finally, it needs the LDAC signal to go low to cause the two analog outputs to change simultaneously.

The desired output frequency for this entire sequence is 16KHz, and I want too involve the CPU as little as possible in the process (as it will be busy doing other things.)

My understanding so far is that that the SPIM3 device is capable of controlling the CSN line, and that it can be used with EasyDMA in ArrayList mode.
I gather that the EasyDMA is limited to outputting a MAXCNT of 256 bytes, but that the ArrayList mode allows a list of pointers to contiguously allocated blocks of 256 bytes to be given to the EasyDMA, which allows it to transmit a longer burst of data.

It is not clear to me from the documentation how the timings of that data transmission are controlled.
For example:
What controls the SCK clock rate of the SPIM?
What controls how many bits/bytes of data are written out of the SPIM before the CSN line is brought high again?
What controls the time interval between successive SPIM writes (of 2-bytes each) from the ArrayList?

In order to allow contiguous transmission from the SPIM, I will need to have two memory "swing" buffers, wherein the CPU fills one buffer while the contents of the other is being transmitted by the SPIM. Then, when the transmission is complete, the buffers swap over and the process continues seamlessly.
I have several questions related to how to implement this.
Firstly I don't know how to trigger an interrupt routine when the SPIM's steadily incrementing TXD.PTR has exceeded a particular value within the ArrayList. (which will be longer than 256 bytes long)
Secondly, as there will typically be a delay between the interrupt triggering and the interrupt service routine (ISR) actually being executed, we need to allow the SPIM module to continue transmitting extra data from the buffer after the interrupt triggers, then, within the ISR read the TXD.PTR to see where the SPIM has actually go to, and use that value as the starting offset when the swing buffers are swapped over. The trouble is, I have no idea how to configure the SPIM and EasyDMA ArrayLists to behave this way, as I am lacking any detailed usage documentation.

For Example:
We could create two "swing buffer" EasyDMA ArrayLists, each with the capacity to transmit data for 30ms each. This could contiguously transmit a series of 20ms audio "frames".
If we fill the first 30ms capacity transmit buffer with 20ms of audio from frame 0, and 10ms from frame 1. The Easy DMA transmission is then started from the first buffer, address 0.
We then start filling a second 30ms buffer with 20ms of audio from frame 1 and 10ms from frame 2.
The interrupt is triggered when the TXD.PTR has reached the 20ms mark, but the SPIM continues to transmit subsequent data from the buffer until told otherwise.
The actual interrupt service routine is later entered and serviced at (say) 24ms. The EasyDMA transmission is then switched to start transmitting from the second buffer, at address 4ms.
The transfer between playing audio from the first buffer and playing audio from the second buffer is then seamless.

None of the above complexity would be required if the SPIM and EasyDMA supported ring-buffers, but oh well.

Finally, we have the issue of generating the LDAC pulses, which (ideally) need to be one SCK clock long, and synchronised with the end of each pair of SPIM transmissions.
I'm not sure how to implement this. Would something to do with the GPIOTE and PPI systems do the trick, perhaps if the timers were regularly re-synchronised within the ISR?

There are a number of errata involving the SPIM3 module, so answers that take those "gotchas" into account would be appreciated.

Regards,
Nicholas Lee

Parents
  • Hi,

     

    As background, what I am trying to implement is continuous uninterrupted SPI master output from the nRF52840, compatible with the MCP4822 two-channel 12-bit DAC.
    The MCP4822 requires programming waveforms from the nRF52840 as shown in this photo.


    i.e. it requires two bytes of data to be sent via SPI while the CSN signal is low (to program one channel), followed by another two bytes of data to be sent via SPI while the CS signal is low (to program one channel), and then finally, it needs the LDAC signal to go low to cause the two analog outputs to change simultaneously.

    The desired output frequency for this entire sequence is 16KHz, and I want too involve the CPU as little as possible in the process (as it will be busy doing other things.)

    My understanding so far is that that the SPIM3 device is capable of controlling the CSN line, and that it can be used with EasyDMA in ArrayList mode.
    I gather that the EasyDMA is limited to outputting a MAXCNT of 256 bytes, but that the ArrayList mode allows a list of pointers to contiguously allocated blocks of 256 bytes to be given to the EasyDMA, which allows it to transmit a longer burst of data.

    It is not clear to me from the documentation how the timings of that data transmission are controlled.
    For example:
    What controls the SCK clock rate of the SPIM?

    The clock frequency itself is controlled by the .FREQUENCY register, a transaction frequency would be approx. FREQUENCY / 16 bits in your case, excluding the CSN rise/fall.

    The sensor itself seems to latch the DAC output based on the /LDAC signal, so it seems you can send data to the device at a faster frequency, then toggle this pin at a given frequency to set the desired output in a given frequency.

    What controls how many bits/bytes of data are written out of the SPIM before the CSN line is brought high again?

     This is controlled by the DMA and the .MAXCNT that sets the amount of bytes for each section. See this section for more detailed information:

    https://www.nordicsemi.com/DocLib/Content/Product_Spec/nRF52840/latest/spim?912#concept_lhv_fx2_wr

    What controls the time interval between successive SPIM writes (of 2-bytes each) from the ArrayList?

     For peripherals that support EasyDMA list, the sequence of them all are similar in the setup:

    https://www.nordicsemi.com/DocLib/Content/Product_Spec/nRF52840/latest/easydma?103#arraylist

    Once configured, you trigger a _START task to run the sequence. When you trigger this is up to your application.

    For instance: This can be tied together using the PPI, where you can have a timer running in the background that triggers this every X us.

     

    In order to allow contiguous transmission from the SPIM, I will need to have two memory "swing" buffers, wherein the CPU fills one buffer while the contents of the other is being transmitted by the SPIM. Then, when the transmission is complete, the buffers swap over and the process continues seamlessly.
    I have several questions related to how to implement this.
    Firstly I don't know how to trigger an interrupt routine when the SPIM's steadily incrementing TXD.PTR has exceeded a particular value within the ArrayList. (which will be longer than 256 bytes long)
    Secondly, as there will typically be a delay between the interrupt triggering and the interrupt service routine (ISR) actually being executed, we need to allow the SPIM module to continue transmitting extra data from the buffer after the interrupt triggers, then, within the ISR read the TXD.PTR to see where the SPIM has actually go to, and use that value as the starting offset when the swing buffers are swapped over. The trouble is, I have no idea how to configure the SPIM and EasyDMA ArrayLists to behave this way, as I am lacking any detailed usage documentation.

     There's support in the SPIM driver to send repeated transfers in this function:

    https://www.nordicsemi.com/DocLib/Content/SDK_Doc/nRF5_SDK/v15-3-0/group__nrfx__spim#gae4b5f522da698ed536ce915ede1216ac

    The "algorithm" for using ArrayList is better described here: https://devzone.nordicsemi.com/f/nordic-q-a/18638/easydma-array-list

    For Example:
    We could create two "swing buffer" EasyDMA ArrayLists, each with the capacity to transmit data for 30ms each. This could contiguously transmit a series of 20ms audio "frames".
    If we fill the first 30ms capacity transmit buffer with 20ms of audio from frame 0, and 10ms from frame 1. The Easy DMA transmission is then started from the first buffer, address 0.
    We then start filling a second 30ms buffer with 20ms of audio from frame 1 and 10ms from frame 2.
    The interrupt is triggered when the TXD.PTR has reached the 20ms mark, but the SPIM continues to transmit subsequent data from the buffer until told otherwise.
    The actual interrupt service routine is later entered and serviced at (say) 24ms. The EasyDMA transmission is then switched to start transmitting from the second buffer, at address 4ms.
    The transfer between playing audio from the first buffer and playing audio from the second buffer is then seamless.

    None of the above complexity would be required if the SPIM and EasyDMA supported ring-buffers, but oh well.

    Finally, we have the issue of generating the LDAC pulses, which (ideally) need to be one SCK clock long, and synchronised with the end of each pair of SPIM transmissions.
    I'm not sure how to implement this. Would something to do with the GPIOTE and PPI systems do the trick, perhaps if the timers were regularly re-synchronised within the ISR?

    There are a number of errata involving the SPIM3 module, so answers that take those "gotchas" into account would be appreciated.

     As briefly mentioned, the /LDAC pin looks to be the synchronization at the DAC output, so the frequency of the SPI transfer can be faster than the update frequency of the DAC, as long as your /LDAC signal is synched towards your wanted frequency. If you use PPI + TIMER + GPIOTE, you can synchronize this sequence based on the /LDAC signal.

    Here's a theoretical approach (steps like GPIOTE, PPI, and TIMER initial setup is omitted):

    PPI channel 0:

    TASK: GPIOTE -> TASKS_CLR

    Event: TIMER -> EVENTS_COMPARE[0]

    PPI Channel 1:

    TASK: GPIOTE -> TASKS_SET

    Event: TIMER -> EVENTS_COMPARE[1]

    PPI.FORK: SPIM3 -> TASKS_START

    Timer in this case needs to be set up with capture CC[0] smaller than CC[1], then you add SHORT to clear the timer on EVENTS_COMPARE[1].

     

    Kind regards,

    Håkon

  • Dear Håkon,
    I have been trying very hard to understand all this and I have done a lot of reading. However, I think I need a few points clarifying before I will be able to implement anything that works. I'm still new to developing with the nRF52840, so I don't understand all the jargon being referred to.

    What does your cryptic answer "PPI.FORK: SPIM3 -> TASKS_START" actually mean?
    What is triggering what, in what order?
    Is this a SPIM starting event triggering the TIMER, or the TIMER event triggering the SPIM to start?

    Regarding the EasyDMA list, I have found the documentation is unintelligible (to me) and the associated example code doesn't actually compile.
    Ref: https://www.nordicsemi.com/DocLib/Content/Product_Spec/nRF52840/latest/easydma?103#arraylist

    This page shows code allocating an array using

    ArrayList_type ReaderList[3]  __at__ 0x20000000;

    However __at__ doesn't work under SEGGER, as it isn't GCC compatible code. Does this matter or will I get a HARD FAULT if the array memory is allocated at a compiler chosen location?

    Also, the following example code doesn't work as MYPERIPHERAL isn't defined and the use needs to be psychic to read the developer's minds to know what code is used to create a  MYPERIPHERAL .

      MYPERIPHERAL->READER.MAXCNT = BUFFER_SIZE;
      MYPERIPHERAL->READER.PTR = &ReaderList;
      MYPERIPHERAL->READER.LIST = MYPERIPHERAL_READER_LIST_ArrayList;

    I'm not sure how this pseudo-code relates to anything, and I want to use the nfrx driver anyway.

    In my case, where each SPI write is 2-bytes long, would my BUFFERSIZE be 2?
    If I want to store an ArrayList of ( 30ms of these at 32KHz), i.e. 960 writes, would the code be as follows:

    #define BUFFER_SIZE  2
      
      typedef struct ArrayList
      {
        uint8_t buffer[BUFFER_SIZE];
      } ArrayList_type;
      
      ArrayList_type WriterList[960]

    I was also unable to work out how to use the nrfx_spim_xfer command as the "documentation" you pointed me to was just a list of function names with no explanation of how they work, how to use them or what they do.
    That might be a great resource if you are already an expert and need a reminder, but it is utterly useless for a beginner who wants to get started in a timely manner.

    So far, my initialisation code looks like this, but I'm not sure if I am missing any vital steps, or how to start or trigger a recurring sequence of writes from the EasyDMA ArrayList

    #define SPI_INSTANCE  3
    #define BUFFER_SIZE 2 // There are 2 bytes in each SPI transfer to the DAC
    #define NUMBER_OF_BUFFERS 960 // There are 960 2-byte transfers to the DAC every 30ms
    static const nrfx_spim_t spim3 = NRFX_SPIM_INSTANCE(SPI_INSTANCE);  /**< SPI instance. */

    typedef struct ArrayList
    {
      volatile uint8_t buffer[BUFFER_SIZE];
    } ArrayList_type;
    static volatile ArrayList_type WriterList0[NUMBER_OF_BUFFERS];
    static volatile ArrayList_type WriterList1[NUMBER_OF_BUFFERS];

    void SPIM3_init(void)
    {
      nrfx_err_t err_code = NRFX_SUCCESS;

      nrfx_spim_config_t spi_config = NRFX_SPIM_DEFAULT_CONFIG;
      spi_config.ss_pin         = SPI_CSN_PIN;
      spi_config.miso_pin       = NRFX_SPIM_PIN_NOT_USED;
      spi_config.mosi_pin       = SPI_MOSI_PIN;
      spi_config.sck_pin        = SPI_SCK_PIN;
      spi_config.dcx_pin        = NRFX_SPIM_PIN_NOT_USED;
      spi_config.use_hw_ss      = true;
      spi_config.ss_active_high = false;
      spi_config.frequency      = NRF_SPIM_FREQ_2M;
      spi_config.mode           = NRF_SPIM_MODE_0;
      spi_config.bit_order      = NRF_SPIM_BIT_ORDER_MSB_FIRST;

      err_code = nrfx_spim_init(&spim3, &spi_config, NULL, NULL);
      APP_ERROR_CHECK(err_code);

      nrfx_spim_xfer_desc_t xfer_desc = NRFX_SPIM_XFER_TRX(WriterList0, BUFFER_SIZE, NULL, 0);

      err_code = nrfx_spim_xfer(&spim3, &xfer_desc, NRFX_SPIM_FLAG_TX_POSTINC||NRFX_SPIM_FLAG_REPEATED_XFER);
      APP_ERROR_CHECK(err_code);
    }





  • Hi Nicholas,

     

    I am sorry for the late reply. Staffing during easter is quite low, as its a public holiday in Norway.

    To test the "timer2_event_handler" isolated, you can trigger the TASKS_COUNT in your while loop:

    m_timer2.p_reg->TASKS_COUNT = 1;
    nrf_delay_ms(1);

    And in your event handler, you can for instance toggle a LED.

    I have close to the same setup as you, and the handler runs as expected here.

    Not sure how your setup is wrt. the nrfx err_code returns, but I needed to define it like this in my project, as it would overflow with the define that you had at my end:

    #define NFRX_APP_ERROR_CHECK(err_code) APP_ERROR_CHECK(err_code)

     

    Are you sure that it's not one of the error checks that fails on your side, and thus not enabling the timer2 interrupt?

     

    As a side note:

    Your counter settings looks to be counting -1 each time, which would (eventually) make the pointer go down into used RAM area in the timer2_event_handler.() Try setting this to 320 instead of 319.

     

    Kind regards,

    Håkon

  • I tried your test to trigger "timer2_event_handler" isolated, and it didn't work.
    The function timer2_event_handler never ever gets called.
    i.e.

     // Configure the IO pins and their directions
      GPIO_init();
      SPIM3_init();
      TIMERs_init();
    
      for (i = 0; i < 100; i++) {
        printf("Hello World %d!\n", i);
      }
      do {
        i++;
        m_timer2.p_reg->TASKS_COUNT = 1;
        nrf_delay_ms(1);
      } while (1);

    At the top of the code you will note the following macro:

    // Translation macro for error handling
    #define NFRX_APP_ERROR_CHECK(err_code) APP_ERROR_CHECK(err_code-NRFX_ERROR_BASE_NUM+NRF_SUCCESS)

    This is similar to your macro, but is suitable for use when nfrx functions are used instead of the old nrf functions; which I am doing.

    Single stepping through the code, checking the function return values, and highlighting timer_cfg to see what is being sent, I observe the following. (It all "seems" to be ok, so I don't understand why the timer2 callback isn't getting called)

    In the above image, the interrupt priority is 7

    To be honest, I don't understand how interrupts are handled inside the NRFX functions. Do I need to "enable interrupts" or add some other setting in a config file, or initialise something, to get this working??

  • This is strange. The timer handler runs in all conditions at my end.

    Here's the main.c I have now, where I've commented out the PPI channels, as well as disabled TIMER1 from running.

    //#include <stdio.h>
    #pragma import(__use_no_semihosting_swi)
    #include <stdlib.h>
    // #include "sdk_config.h"
    #include "nrfx_config.h"
    #include "nrfx_spim.h"
    #include "nrfx_timer.h"
    #include "nrf_gpio.h"
    #include "nrfx_gpiote.h"
    #include "nrfx_ppi.h"
    #include "nrfx_coredep.h" // for delay function
    #include "app_error.h"
    #include <math.h>
    
    #include "nrf_delay.h"
    
    // Define the pins used for testing on the nRF52840_DK board
    #include "pca10056.h"
    
    // Translation macro for error handling
    #define NFRX_APP_ERROR_CHECK(err_code) APP_ERROR_CHECK(err_code)
    
    // Define the pins used on the Headphones board
    #define AIN0_PIN     NRF_GPIO_PIN_MAP(0,2)  // P0.02 = SIO_02
    #define AIN1_PIN     NRF_GPIO_PIN_MAP(0,3)  // P0.03 = SIO_03
    #define SPI_MISO_PIN NRF_GPIO_PIN_MAP(0,4)  // P0.04 = SIO_04
    #define UART_RTS_PIN NRF_GPIO_PIN_MAP(0,5)  // P0.05 = SIO_05
    #define UART_TX_PIN  NRF_GPIO_PIN_MAP(0,6)  // P0.06 = SIO_06
    #define UART_CTS_PIN NRF_GPIO_PIN_MAP(0,7)  // P0.07 = SIO_07
    #define UART_RX_PIN  NRF_GPIO_PIN_MAP(0,8)  // P0.08 = SIO_08
    #define GPOUT_PIN    NRF_GPIO_PIN_MAP(0,12) // P0.12 = SIO_12
    #define SW_PIN       NRF_GPIO_PIN_MAP(0,13) // P0.13 = SIO_13
    #define B_PIN        NRF_GPIO_PIN_MAP(0,14) // P0.14 = SIO_16
    #define A_PIN        NRF_GPIO_PIN_MAP(0,16) // P0.16 = SIO_14
    #define RESET_PIN    NRF_GPIO_PIN_MAP(0,18) // P0.18 = SIO_18
    #define POWER_EN_PIN NRF_GPIO_PIN_MAP(0,24) // P0.24 = SIO_24
    #define I2C_SDA_PIN  NRF_GPIO_PIN_MAP(0,26) // P0.26 = SIO_26
    #define I2C_SCL_PIN  NRF_GPIO_PIN_MAP(0,27) // P0.27 = SIO_27
    #define AIN4_PIN     NRF_GPIO_PIN_MAP(0,28) // P0.28 = SIO_28
    #define AIN5_PIN     NRF_GPIO_PIN_MAP(0,29) // P0.29 = SIO_29
    #define AIN6_PIN     NRF_GPIO_PIN_MAP(0,30) // P0.30 = SIO_30
    #define AIN7_PIN     NRF_GPIO_PIN_MAP(0,31) // P0.31 = SIO_31
    
    #define KG_PIN       NRF_GPIO_PIN_MAP(1,2)  // P1.02 = SIO_34
    #define KR_PIN       NRF_GPIO_PIN_MAP(1,4)  // P1.04 = SIO_36
    #define SW1_PIN      NRF_GPIO_PIN_MAP(1,5)  // P1.05 = SIO_37
    #define SPI_MOSI_PIN NRF_GPIO_PIN_MAP(1,8)  // P1.08 = SIO_40
    #define SPI_SCK_PIN  NRF_GPIO_PIN_MAP(1,9)  // P1.09 = SIO_41
    #define SW3_PIN      NRF_GPIO_PIN_MAP(1,10) // P1.10 = SIO_42
    #define SW2_PIN      NRF_GPIO_PIN_MAP(1,11) // P1.11 = SIO_43
    #define SPI_CSN_PIN  NRF_GPIO_PIN_MAP(1,12) // P1.12 = SIO_44
    #define SPI_LDAC_PIN NRF_GPIO_PIN_MAP(1,13) // P1.13 = SIO_45
    #define JACK_EN_PIN  NRF_GPIO_PIN_MAP(1,14) // P1.14 = SIO_46
    #define ISET2_PIN    NRF_GPIO_PIN_MAP(1,15) // P1.15 = SIO_47
    
    #define DAC_A    0x0000 // DAC Channel Selection (0=>A, 1=>B)
    #define DAC_B    0x8000 // DAC Channel Selection (0=>A, 1=>B)
    #define DAC_GAx1 0x2000 // DAC Gain Selection (0=>2x, 1=>1x)
    #define DAC_GAx2 0x0000 // DAC Gain Selection (0=>2x, 1=>1x)
    #define DAC_RUN  0x1000 // DAC Shutdown (0=>SHUTDOWN, 1=>RUNNING)
    #define DAC_SHDN 0x0000 // DAC Shutdown (0=>SHUTDOWN, 1=>RUNNING)
    
    #define AngleStep 0.0098174770424681038701957605727484
    
    // EasyDMA configuration for the SPI DAC
    #define SPI_INSTANCE 3 // SPI instance index.
    #define BUFFER_SIZE 2 // There are 2 bytes in each SPI transfer to the DAC
    #define NUMBER_OF_BUFFERS 960 // There are 960 2-byte transfers to the DAC every 30ms
    static const nrfx_spim_t spim3 = NRFX_SPIM_INSTANCE(SPI_INSTANCE);  // SPI instance.
    static volatile bool WriterListTransmitting = 0;
      
    typedef struct ArrayList
    {
      volatile uint8_t buffer[BUFFER_SIZE];
    } ArrayList_type;
    static volatile ArrayList_type WriterList0[NUMBER_OF_BUFFERS] __attribute__((at(0x20005000))); //NB: __at__ is not supported in SEGGER due to GCC restrictions.
    static volatile ArrayList_type WriterList1[NUMBER_OF_BUFFERS] __attribute__((at(0x20006000))); //NB: __at__ is not supported in SEGGER due to GCC restrictions.
    
    // Declare the Timer instances (NB: Timer 0 is used by the soft device)
    static const nrfx_timer_t m_timer1 = NRFX_TIMER_INSTANCE(1);
    static const nrfx_timer_t m_timer2 = NRFX_TIMER_INSTANCE(2);
    
    // Declare the PPI instances
    nrf_ppi_channel_t ppi_channel0;
    nrf_ppi_channel_t ppi_channel1;
    nrf_ppi_channel_t ppi_channel2;
    nrf_ppi_channel_t ppi_channel3;
    nrf_ppi_channel_t ppi_channel4;
    
    void GPIO_init(void)
    {
      nrfx_err_t err_code = NRFX_SUCCESS;
      // Initialise the GPIOTE driver if it hasn't already been initilaised
      if(!nrfx_gpiote_is_init())
      {
        err_code = nrfx_gpiote_init();
        NFRX_APP_ERROR_CHECK(err_code);
      }
    
      // set the IO direction and IO type (digital/analog) for every used pin on the IC.
    
      // Digital Outputs
      nrf_gpio_cfg_output(SPI_SCK_PIN);
      nrf_gpio_cfg_output(SPI_MOSI_PIN);
      nrf_gpio_cfg_output(SPI_CSN_PIN);
      nrf_gpio_cfg_output(SPI_LDAC_PIN);
      nrf_gpio_cfg_output(ISET2_PIN);
      nrf_gpio_cfg_output(KR_PIN);
      nrf_gpio_cfg_output(KG_PIN);
      nrf_gpio_cfg_output(POWER_EN_PIN);
      nrf_gpio_cfg_output(GPOUT_PIN);
      nrf_gpio_cfg_output(UART_TX_PIN);
      nrf_gpio_cfg_output(I2C_SDA_PIN);
      nrf_gpio_cfg_output(I2C_SCL_PIN);
      nrf_gpio_cfg_output(UART_RTS_PIN);
      nrf_gpio_cfg_output(JACK_EN_PIN);
    
      // Digital Inputs
      nrf_gpio_cfg_input(SPI_MISO_PIN, NRF_GPIO_PIN_PULLDOWN);
      nrf_gpio_cfg_input(RESET_PIN, NRF_GPIO_PIN_PULLUP);
      nrf_gpio_cfg_input(A_PIN, NRF_GPIO_PIN_PULLDOWN);
      nrf_gpio_cfg_input(B_PIN, NRF_GPIO_PIN_PULLDOWN);
      nrf_gpio_cfg_input(SW_PIN, NRF_GPIO_PIN_PULLDOWN);
      nrf_gpio_cfg_input(GPOUT_PIN, NRF_GPIO_PIN_PULLUP);
      nrf_gpio_cfg_input(UART_RX_PIN, NRF_GPIO_PIN_PULLUP);
      nrf_gpio_cfg_input(UART_CTS_PIN, NRF_GPIO_PIN_PULLUP);
      nrf_gpio_cfg_input(AIN7_PIN, NRF_GPIO_PIN_NOPULL);
      nrf_gpio_cfg_input(AIN6_PIN, NRF_GPIO_PIN_NOPULL);
      nrf_gpio_cfg_input(AIN5_PIN, NRF_GPIO_PIN_NOPULL);
      nrf_gpio_cfg_input(AIN4_PIN, NRF_GPIO_PIN_NOPULL);
      nrf_gpio_cfg_input(AIN1_PIN, NRF_GPIO_PIN_NOPULL);
      nrf_gpio_cfg_input(AIN0_PIN, NRF_GPIO_PIN_NOPULL);
      nrf_gpio_cfg_input(SW1_PIN, NRF_GPIO_PIN_PULLDOWN);
      nrf_gpio_cfg_input(SW2_PIN, NRF_GPIO_PIN_PULLDOWN);
      nrf_gpio_cfg_input(SW3_PIN, NRF_GPIO_PIN_PULLDOWN);
    
      // configure gpiote with the task to toggle the bit. Takes the pin to toggle and the task config
      nrfx_gpiote_out_config_t config = NRFX_GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
      err_code = nrfx_gpiote_out_init(SPI_LDAC_PIN, &config);
      NFRX_APP_ERROR_CHECK(err_code);
    }
    
    void timer2_event_handler(nrf_timer_event_t event_type, void *p_context)
    {
      nrfx_err_t err_code = NRFX_SUCCESS;
      volatile uint32_t uiTXD_PTR;
      volatile uint32_t uiOverflow;
      printf("PTR: %x\n", spim3.p_reg->TXD.PTR);
    
      // NOTES: Consider connecting WriterListTransmitting to a GPIO line to test that it toggles evenly at 16KHz
      if (WriterListTransmitting==0)
      {
        // As a test indicator, clear the KG_PIN
        // TODO remove this after testing is complete
        nrf_gpio_pin_clear(KG_PIN); // P1.02 = SIO_34
    
        // Switch the SPIM3 transmission to the other ArrayList, at a starting offset 
        // relative to the value read from TXD.PTR
    
        // Read the TXD.PTR value from the currently transmitting ArrayList to see 
        // where in the buffer the transmit process has currently reached
        uiTXD_PTR = spim3.p_reg->TXD.PTR;
    
        // uiOverflow is the number of bytes past the 20ms point into WriterList0 that the TXD.PTR has reached
        uiOverflow = (uiTXD_PTR - (uint32_t)WriterList0) - 1280; // 1280 bytes is (20ms * 16KHz * 4 bytes)
    
        // Modify the TXD.PTR to point to the beginning of the new array list, plus the overflow amount
        spim3.p_reg->TXD.PTR = (uint32_t)WriterList1 + uiOverflow;
    
        WriterListTransmitting=1;
    
        // TODO: Write new audio data to the relevant sections of the two 30ms swing-buffer(s)
      }
      else
      {
        // As a test indicator, set the KG_PIN
        // TODO remove this after testing is complete
        nrf_gpio_pin_set(KG_PIN); // P1.02 = SIO_34
    
        // Switch the SPIM3 transmission to the other ArrayList, at a starting offset 
        // relative to the value read from TXD.PTR
    
        // Read the TXD.PTR value from the currently transmitting ArrayList to see 
        // where in the buffer the transmit process has currently reached
        uiTXD_PTR = spim3.p_reg->TXD.PTR;
    
        // uiOverflow is the number of bytes past the 20ms point into WriterList0 that the TXD.PTR has reached
        uiOverflow = (uiTXD_PTR - (uint32_t)WriterList1) - 1280; // 1280 bytes is (20ms * 16KHz * 4 bytes)
    
        // Modify the TXD.PTR to point to the beginning of the new array list, plus the overflow amount
        spim3.p_reg->TXD.PTR = (uint32_t)WriterList0 + uiOverflow;
    
        WriterListTransmitting=0;
    
        // TODO: Write new audio data to the relevant sections of the two 30ms swing-buffer(s)
      }
    }
    
    static void TIMERs_init(void)
    {
      // Configure and initialise Timer 0 as a 16-bit 4MHz counter.
      nrfx_err_t err_code = NRFX_SUCCESS;
      nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
      timer_cfg.frequency = NRF_TIMER_FREQ_4MHz;
      timer_cfg.mode      = NRF_TIMER_MODE_TIMER;
      timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_16;
      err_code = nrfx_timer_init(&m_timer1, &timer_cfg, NULL);
      NFRX_APP_ERROR_CHECK(err_code);
    
      // Allocate the four PPI channels
      err_code = nrfx_ppi_channel_alloc(&ppi_channel0);
      NFRX_APP_ERROR_CHECK(err_code);
      err_code = nrfx_ppi_channel_alloc(&ppi_channel1);
      NFRX_APP_ERROR_CHECK(err_code);
      err_code = nrfx_ppi_channel_alloc(&ppi_channel2);
      NFRX_APP_ERROR_CHECK(err_code);
      err_code = nrfx_ppi_channel_alloc(&ppi_channel3);
      NFRX_APP_ERROR_CHECK(err_code);
    
      // set TIMER1 COMPARE[0] count to be 15 (so it generates an event every time the Timer starts or is reset)
      nrfx_timer_compare(&m_timer1,
                         NRF_TIMER_CC_CHANNEL0,
                         15,
                         false);
    
      // set TIMER1 COMPARE[1] count to be 140 (repeats at 32KHz = 31.25uS)
      nrfx_timer_compare(&m_timer1,
                         NRF_TIMER_CC_CHANNEL1,
                         140,
                         false);
    
      // Set TIMER1 COMPARE[2] count to be 246 (0.5uS before the COMPARE[2] event)
      nrfx_timer_compare(&m_timer1,
                         NRF_TIMER_CC_CHANNEL2,
                         244,
                         false);
    
      uint32_t compare0_timer_event_address = nrfx_timer_event_address_get(&m_timer1, NRF_TIMER_EVENT_COMPARE0);
      uint32_t compare1_timer_event_address = nrfx_timer_event_address_get(&m_timer1, NRF_TIMER_EVENT_COMPARE1);
      uint32_t compare2_timer_event_address = nrfx_timer_event_address_get(&m_timer1, NRF_TIMER_EVENT_COMPARE2);
      uint32_t compare3_timer_event_address = nrfx_timer_event_address_get(&m_timer1, NRF_TIMER_EVENT_COMPARE3);
    
      uint32_t spim_start_task = nrfx_spim_start_task_get(&spim3);
      uint32_t gpiote_clr_task = nrfx_gpiote_clr_task_addr_get(SPI_LDAC_PIN);
      uint32_t gpiote_set_task = nrfx_gpiote_set_task_addr_get(SPI_LDAC_PIN);
    
      // set TIMER1 COMPARE[3] count to be 250 (repeats at 16KHz = 62.5uS)
      // SHORT the COMPARE[3] event and CLEAR task.
      nrfx_timer_extended_compare(&m_timer1,
                                  NRF_TIMER_CC_CHANNEL3,
                                  252, // TODO Find out why this needs to be 252 to get real-world 16KHz
                                  NRF_TIMER_SHORT_COMPARE3_CLEAR_MASK,
                                  false);
    
      // Connect COMPARE[0] event to TASKS_START of SPIM3 to start transmission every time the event occurs
      err_code = nrfx_ppi_channel_assign(ppi_channel0, compare0_timer_event_address, spim_start_task);
      NFRX_APP_ERROR_CHECK(err_code);
    
      // Connect COMPARE[1] event to TASKS_START of SPIM3 to start transmission every time the event occurs
      err_code = nrfx_ppi_channel_assign(ppi_channel1, compare1_timer_event_address, spim_start_task);
      NFRX_APP_ERROR_CHECK(err_code);
    
      // Connect COMPARE[2] event to GPIOTE->TASKS_CLR for the /LDAC signal to make it go LOW
      err_code = nrfx_ppi_channel_assign(ppi_channel2, compare2_timer_event_address, gpiote_clr_task);
      NFRX_APP_ERROR_CHECK(err_code);
    
      // Connect COMPARE[3] event to GPIOTE->TASKS_CLR for the /LDAC signal to make it go HIGH
      err_code = nrfx_ppi_channel_assign(ppi_channel3, compare3_timer_event_address, gpiote_set_task);
      NFRX_APP_ERROR_CHECK(err_code);
    
      // Enable the GPIOTE task events for SPI_LDAC_PIN
      nrfx_gpiote_out_task_enable(SPI_LDAC_PIN);
    
      // Enable the PPI channels
      /*err_code = nrfx_ppi_channel_enable(ppi_channel0);
      NFRX_APP_ERROR_CHECK(err_code);
      err_code = nrfx_ppi_channel_enable(ppi_channel1);
      NFRX_APP_ERROR_CHECK(err_code);
      err_code = nrfx_ppi_channel_enable(ppi_channel2);
      NFRX_APP_ERROR_CHECK(err_code);
      err_code = nrfx_ppi_channel_enable(ppi_channel3);
      NFRX_APP_ERROR_CHECK(err_code);
    */
    
      // Configure TIMER2 in COUNTER mode, 
      // PPI.FORK COMPARE[3] to COUNT input of TIMER2, to cause TIMER2 to increment on each COMPARE[2] event 
      // Set TIMER2 COMPARE[0] count to be 319
      // When the count reaches 320, we have sent 20ms of samples (2 lots of 2 byte tramsissions)
      // Connect TIMER2 COMPARE[0] event to an Interrupt
    
       // Configure and initialise Timer 2 in COUNTER mode
      timer_cfg.frequency = NRF_TIMER_FREQ_4MHz;
      timer_cfg.mode      = NRF_TIMER_MODE_COUNTER;
      timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_16;
      err_code = nrfx_timer_init(&m_timer2, &timer_cfg, timer2_event_handler);
      NFRX_APP_ERROR_CHECK(err_code);
    
      // set TIMER2 COMPARE[0] count to be 320 (20ms of 2x 16-bit at 16KHz)
      // SHORT the TIMER2 COMPARE[0] event and CLEAR task.
      nrfx_timer_extended_compare(&m_timer2,
                                  NRF_TIMER_CC_CHANNEL0,
                                  320,
                                  NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                  true);
    
      // Fork Timer 1's COMPARE[3] event to Timer 2's count input task
      uint32_t timer_count_task_addr = nrfx_timer_task_address_get(&m_timer2, NRF_TIMER_TASK_COUNT);
      m_timer2.p_reg->TASKS_COUNT=1;
      
      err_code = nrfx_ppi_channel_fork_assign(ppi_channel3, timer_count_task_addr);
      NFRX_APP_ERROR_CHECK(err_code);
    
      // Clear Timer 2
      nrfx_timer_clear(&m_timer2);
      // Enable Timer 2
      nrfx_timer_enable(&m_timer2);
    
    
      // Clear Timer 1
      nrfx_timer_clear(&m_timer1);
      // Enable Timer 1
      //nrfx_timer_enable(&m_timer1);
    }
    
    // Configure the SPIM3, PPI and GPIOTE to enable regular writing to the stereo DAC
    void SPIM3_init(void)
    {
      nrfx_err_t err_code = NRFX_SUCCESS;
    
      nrfx_spim_config_t spi_config = NRFX_SPIM_DEFAULT_CONFIG;
      spi_config.ss_pin         = SPI_CSN_PIN;
      spi_config.miso_pin       = NRFX_SPIM_PIN_NOT_USED;
      spi_config.mosi_pin       = SPI_MOSI_PIN;
      spi_config.sck_pin        = SPI_SCK_PIN;
      spi_config.dcx_pin        = NRFX_SPIM_PIN_NOT_USED;
      spi_config.use_hw_ss      = true;
      spi_config.ss_active_high = false;
      spi_config.frequency      = NRF_SPIM_FREQ_1M;
      spi_config.mode           = NRF_SPIM_MODE_0;
      spi_config.bit_order      = NRF_SPIM_BIT_ORDER_MSB_FIRST;
    
      err_code = nrfx_spim_init(&spim3, &spi_config, NULL, NULL);
      NFRX_APP_ERROR_CHECK(err_code);
    
      // Enable the use of EasyDMA ArrayList
      spim3.p_reg->TXD.LIST = 1;
    
      // Function for initializing the SPI master driver instance.
      nrfx_spim_xfer_desc_t xfer_desc = NRFX_SPIM_XFER_TX(WriterList0, BUFFER_SIZE);
    
      // Function for starting the SPI data transfer
      // NRFX_SPIM_FLAG_REPEATED_XFER = Flag indicating that the transfer will be executed multiple times. 
      // NRFX_SPIM_FLAG_TX_POSTINC = TX buffer address incremented after transfer. 
      // NRFX_SPIM_FLAG_HOLD_XFER = Set up the transfer but do not start it. 
      err_code = nrfx_spim_xfer(&spim3, &xfer_desc, NRFX_SPIM_FLAG_TX_POSTINC|NRFX_SPIM_FLAG_REPEATED_XFER|NRFX_SPIM_FLAG_HOLD_XFER);
      NFRX_APP_ERROR_CHECK(err_code);
    }
    
    //********************************************************************
    //
    //       main()
    //
    //  Function description
    //   Application entry point.
    //
    
    void main(void)
    {
      unsigned short i;
      unsigned short s;
      float m_fSin;
      float m_fCos;
      float m_fAngle;
    
      // Read the HWID field of the CONFIGID register in FICR to determine the IC's version
      // number, and therefore apply the correct errata work-arounds.
    
      // Write some initial data to the SPI write buffers.
      // There are 480 pairs of 16-bit values
      for(i=0;i<480;i++)
      {
        m_fAngle = ((float)i) * AngleStep;    // 640 steps gives 2*PI radians
        m_fSin = (sinf(m_fAngle) + 1) * 2047; // Gain gives full 12-bit range
        m_fCos = (cosf(m_fAngle) + 1) * 2047; // Gain gives full 12-bit range
    
        // Channel A (Even addresses)
        s = DAC_A | DAC_GAx1 | DAC_RUN |0xAAA;
        WriterList0[i*2].buffer[0] = s >> 8;   // MSB
        WriterList0[i*2].buffer[1] = s & 0xFF; // LSB
        WriterList1[i*2].buffer[0] = s >> 8;   // MSB
        WriterList1[i*2].buffer[1] = s & 0xFF; // LSB
        // Channel B (Odd Addresses)
        s = DAC_B | DAC_GAx1 | DAC_RUN | 0x555;
        WriterList0[(i*2)+1].buffer[0] = s >> 8;   // MSB
        WriterList0[(i*2)+1].buffer[1] = s & 0xFF; // LSB
        WriterList1[(i*2)+1].buffer[0] = s >> 8;   // MSB
        WriterList1[(i*2)+1].buffer[1] = s & 0xFF; // LSB
    
        /*
        // Channel A (Even addresses)
        s = DAC_A | DAC_GAx1 | DAC_RUN | (unsigned short)lroundf(m_fSin);
        WriterList0[i*2].buffer[0] = s >> 8;   // MSB
        WriterList0[i*2].buffer[1] = s & 0xFF; // LSB
        WriterList1[i*2].buffer[0] = s >> 8;   // MSB
        WriterList1[i*2].buffer[1] = s & 0xFF; // LSB
        // Channel B (Odd Addresses)
        s = DAC_B | DAC_GAx1 | DAC_RUN | (unsigned short)lroundf(m_fCos);
        WriterList0[(i*2)+1].buffer[0] = s >> 8;   // MSB
        WriterList0[(i*2)+1].buffer[1] = s & 0xFF; // LSB
        WriterList1[(i*2)+1].buffer[0] = s >> 8;   // MSB
        WriterList1[(i*2)+1].buffer[1] = s & 0xFF; // LSB
        */
      }
    
      // Configure the IO pins and their directions
      GPIO_init();
      //SPIM3_init();
      TIMERs_init();
      while(1)
      {
        m_timer2.p_reg->TASKS_COUNT = 1;
        nrf_delay_ms(10);
      }
    }
    
    /*************************** End of file ****************************/

     

    Do you still, with the above code, that timer2 does not execute?

    What version of the nRF52840 are you running? Mine has the chip markings QIAAC0, meaning revision 1.

     

    Kind regards,

    Håkon

  • Dear Hakon,
    I am using an nRF54840 DK board with a chip marked with QIAAC0, the same as yours.

    I tried replacing my main.c with your above code, so we are running identical code.

    When running your code, the "timer2_event_handler" function never gets called, and the KG_PIN never toggles.

    The only change I have had to make to get your code to run in my SEGGER environment is to replace this macro:

    #define NFRX_APP_ERROR_CHECK(err_code) APP_ERROR_CHECK(err_code)

    with this macro...

    #define NFRX_APP_ERROR_CHECK(err_code) APP_ERROR_CHECK(err_code-NRFX_ERROR_BASE_NUM+NRF_SUCCESS)

    Without doing this, calling NFRX_APP_ERROR_CHECK(err_code); results in APP_ERROR_CHECK breaking permanently when it doesn't recognise NRFX_SUCCESS as a good return code.
    I think tis is because I include nrfx header files, and I am not including any of the legacy nrf header files.
    This image shows where your code was stopping.

  • Dear Hakon,
    As we have established that the problem seems to be something outside the main.c file, I have zipped-up my project folder and put it in DropBox for you to download.

    [redacted]

    This should allow you to replicate the project , and the issue, and hopefully to spot what configuration setting is preventing the timer2 from triggering its callback function.

Reply Children
  • Dear Nicholas,

     

    Thanks for the project!

    I believe I have found the issue. It is related to the nrfx_glue.h file, which is the file that sets the macros for NVIC interrupt handling, which is the template file (with empty definitions, unfortunately). The one that has functions implemented is in $(SdkDir)/integrations/nrfx, and this one also redefines the NRFX_ return codes as well.

    After adding the NRFX_IRQ_* macros manually in your nrfx_glue.h file, it seems to run the handler correctly.

    Could you try this and see if it works on your side?

     

    Kind regards,

    Håkon

  • I facing the same issue. My timer interrupt routine gets never called although I can see transfers on the SPI lines. What was the exact change you have to make in the nrfx_glue.h. And is it somewhere documented?

  • Dear Constantin,

    Documented??, Good grief no.

    Based on the paucity of explanatory documentation or plain-english tutorials, there's basically no way to start from scratch and create a working project. For developers, it's basically so bad that the only viable technique is to start from one of their example projects and then make tiny incremental changes (testing after each change) until you get the functionality you need. I don't think Nordic fully appreciates just how incomprehensible their SDKs and macro-based configuration system documentation is for developers, or how unnecessarily time-consuming and expensive (in wages) that makes it to design Nordic chips into products. I really hope they address this issue.


    Here is my working version of nrfx_glue.h, I hope it helps. Good luck.

    /**
     * Copyright (c) 2017 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    
    #ifndef NRFX_GLUE_H__
    #define NRFX_GLUE_H__
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    /**
     * @defgroup nrfx_glue nrfx_glue.h
     * @{
     * @ingroup nrfx
     *
     * @brief This file contains macros that should be implemented according to
     *        the needs of the host environment into which @em nrfx is integrated.
     */
    
    #include <legacy/apply_old_config.h>
    
    #include <soc/nrfx_irqs.h>
    
    //------------------------------------------------------------------------------
    
    #include <nrf_assert.h>
    /**
     * @brief Macro for placing a runtime assertion.
     *
     * @param expression  Expression to evaluate.
     */
    #define NRFX_ASSERT(expression)     ASSERT(expression)
    
    #include <app_util.h>
    /**
     * @brief Macro for placing a compile time assertion.
     *
     * @param expression  Expression to evaluate.
     */
    #define NRFX_STATIC_ASSERT(expression)  STATIC_ASSERT(expression)
    
    //------------------------------------------------------------------------------
    
    #ifdef NRF51
    #ifdef SOFTDEVICE_PRESENT
    #define INTERRUPT_PRIORITY_IS_VALID(pri) (((pri) == 1) || ((pri) == 3))
    #else
    #define INTERRUPT_PRIORITY_IS_VALID(pri) ((pri) < 4)
    #endif //SOFTDEVICE_PRESENT
    #else
    #ifdef SOFTDEVICE_PRESENT
    #define INTERRUPT_PRIORITY_IS_VALID(pri) ((((pri) > 1) && ((pri) < 4)) || \
                                              (((pri) > 4) && ((pri) < 8)))
    #else
    #define INTERRUPT_PRIORITY_IS_VALID(pri) ((pri) < 8)
    #endif //SOFTDEVICE_PRESENT
    #endif //NRF52
    
    /**
     * @brief Macro for setting the priority of a specific IRQ.
     *
     * @param irq_number  IRQ number.
     * @param priority    Priority to set.
     */
    #define NRFX_IRQ_PRIORITY_SET(irq_number, priority) \
        _NRFX_IRQ_PRIORITY_SET(irq_number, priority)
    static inline void _NRFX_IRQ_PRIORITY_SET(IRQn_Type irq_number,
                                              uint8_t   priority)
    {
        ASSERT(INTERRUPT_PRIORITY_IS_VALID(priority));
        NVIC_SetPriority(irq_number, priority);
    }
    
    /**
     * @brief Macro for enabling a specific IRQ.
     *
     * @param irq_number  IRQ number.
     */
    #define NRFX_IRQ_ENABLE(irq_number)  _NRFX_IRQ_ENABLE(irq_number)
    static inline void _NRFX_IRQ_ENABLE(IRQn_Type irq_number)
    {
        NVIC_EnableIRQ(irq_number);
    }
    
    /**
     * @brief Macro for checking if a specific IRQ is enabled.
     *
     * @param irq_number  IRQ number.
     *
     * @retval true  If the IRQ is enabled.
     * @retval false Otherwise.
     */
    #define NRFX_IRQ_IS_ENABLED(irq_number)  _NRFX_IRQ_IS_ENABLED(irq_number)
    static inline bool _NRFX_IRQ_IS_ENABLED(IRQn_Type irq_number)
    {
        return 0 != (NVIC->ISER[irq_number / 32] & (1UL << (irq_number % 32)));
    }
    
    /**
     * @brief Macro for disabling a specific IRQ.
     *
     * @param irq_number  IRQ number.
     */
    #define NRFX_IRQ_DISABLE(irq_number)  _NRFX_IRQ_DISABLE(irq_number)
    static inline void _NRFX_IRQ_DISABLE(IRQn_Type irq_number)
    {
        NVIC_DisableIRQ(irq_number);
    }
    
    /**
     * @brief Macro for setting a specific IRQ as pending.
     *
     * @param irq_number  IRQ number.
     */
    #define NRFX_IRQ_PENDING_SET(irq_number) _NRFX_IRQ_PENDING_SET(irq_number)
    static inline void _NRFX_IRQ_PENDING_SET(IRQn_Type irq_number)
    {
        NVIC_SetPendingIRQ(irq_number);
    }
    
    /**
     * @brief Macro for clearing the pending status of a specific IRQ.
     *
     * @param irq_number  IRQ number.
     */
    #define NRFX_IRQ_PENDING_CLEAR(irq_number) _NRFX_IRQ_PENDING_CLEAR(irq_number)
    static inline void _NRFX_IRQ_PENDING_CLEAR(IRQn_Type irq_number)
    {
        NVIC_ClearPendingIRQ(irq_number);
    }
    
    /**
     * @brief Macro for checking the pending status of a specific IRQ.
     *
     * @retval true  If the IRQ is pending.
     * @retval false Otherwise.
     */
    #define NRFX_IRQ_IS_PENDING(irq_number) _NRFX_IRQ_IS_PENDING(irq_number)
    static inline bool _NRFX_IRQ_IS_PENDING(IRQn_Type irq_number)
    {
        return (NVIC_GetPendingIRQ(irq_number) == 1);
    }
    
    #include <nordic_common.h>
    #include <app_util_platform.h>
    /**
     * @brief Macro for entering into a critical section.
     */
    #define NRFX_CRITICAL_SECTION_ENTER()   CRITICAL_REGION_ENTER()
    
    /**
     * @brief Macro for exiting from a critical section.
     */
    #define NRFX_CRITICAL_SECTION_EXIT()    CRITICAL_REGION_EXIT()
    
    //------------------------------------------------------------------------------
    
    /**
     * @brief When set to a non-zero value, this macro specifies that
     *        @ref nrfx_coredep_delay_us uses a precise DWT-based solution.
     *        A compilation error is generated if the DWT unit is not present
     *        in the SoC used.
     */
    #define NRFX_DELAY_DWT_BASED 0
    
    #include <soc/nrfx_coredep.h>
    
    #define NRFX_DELAY_US(us_time) nrfx_coredep_delay_us(us_time)
    
    //------------------------------------------------------------------------------
    
    #include <soc/nrfx_atomic.h>
    
    /**
     * @brief Atomic 32 bit unsigned type.
     */
    #define nrfx_atomic_t               nrfx_atomic_u32_t
    
    /**
     * @brief Stores value to an atomic object and returns previously stored value.
     *
     * @param[in] p_data  Atomic memory pointer.
     * @param[in] value   Value to store.
     *
     * @return Old value stored into atomic object.
     */
    #define NRFX_ATOMIC_FETCH_STORE(p_data, value) nrfx_atomic_u32_fetch_store(p_data, value)
    
    /**
     * @brief Performs logical OR operation on an atomic object and returns previously stored value.
     *
     * @param[in] p_data  Atomic memory pointer.
     * @param[in] value   Value of second operand of OR operation.
     *
     * @return Old value stored into atomic object.
     */
    #define NRFX_ATOMIC_FETCH_OR(p_data, value)   nrfx_atomic_u32_fetch_or(p_data, value)
    
    /**
     * @brief Performs logical AND operation on an atomic object and returns previously stored value.
     *
     * @param[in] p_data  Atomic memory pointer.
     * @param[in] value   Value of second operand of AND operation.
     *
     * @return Old value stored into atomic object.
     */
    #define NRFX_ATOMIC_FETCH_AND(p_data, value)   nrfx_atomic_u32_fetch_and(p_data, value)
    
    /**
     * @brief Performs logical XOR operation on an atomic object and returns previously stored value.
     *
     * @param[in] p_data  Atomic memory pointer.
     * @param[in] value   Value of second operand of XOR operation.
     *
     * @return Old value stored into atomic object.
     */
    #define NRFX_ATOMIC_FETCH_XOR(p_data, value)   nrfx_atomic_u32_fetch_xor(p_data, value)
    
    /**
     * @brief Performs logical ADD operation on an atomic object and returns previously stored value.
     *
     * @param[in] p_data  Atomic memory pointer.
     * @param[in] value   Value of second operand of ADD operation.
     *
     * @return Old value stored into atomic object.
     */
    #define NRFX_ATOMIC_FETCH_ADD(p_data, value)   nrfx_atomic_u32_fetch_add(p_data, value)
    
    /**
     * @brief Performs logical SUB operation on an atomic object and returns previously stored value.
     *
     * @param[in] p_data  Atomic memory pointer.
     * @param[in] value   Value of second operand of SUB operation.
     *
     * @return Old value stored into atomic object.
     */
    #define NRFX_ATOMIC_FETCH_SUB(p_data, value)   nrfx_atomic_u32_fetch_sub(p_data, value)
    
    //------------------------------------------------------------------------------
    #ifndef NRFX_CUSTOM_ERROR_CODES
    
    #include <sdk_errors.h>
    /**
     * @brief When set to a non-zero value, this macro specifies that the
     *        @ref nrfx_error_codes and the @ref ret_code_t type itself are defined
     *        in a customized way and the default definitions from @c <nrfx_error.h>
     *        should not be used.
     */
    #define NRFX_CUSTOM_ERROR_CODES 1
    
    typedef ret_code_t nrfx_err_t;
    
    #define NRFX_SUCCESS                    NRF_SUCCESS
    #define NRFX_ERROR_INTERNAL             NRF_ERROR_INTERNAL
    #define NRFX_ERROR_NO_MEM               NRF_ERROR_NO_MEM
    #define NRFX_ERROR_NOT_SUPPORTED        NRF_ERROR_NOT_SUPPORTED
    #define NRFX_ERROR_INVALID_PARAM        NRF_ERROR_INVALID_PARAM
    #define NRFX_ERROR_INVALID_STATE        NRF_ERROR_INVALID_STATE
    #define NRFX_ERROR_INVALID_LENGTH       NRF_ERROR_INVALID_LENGTH
    #define NRFX_ERROR_TIMEOUT              NRF_ERROR_TIMEOUT
    #define NRFX_ERROR_FORBIDDEN            NRF_ERROR_FORBIDDEN
    #define NRFX_ERROR_NULL                 NRF_ERROR_NULL
    #define NRFX_ERROR_INVALID_ADDR         NRF_ERROR_INVALID_ADDR
    #define NRFX_ERROR_BUSY                 NRF_ERROR_BUSY
    #define NRFX_ERROR_ALREADY_INITIALIZED  NRF_ERROR_MODULE_ALREADY_INITIALIZED
    
    #define NRFX_ERROR_DRV_TWI_ERR_OVERRUN  NRF_ERROR_DRV_TWI_ERR_OVERRUN
    #define NRFX_ERROR_DRV_TWI_ERR_ANACK    NRF_ERROR_DRV_TWI_ERR_ANACK
    #define NRFX_ERROR_DRV_TWI_ERR_DNACK    NRF_ERROR_DRV_TWI_ERR_DNACK
    
    #endif // NRFX_CUSTOM_ERROR_CODES
    //------------------------------------------------------------------------------
    
    #include <sdk_resources.h>
    /**
     * @brief Bitmask defining PPI channels reserved to be used outside of nrfx.
     */
    #define NRFX_PPI_CHANNELS_USED  NRF_PPI_CHANNELS_USED
    
    /**
     * @brief Bitmask defining PPI groups reserved to be used outside of nrfx.
     */
    #define NRFX_PPI_GROUPS_USED    NRF_PPI_GROUPS_USED
    
    /**
     * @brief Bitmask defining SWI instances reserved to be used outside of nrfx.
     */
    #define NRFX_SWI_USED           NRF_SWI_USED
    
    /**
     * @brief Bitmask defining TIMER instances reserved to be used outside of nrfx.
     */
    #define NRFX_TIMERS_USED        NRF_TIMERS_USED
    
    /** @} */
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif // NRFX_GLUE_H__
    

Related