Monitor SPIS tx amount during transfer

I'm trying to figure out a way to monitor the TX data when using the SPIS. When the slave is requesting to send data back to the master, we are asserting a pin. When all of the data has been transferred, we would like to be able to de-assert the pin to let the master know it can end the transaction.

So far, it looks like none of the SPIS registers get updated until after the transaction.

Is there something I can monitor?

All suggestions welcome.

Parents
  • Hi Edvin,

    I do not control the master, and it is not an nRF part. TI, I think. The way they set it up is that they will keep the transaction going until I de-assert the slave write pin. Normally, it is sending my part data. There are rare occasions that the slave needs to write data back. Otherwise, the data is single direction from them to me.

    The root of the question is really whether or not there is access to the transaction as it is occuring? From what I can tell, the peripheral is communicating with DMA, and doesn't update the peripheral registers until the transaction is completed. Is that correct?

    Would taking a copy of the TXD.PTR value work, or is this always fixed to the start of the data?

    I offered your suggestion previously and they did not like it. I just want to make sure that I exhaust all options before I push back more.

  • brett_anderson said:
    The way they set it up is that they will keep the transaction going until I de-assert the slave write pin.

    That is a weird non-standard way to operate a SPI master. 

    brett_anderson said:

    From what I can tell, the peripheral is communicating with DMA, and doesn't update the peripheral registers until the transaction is completed. Is that correct?

    Yes. That is correct. 

    brett_anderson said:

    Would taking a copy of the TXD.PTR value work, or is this always fixed to the start of the data?

    That would be fixed to the first byte, I believe.

    Perhaps, if you know what CLK frequency the master is using, you can use a timer and the amount of data you know you will be sending to control this pin?

    In fact, you can probably even just count the pulses of the clock pin and a timer in counter mode tied together with PPI to trigger this pin. The only thing you need to keep track of then is the amount of data you will be sending. Do you always know that before the master suddenly starts the transaction?

    I can get back to details tomorrow (if you reply to me in this ticket) with more details. I believe you can just piggyback the Clock pin already being used by the SPI, but I will need to verify it.

    How much slack do you have? Do you need to stop at exactly the correct byte, or is it ok if the pin doesn't stop the master until a byte or two after all the relevant data was transferred?

    Best regards,

    Edvin

  • Thank you for the information Edvin! I know others will appreciate it as well. I think for completeness of the answer, it would be great if you could comment on if it is possible to add a GPIO event to a PPI task while the GPIO is also the clock of the peripheral. I wouldn't spend more time on it than that.

