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);
    }





  • 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.

  • 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__
    

Reply
  • 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__
    

Children
No Data
Related