This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CC2650: CC2650 SPI DMA in callback mode

Part Number: CC2650

Hello 

Does TI have any example of using SPI DMA in callback mode. 

Below is a description of my problem -

I have to sense two ADC pins sequentially and store the data to the on board flash. To do this, I understand the I have to open an ADC channel -> take a reading and store in flash -> close channel -> open new channel and repeat process. Example of commands below 

adc = ADC_open(Board_ADCBufChannel0, &params);

res = ADC_convert(adc, &adcValue1[1]);

ExtFlash_write(flash_addr+ 2*i, 2, (uint8_t*)&adcValue1[1]);

ADC_close(adc);

Unfortunately the open and closing of ADC channels and flash write takes  a long time and I am only able to get a sampling frequency of 5KHz . I realized that the flash write was using SPI DMA in blocking mode. I changed this to callback mode, threw in a callback function but cannot get the code to execute well... mostly because of my limited FW experience :) 

So a few questions here. 

1. Is my algorithm above the best way to accomplish my task. Should I be saving a chunk of ADC data from two channels to RAM and then transfer from RAM to FLASH. But even this would need the RAM to FLASH transfer to be handled by SPI DMA in callback mode while CPU continues to read ADC channels. 

2. Is there an example of SPI DMA is callback mode that would help me get started. 

Thanks