Reply Children
  • Hello,

    I realize now that I don't know whether you are using the nRF5 SDK or if you are using the nRF Connect SDK (NCS). I'll use the nRF5 SDK as reference, since it has an out of the box SPIS example. However, the PPI part of this will be the same in both cases, as it is a "bare metal" approach. PPI API exists in both the nRF5 SDK and in NCS, so if you like to use that you are free to do so, but you should get the idea looking at this.

    Ok, so now I have written the sample. Good news and bad news. Good news is that it seems to work without the need of extra GPIOs (Apart from the one that you toggle/set low/set high when the transfer is complete). The bad news is that it will not work if your SPI frequency is too high. I tested with 4MHz, and that is too much. It does work with 1MHz.

    I'll attach the entire main.c that I used for testing. I used this with the nRF5_SDK_17.1.0\examples\peripheral\spis

    (and I modified the pca10056/nRF52840 project to run on the pca10100\nRF52833).

    /**
     * Copyright (c) 2015 - 2021, 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.
     *
     */
    #include "sdk_config.h"
    #include "nrf_drv_spis.h"
    #include "nrf_gpio.h"
    #include "boards.h"
    #include "app_error.h"
    #include <string.h>
    #include "nrf_delay.h"
    
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #define SPIS_INSTANCE 1 /**< SPIS instance index. */
    static const nrf_drv_spis_t spis = NRF_DRV_SPIS_INSTANCE(SPIS_INSTANCE);/**< SPIS instance. */
    
    #define TEST_STRING "Nordic"
    static uint8_t       m_tx_buf[] = TEST_STRING;           /**< TX buffer. */
    static uint8_t       m_rx_buf[sizeof(TEST_STRING) + 1];    /**< RX buffer. */
    static const uint8_t m_length = sizeof(m_tx_buf);        /**< Transfer length. */
    
    static volatile bool spis_xfer_done; /**< Flag used to indicate that SPIS instance completed the transfer. */
    
    /**
     * @brief SPIS user event handler.
     *
     * @param event
     */
    void spis_event_handler(nrf_drv_spis_event_t event)
    {
        if (event.evt_type == NRF_DRV_SPIS_XFER_DONE)
        {
            spis_xfer_done = true;
            NRF_LOG_INFO(" Transfer completed. Received: %s",(uint32_t)m_rx_buf);
        }
    }
    
    #define GPIOTE_CH_IN 0
    #define GPIOTE_CH_OUT 1
    #define PPI_CH_0 0
    #define PPI_CH_1 1
    #define COUNT_PULSE_PIN APP_SPIS_SCK_PIN
    #define COUNT_LIMIT 56 //7 bytes, 8 clock cycles per byte
    
    void ppi_init(void)
    {
        NRF_LOG_INFO("PPI init");
    
        NRF_GPIOTE->CONFIG[GPIOTE_CH_IN]    = GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos |              // Event Mode (Input pin)
                                              GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos |     // Check for falling edges.
                                              COUNT_PULSE_PIN << GPIOTE_CONFIG_PSEL_Pos;// |                    // Pin number. Can be same as SPI CLK
                                              //GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos;        // Not used in Event mode.
    
        NRF_GPIOTE->CONFIG[GPIOTE_CH_OUT]   = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos |               // Task mode (Output pin)
                                              //GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos |   // Use this to toggle the pin.
                                              GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos |     // Use this to set the pin to low (need to set high manually)
                                              16 /*LED4*/ << GPIOTE_CONFIG_PSEL_Pos |                           // Pin number. Using LED for demonstration purposes.
                                              GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_MODE_Pos;             // Initial value. (High -> LED off).
    
        NRF_TIMER3->BITMODE                 = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;         // Using 32 bit counter
        NRF_TIMER3->PRESCALER               = 0;                                                                // Prescaler 0
        NRF_TIMER3->SHORTS                  = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << 0;                         // Clear the timer counter when timer reaches CC[0].
        NRF_TIMER3->MODE                    = TIMER_MODE_MODE_Counter << TIMER_MODE_MODE_Pos;                   // Timer used in counter mode. 
    
        NRF_TIMER3->CC[0] = COUNT_LIMIT;    // Set the counter limit (the amount of clock cycles you count before you toggle the pin.
    
        NRF_TIMER3->TASKS_START = 1;        // Turn on the timer3
    
    
        NRF_PPI->CH[PPI_CH_0].EEP           = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_CH_IN];                   // PPI channel 0 event
        NRF_PPI->CH[PPI_CH_0].TEP           = (uint32_t)&NRF_TIMER3->TASKS_COUNT;                               // PPI channel 0 task
    
        NRF_PPI->CH[PPI_CH_1].EEP           = (uint32_t)&NRF_TIMER3->EVENTS_COMPARE[0];                         // PPI channel 1 event
        NRF_PPI->CH[PPI_CH_1].TEP           = (uint32_t)&NRF_GPIOTE->TASKS_OUT[GPIOTE_CH_OUT];                  // PPI channel 1 task
    
        NRF_PPI->CHENSET |= (1 << PPI_CH_0);                                                                    // Enable PPI Channel 0
        NRF_PPI->CHENSET |= (1 << PPI_CH_1);                                                                    // Enable PPI Channel 1
    }
    
    int main(void)
    {
        // Enable the constant latency sub power mode to minimize the time it takes
        // for the SPIS peripheral to become active after the CSN line is asserted
        // (when the CPU is in sleep mode).
        NRF_POWER->TASKS_CONSTLAT = 1;
        uint32_t counter=0;
        uint32_t previous = 0;
    
        bsp_board_init(BSP_INIT_LEDS);
    
        APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    
        NRF_LOG_INFO("SPIS example");
    		NRF_LOG_FLUSH();
    
        nrf_drv_spis_config_t spis_config = NRF_DRV_SPIS_DEFAULT_CONFIG;
        spis_config.csn_pin               = APP_SPIS_CS_PIN;		// P0.31
        spis_config.miso_pin              = APP_SPIS_MISO_PIN;              // P0.30
        spis_config.mosi_pin              = APP_SPIS_MOSI_PIN;              // P0.29
        spis_config.sck_pin               = APP_SPIS_SCK_PIN;		// P0.26
    
        APP_ERROR_CHECK(nrf_drv_spis_init(&spis, &spis_config, spis_event_handler));
    
        ppi_init();
    
    
        while (1)
        {
            memset(m_rx_buf, 0, m_length);
            spis_xfer_done = false;
    
            APP_ERROR_CHECK(nrf_drv_spis_buffers_set(&spis, m_tx_buf, m_length, m_rx_buf, m_length));
            
    
            while (!spis_xfer_done)
            {
                __WFE();
            }
            nrf_delay_ms(200);                              // Adding delay for visibility on LED.
            //k_sleep(K_MSEC(200);
            NRF_GPIOTE->TASKS_SET[GPIOTE_CH_OUT] = 1;       // Reset the GPIO to high.
    
            // TODO: Uncomment the lines below for debugging purposes. 
            //If this is to make sense, you need to disable TIMER3->SHORTS (comment out that line) and clear the timer manually after logging.
            //NRF_TIMER3->TASKS_CAPTURE[1] = 1;
            //previous = counter;
            //counter = NRF_TIMER3->CC[1];
            //NRF_LOG_INFO("counter %d, diff %d", counter, counter-previous);
            //NRF_TIMER3->TASKS_CLEAR = 1;
            
    
    
            NRF_LOG_FLUSH();
    
            bsp_board_led_invert(BSP_BOARD_LED_0);
        }
    }
    

    The only parts you really need is:

    #define GPIOTE_CH_IN 0
    #define GPIOTE_CH_OUT 1
    #define PPI_CH_0 0
    #define PPI_CH_1 1
    #define COUNT_PULSE_PIN APP_SPIS_SCK_PIN
    #define COUNT_LIMIT 56 //7 bytes, 8 clock cycles per byte
    
    void ppi_init(void)
    {
        NRF_LOG_INFO("PPI init");
    
        NRF_GPIOTE->CONFIG[GPIOTE_CH_IN]    = GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos |              // Event Mode (Input pin)
                                              GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos |     // Check for falling edges.
                                              COUNT_PULSE_PIN << GPIOTE_CONFIG_PSEL_Pos;// |                    // Pin number. Can be same as SPI CLK
                                              //GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos;        // Not used in Event mode.
    
        NRF_GPIOTE->CONFIG[GPIOTE_CH_OUT]   = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos |               // Task mode (Output pin)
                                              //GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos |   // Use this to toggle the pin.
                                              GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos |     // Use this to set the pin to low (need to set high manually)
                                              16 /*LED4*/ << GPIOTE_CONFIG_PSEL_Pos |                           // Pin number. Using LED for demonstration purposes.
                                              GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_MODE_Pos;             // Initial value. (High -> LED off).
    
        NRF_TIMER3->BITMODE                 = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;         // Using 32 bit counter
        NRF_TIMER3->PRESCALER               = 0;                                                                // Prescaler 0
        NRF_TIMER3->SHORTS                  = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << 0;                         // Clear the timer counter when timer reaches CC[0].
        NRF_TIMER3->MODE                    = TIMER_MODE_MODE_Counter << TIMER_MODE_MODE_Pos;                   // Timer used in counter mode. 
    
        NRF_TIMER3->CC[0] = COUNT_LIMIT;    // Set the counter limit (the amount of clock cycles you count before you toggle the pin.
    
        NRF_TIMER3->TASKS_START = 1;        // Turn on the timer3
    
    
        NRF_PPI->CH[PPI_CH_0].EEP           = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_CH_IN];                   // PPI channel 0 event
        NRF_PPI->CH[PPI_CH_0].TEP           = (uint32_t)&NRF_TIMER3->TASKS_COUNT;                               // PPI channel 0 task
    
        NRF_PPI->CH[PPI_CH_1].EEP           = (uint32_t)&NRF_TIMER3->EVENTS_COMPARE[0];                         // PPI channel 1 event
        NRF_PPI->CH[PPI_CH_1].TEP           = (uint32_t)&NRF_GPIOTE->TASKS_OUT[GPIOTE_CH_OUT];                  // PPI channel 1 task
    
        NRF_PPI->CHENSET |= (1 << PPI_CH_0);                                                                    // Enable PPI Channel 0
        NRF_PPI->CHENSET |= (1 << PPI_CH_1);                                                                    // Enable PPI Channel 1
    }

    And somewhere in your code that is triggered when the SPI callback occurs, you can add this to reset the GPIO:

            nrf_delay_ms(200);                              // Adding delay for visibility on LED.
            //k_sleep(K_MSEC(200);
            NRF_GPIOTE->TASKS_SET[GPIOTE_CH_OUT] = 1;       // Reset the GPIO to high.
    
            // TODO: Uncomment the lines below for debugging purposes. 
            //If this is to make sense, you need to disable TIMER3->SHORTS (comment out that line) and clear the timer manually after logging.
            //NRF_TIMER3->TASKS_CAPTURE[1] = 1;
            //previous = counter;
            //counter = NRF_TIMER3->CC[1];
            //NRF_LOG_INFO("counter %d, diff %d", counter, counter-previous);
            //NRF_TIMER3->TASKS_CLEAR = 1;

    This implementation is perhaps not that intuitive. I tried to add comments to describe what I am doing. The idea behind PPI is that you link certain events from one peripheral to tasks in another (or the same) peripheral. The nice thing about it is that this will occur without the need to wait for the CPU to actually perform the tasks in the event handlers. So once this is set up, it will toggle the GPIO after X bytes has been transferred on the SPIS. Here we only use the CPU to reset the pin state to high some time after the transfer is done, and the GPIO was set to low. No need to do it fast or anything. Just make sure that it is set before you start the next transfer.

    Best regards,

    Edvin

Related