Raghu

  • Hi,

    1- I don't know all the constraints of your application so it's difficult to give you a good answer. Yes, write in flash consume time, but it also prevents you to loose data in case of an unexpected reboot. So, you have to find a balance between your constraints and make the best decision.

    2- You can have a look to the examples provided in your SDK.

    Regards,

  • Hmm... ok. that didn't really answer any of my questions:). I guess I can try to be more specific. So here goes

    1. Is opening and closing of a current ADC channel the only way to move to the next channel? Is there a way to open two ADC channels at once and sequentially get ADC readings from the two channels?

    2. I did look at examples in the SDK and only then posted the question. Any example I found with SPI was using blocking mode.  So I was hoping someone in TI would already know because I would think collecting data and storing in flash would be a very common use case. Maybe I missed something because there are so many examples.

    Thanks

    Raghu

  • Hi,

    1- The solution you are describing to sample several ADC channels is not the only one. This question is often asked on our forum: for example here https://e2e.ti.com/support/wireless-connectivity/bluetooth/f/538/t/486753 or here https://e2e.ti.com/support/wireless-connectivity/bluetooth/f/538/t/794816.

    2- The example spislave (in examples\rtos\<BOARD>\drivers\spislave) uses the SPI in callback mode. In addition, you can find some threads related to this question on the forum: [EDIT: link updated] https://e2e.ti.com/support/wireless-connectivity/bluetooth/f/538/t/683421

    Regards,

  • Hi Clement

    1. Thanks for the links. I will work with these.

    2. Im not sure where this path exists examples\rtos\<BOARD>\drivers\spislave in my installation. 

    Is this in my BLE SDK directory or the tirtos_cc13xx_cc26xx_2_21_01_08 directory? I do not have any folder called rtos in any of the installations. 

    The 3rd link seems to be broken. There is an access denied error on clicking it. 

    https://e2e.ti.com/support/wireless-connectivity/bluetooth/f/538/t/68342. 

    Also, Im guessing I can make changes to a spislave example to convert it to a spamaster example? 

    Thanks

    Raghu

  • Hi,

    I have made a mistake in the link I gave you. Sorry for that. The right link is the following: https://e2e.ti.com/support/wireless-connectivity/bluetooth/f/538/t/683421 (I have updated my previous message)

    If needed, you can find the spislave example on TI clouds: http://dev.ti.com/tirex/explore/node?node=AEyFXH-qFUs4SoXlGQpF.w__krol.2c__LATEST . I have also pasted the spislave.c file below. Of course you can modify the example to get master mode.

    I hope this will help,

    Regards,

    /*
     * Copyright (c) 2018-2019, Texas Instruments Incorporated
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     *
     * *  Redistributions of source code must retain the above copyright
     *     notice, this list of conditions and the following disclaimer.
     *
     * *  Redistributions in binary form 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.
     *
     * *  Neither the name of Texas Instruments Incorporated nor the names of
     *     its contributors may be used to endorse or promote products derived
     *     from this software without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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.
     */
    
    /*
     *  ======== spislave.c ========
     */
    #include <stddef.h>
    #include <stdint.h>
    #include <string.h>
    
    /* POSIX Header files */
    #include <pthread.h>
    #include <semaphore.h>
    #include <unistd.h>
    
    /* Driver Header files */
    #include <ti/drivers/GPIO.h>
    #include <ti/drivers/SPI.h>
    #include <ti/display/Display.h>
    
    /* Example/Board Header files */
    #include "Board.h"
    
    #define THREADSTACKSIZE (1024)
    
    #define SPI_MSG_LENGTH  (30)
    #define SLAVE_MSG       ("Hello from slave, msg#: ")
    
    #define MAX_LOOP        (10)
    
    static Display_Handle display;
    
    unsigned char slaveRxBuffer[SPI_MSG_LENGTH];
    unsigned char slaveTxBuffer[SPI_MSG_LENGTH];
    
    /* Semaphore to block slave until transfer is complete */
    sem_t slaveSem;
    
    /*
     *  ======== transferCompleteFxn ========
     *  Callback function for SPI_transfer().
     */
    void transferCompleteFxn(SPI_Handle handle, SPI_Transaction *transaction)
    {
        sem_post(&slaveSem);
    }
    
    /*
     * ======== slaveThread ========
     *  Slave SPI sends a message to master while simultaneously receiving a
     *  message from the master.
     */
    void *slaveThread(void *arg0)
    {
        SPI_Handle      slaveSpi;
        SPI_Params      spiParams;
        SPI_Transaction transaction;
        uint32_t        i;
        bool            transferOK;
        int32_t         status;
    
        /*
         * Board_SPI_MASTER_READY & Board_SPI_SLAVE_READY are GPIO pins connected
         * between the master & slave.  These pins are used to synchronize
         * the master & slave applications via a small 'handshake'.  The pins
         * are later used to synchronize transfers & ensure the master will not
         * start a transfer until the slave is ready.  These pins behave
         * differently between spimaster & spislave examples:
         *
         * spislave example:
         *     * Board_SPI_MASTER_READY is configured as an input pin.  During the
         *       'handshake' this pin is read & a high value will indicate the
         *       master is ready to run the application.  Afterwards, the pin is
         *       read to determine if the master has already opened its SPI pins.
         *       The master will pull this pin low when it has opened its SPI.
         *
         *     * Board_SPI_SLAVE_READY is configured as an output pin.  During the
         *       'handshake' this pin is changed from low to high output.  This
         *       notifies the master the slave is ready to run the application.
         *       Afterwards, the pin is used by the slave to notify the master it
         *       is ready for a transfer.  When ready for a transfer, this pin will
         *       be pulled low.
         *
         * Below we set Board_SPI_MASTER_READY & Board_SPI_SLAVE_READY initial
         * conditions for the 'handshake'.
         */
        GPIO_setConfig(Board_SPI_SLAVE_READY, GPIO_CFG_OUTPUT | GPIO_CFG_OUT_LOW);
        GPIO_setConfig(Board_SPI_MASTER_READY, GPIO_CFG_INPUT);
    
        /*
         * Handshake - Set Board_SPI_SLAVE_READY high to indicate slave is ready
         * to run.  Wait for Board_SPI_MASTER_READY to be high.
         */
        GPIO_write(Board_SPI_SLAVE_READY, 1);
        while (GPIO_read(Board_SPI_MASTER_READY) == 0) {}
    
        /*
         * Create synchronization semaphore; this semaphore will block the slave
         * until a transfer is complete.  The slave is configured in callback mode
         * to allow us to configure the SPI transfer & then notify the master the
         * slave is ready.  However, we must still wait for the current transfer
         * to be complete before setting up the next.  Thus, we wait on slaveSem;
         * once the transfer is complete the callback function will unblock the
         * slave.
         */
        status = sem_init(&slaveSem, 0, 0);
        if (status != 0) {
            Display_printf(display, 0, 0, "Error creating slaveSem\n");
    
            while(1);
        }
    
        /*
         * Wait until master SPI is open.  When the master is configuring SPI pins
         * the clock may toggle from low to high (or high to low depending on
         * polarity).  If using 3-pin SPI & the slave has been opened before the
         * master, clock transitions may cause the slave to shift bits out assuming
         * it is an actual transfer.  We can prevent this behavior by opening the
         * master first & then opening the slave.
         */
        while (GPIO_read(Board_SPI_MASTER_READY)) {}
    
        /*
         * Open SPI as slave in callback mode; callback mode is used to allow us to
         * configure the transfer & then set Board_SPI_SLAVE_READY high.
         */
        SPI_Params_init(&spiParams);
        spiParams.frameFormat = SPI_POL0_PHA1;
        spiParams.mode = SPI_SLAVE;
        spiParams.transferCallbackFxn = transferCompleteFxn;
        spiParams.transferMode = SPI_MODE_CALLBACK;
        slaveSpi = SPI_open(Board_SPI_SLAVE, &spiParams);
        if (slaveSpi == NULL) {
            Display_printf(display, 0, 0, "Error initializing slave SPI\n");
            while (1);
        }
        else {
            Display_printf(display, 0, 0, "Slave SPI initialized\n");
        }
    
        /* Copy message to transmit buffer */
        strncpy((char *) slaveTxBuffer, SLAVE_MSG, SPI_MSG_LENGTH);
    
        for (i = 0; i < MAX_LOOP; i++) {
            /* Initialize slave SPI transaction structure */
            slaveTxBuffer[sizeof(SLAVE_MSG) - 1] = (i % 10) + '0';
            memset((void *) slaveRxBuffer, 0, SPI_MSG_LENGTH);
            transaction.count = SPI_MSG_LENGTH;
            transaction.txBuf = (void *) slaveTxBuffer;
            transaction.rxBuf = (void *) slaveRxBuffer;
    
            /* Toggle on user LED, indicating a SPI transfer is in progress */
            GPIO_toggle(Board_GPIO_LED1);
    
            /*
             * Setup SPI transfer; Board_SPI_SLAVE_READY will be set to notify
             * master the slave is ready.
             */
            transferOK = SPI_transfer(slaveSpi, &transaction);
            if (transferOK) {
                GPIO_write(Board_SPI_SLAVE_READY, 0);
    
                /* Wait until transfer has completed */
                sem_wait(&slaveSem);
    
                /*
                 * Drive Board_SPI_SLAVE_READY high to indicate slave is not ready
                 * for another transfer yet.
                 */
                GPIO_write(Board_SPI_SLAVE_READY, 1);
    
    
                Display_printf(display, 0, 0, "Slave received: %s", slaveRxBuffer);
            }
            else {
                Display_printf(display, 0, 0, "Unsuccessful slave SPI transfer");
            }
        }
    
        SPI_close(slaveSpi);
    
        /* Example complete - set pins to a known state */
        GPIO_setConfig(Board_SPI_MASTER_READY, GPIO_CFG_OUTPUT | GPIO_CFG_OUT_LOW);
        GPIO_write(Board_SPI_SLAVE_READY, 0);
    
        Display_printf(display, 0, 0, "\nDone");
    
        return (NULL);
    }
    
    /*
     *  ======== mainThread ========
     */
    void *mainThread(void *arg0)
    {
        pthread_t           thread0;
        pthread_attr_t      attrs;
        struct sched_param  priParam;
        int                 retc;
        int                 detachState;
    
        /* Call driver init functions. */
        Display_init();
        GPIO_init();
        SPI_init();
    
        /* Configure the LED pins */
        GPIO_setConfig(Board_GPIO_LED0, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW);
        GPIO_setConfig(Board_GPIO_LED1, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW);
    
        /* Open the display for output */
        display = Display_open(Display_Type_UART, NULL);
        if (display == NULL) {
            /* Failed to open display driver */
            while (1);
        }
    
        /* Turn on user LED */
        GPIO_write(Board_GPIO_LED0, Board_GPIO_LED_ON);
    
        Display_printf(display, 0, 0, "Starting the SPI slave example");
        Display_printf(display, 0, 0, "This example requires external wires to be "
            "connected to the header pins. Please see the Board.html for details.\n");
    
        /* Create application thread */
        pthread_attr_init(&attrs);
    
        detachState = PTHREAD_CREATE_DETACHED;
        /* Set priority and stack size attributes */
        retc = pthread_attr_setdetachstate(&attrs, detachState);
        if (retc != 0) {
            /* pthread_attr_setdetachstate() failed */
            while (1);
        }
    
        retc |= pthread_attr_setstacksize(&attrs, THREADSTACKSIZE);
        if (retc != 0) {
            /* pthread_attr_setstacksize() failed */
            while (1);
        }
    
        /* Create slave thread */
        priParam.sched_priority = 1;
        pthread_attr_setschedparam(&attrs, &priParam);
    
        retc = pthread_create(&thread0, &attrs, slaveThread, NULL);
        if (retc != 0) {
            /* pthread_create() failed */
            while (1);
        }
    
        return (NULL);
    }
    

  • Hi Clement.

    Thanks for the reply and the link. This should be good for me to experiment with. If anything else pops up, I will open a new thread. For now, I appreciate all your help. 

    Raghu