[Contribution] SPI "Async" data transfers

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
lock attach
Attachments are accessible only for community members.
cadi_1014291
Level 6
Level 6
25 likes received 10 likes received 10 likes given

Greetings,

I was getting familiar with the SPI Master interrupts, so i started a project to send data via SPI taking advantage of the SPI TX FIFO and interrupts, so that way i don't stall the CPU waiting for the transfer to be completed, in the example the cpu triggers the SPI transfer and leave the interrupts to handle it, meanwhile the CPU toogles a digital output (silly but illustrate the point ;P), this can be useful when using PSoCs with no DMA.

To send just 3 bytes (less than the FIFO depth) using the spi_xfer_async function:

int main(void)

{

    const uint8_t data_spi[10] = {0x01, 0x02, 0x03, 0x04, 0x05,

                              0x06, 0x07, 0x08, 0x09, 0x0A};

   

    isr_SPI_Tx_StartEx(SPI_tx_handler);

   

    // Enable interrupts

    CyGlobalIntEnable;

   

    // Configure and enable the peripherals

    SPI_Start();

    UART_Start();

   

    UART_PutChar(0x0C); // clear the screen

    UART_PutString("Test SPI with interrupts.\r\n");

    while (1) {

        if (TX_IDLE == tx_state) {

            spi_xfer_asynch(data_spi, 3);

        }

        CyDelayUs(10);

        LED_Write(~LED_Read());

    }

}

The data transfer captured with a LA (you can see the LED toogling on the top trace):

async_spi_3.png

To send just 10 bytes (more than the FIFO depth):

int main(void)

{

    const uint8_t data_spi[10] = {0x01, 0x02, 0x03, 0x04, 0x05,

                              0x06, 0x07, 0x08, 0x09, 0x0A};

   

    isr_SPI_Tx_StartEx(SPI_tx_handler);

   

    // Enable interrupts

    CyGlobalIntEnable;

   

    // Configure and enable the peripherals

    SPI_Start();

    UART_Start();

   

    UART_PutChar(0x0C); // clear the screen

    UART_PutString("Test SPI with interrupts.\r\n");

    while (1) {

        if (TX_IDLE == tx_state) {

            spi_xfer_asynch(data_spi, 10);

        }

        CyDelayUs(10);

        LED_Write(~LED_Read());

    }

}

The data transfer captured with a LA (you can see the LED toogling on the top trace):

async_spi_10.png

Hope is useful to you (find the project attached, it use SPI based on UDB, but the principle apply to SCB as well), if you see something wrong let me know.

Regards,

Carlos

0 Likes
1 Solution
lock attach
Attachments are accessible only for community members.
cadi_1014291
Level 6
Level 6
25 likes received 10 likes received 10 likes given

I worked a bit more and updated the buffer_t struct, now it just have a pointer to a uint8_t instead of the array of uint8_t, so now we must have an array of uint8_t with global scope to work with it, this avoid using memcpy (on the spi_xfer_async function) from the previous implementation.

This is the new main.c file

#include "project.h"

enum {

    MAX_ITEMS = 10,

};

typedef enum  {

    TX_IDLE,

    TX_TRANSFERING,

} tx_state_tag;

volatile tx_state_tag tx_state = TX_IDLE;

typedef struct {

    uint8_t *const buffer;

    uint8_t cnt;

    uint8_t left_to_xfer;

} buffer_t;

uint8_t data_spi[MAX_ITEMS] = {0x01, 0x02, 0x03, 0x04, 0x05,

                              0x06, 0x07, 0x08, 0x09, 0x0A};

buffer_t tx_buffer = {

    .buffer = data_spi,

    .cnt = 0,

    .left_to_xfer = 0,

};

void spi_xfer_async(uint8_t *data, const size_t size);

void SPI_handler(void);

int main(void)

{

    isr_SPI_StartEx(SPI_handler);

   

    CyGlobalIntEnable;

   

    SPI_Start();

    UART_Start();

   

    UART_PutChar(0x0C); // clear the screen

    UART_PutString("Test SPI with interrupts.\r\n");

    while (1) {

       

        if (TX_IDLE == tx_state) {

            spi_xfer_async(tx_buffer.buffer, 3);

        }

       

        CyDelayUs(50);

        LED_Write(~LED_Read());

       

        if (TX_IDLE == tx_state) {

            spi_xfer_async(tx_buffer.buffer, 10);

        }

       

        CyDelayUs(100);

        LED_Write(~LED_Read());

    }

}

The spi_xfer_async function:

void spi_xfer_async(uint8_t *data, const size_t size)

{

    if (NULL != data) {

        CyGlobalIntDisable;

        if (TX_IDLE == tx_state) {

            tx_state = TX_TRANSFERING;

           

            if (size <= SPI_FIFO_SIZE) {

                // fill the spi tx fifo

                for (uint8_t i = 0; i < size; i++) {

                    SPI_WriteTxData(data);

                }

            } else {

                tx_buffer.cnt = 2;

                tx_buffer.left_to_xfer = size;

                // put at least 2 bytes into the spi tx fifo

                for (uint8_t i = 0; i < 2; i++) {

                    SPI_WriteTxData(data);

                }

            }

           

        }

        CyGlobalIntEnable;

    }

}

The interrupt handler:

void SPI_handler(void)

{

    // clear interrupt flag

    volatile uint8_t sts = SPI_ReadTxStatus();

   

    // TODO: Get the data from MISO

    (void)SPI_ReadRxStatus();

    if (TX_IDLE != tx_state) {

        if (SPI_STS_BYTE_COMPLETE & sts) {

            // do we still have data to send?

            if (tx_buffer.left_to_xfer > tx_buffer.cnt) {

                SPI_WriteTxData(tx_buffer.buffer[tx_buffer.cnt]);

                tx_buffer.cnt++;

            }

        }

        // the interrupt was triggered by the SPI_DONE flag

        if (SPI_STS_SPI_IDLE & sts) {

            tx_state = TX_IDLE;

        }

    }

}

We should be careful when updating the global data_spi array doing it when a transfer is not in process.

The project is attached (PSoC Creator 4.1).

Regards,

Carlos

View solution in original post

0 Likes
5 Replies
cadi_1014291
Level 6
Level 6
25 likes received 10 likes received 10 likes given

I tend to 'reinvent the wheel', i know there's a way to let the component internal implementation to handle transfers bigger than the SPI FIFO (bigger than 4bytes), but that implementation use software and this one try to take advantage of the FIFO hw

0 Likes

Yes, you certainly did reinvent the wheel since the existing implementation already uses the FIFO and interrupts. (Actually you cannot send anything over SPI without using the TX FIFO

But your function handles the CS signal in a better way and is easier to use.

0 Likes

Hi, thanks for taking a look,

I didn't check all their implementation, i just saw a big amount of code handling the software buffer and iirc their implementation waits until all the data is sent, while mine just triggers the transfers and leaves, it may be useful when "drawing" something on a display, trigger the transfer and leave, without having to wait to draw the full display.

This project doesn't handle (yet) receiving data, may be i can work on that the next weekend.

The pictures saw small on here but i controlled two /SS lines, one with software (the blue channel) and one with the /SS signal of the SPI component meaning controlled with hardware (orange channel), it seems like they got almost the same performance, the hardware controlled /SS gets asserted faster tho, i remember having a lot of problems when using the SPI and the /SS controlled by hardware using their implementation (or i wasn't using it correctly).

Hope you and others can play around with the project 

It was a fun pet project to work on , i hope to use it on another projects.

0 Likes
cadi_1014291
Level 6
Level 6
25 likes received 10 likes received 10 likes given

The project is based on the spi_xfer_async function and the interrupt triggered by the SPI Done and Byte Transaction Complete flags of the SPI Master. The SPI Master component is configured to have 4byte depth TX FIFO, this way we avoid using the software buffer.

The spi_xfer_async function checks the size of the data to be transferred and fills the SPI TX FIFO accordingly, if the SPI TX FIFO is not filled (for example sending just one byte) we got gaps of 4us (with the CPU @ 24MHz) between the each byte transferred, we don't want that, we like to run as fast as possible so if we fill the SPI TX FIFO with more than 1 byte we have no gaps as shown on the pictures of the first post.

void spi_xfer_asynch(const uint8_t *data, const size_t size)

{

    CyGlobalIntDisable;

    if (TX_IDLE == tx_state) {

        tx_state = TX_TRANSFERING;

        if (size <= SPI_FIFO_SIZE) {

            SS_Write(0);           

            // fill the spi tx fifo

            for (uint8_t i = 0; i < size; i++) {

                SPI_WriteTxData(data);

            }

        } else {

            tx_buffer.cnt = 0;

            tx_buffer.left_to_xfer = size - SPI_FIFO_SIZE;

            memcpy(tx_buffer.buffer, &data[SPI_FIFO_SIZE], tx_buffer.left_to_xfer);

            SS_Write(0);

           

            // fill the spi tx fifo

            for (uint8_t i = 0; i < SPI_FIFO_SIZE; i++) {

                SPI_WriteTxData(data);

            }

        }

    }

    CyGlobalIntEnable;

}

TODO: Test filling the SPI TX FIFO with just two bytes instead of 4 when the size parameter is bigger than 4.

TODO2: Remove the software controlled /SS line and work with the hardware controlled one, the hw controlled one set the /SS signal low and high a bit faster (to be measured).

The interrupt handler is as follows, it clears the SPI interrupt flag and checks if the interrupt was triggered by the SPI Byte Transfer Complete or SPI Done interrupt sources.

void SPI_tx_handler(void)

{

    // clear interrupt flag by reading the SPI_TX_STS register

    volatile uint8_t sts = SPI_ReadTxStatus();

   

    // TODO: Get the data from MISO

    (void)SPI_ReadRxStatus();

   

    if (TX_IDLE != tx_state) {

        if (SPI_STS_BYTE_COMPLETE & sts) {

            if (TX_IDLE != tx_state) {

                // do we still have data to send?

                if (tx_buffer.left_to_xfer > tx_buffer.cnt) {

                    SPI_WriteTxData(tx_buffer.buffer[tx_buffer.cnt]);

                    tx_buffer.cnt++;

                }

            } else {

                // we shouldn't get in here

            }

        }

       

        // the interrupt was triggered by the SPI_DONE flag

        // the SPI_IDLE flag is set to 0 when data is detected on the TX FIFO

        // SPI_IDLE set to 1 is the default state of the SPI state machine,

        if (SPI_STS_SPI_IDLE & sts) {

            SS_Write(1);

            tx_state = TX_IDLE;

        }

    }

}

TODO: Get the data received if necessary.

The tx_buffer is a buffer_t  type variable (typedef struct) with three members:

typedef struct {

    uint8_t buffer[MAX_ITEMS];

    uint8_t cnt;

    uint8_t left_to_xfer;

} buffer_t;

where the buffer member is a buffer to hold a copy of the data to be transferred with the interrupt, the cnt member is the amount of data to be transferred and the left_to_transfer is just a count of byte to be transferred after the data transferred with the spi_xfer_async function. the MAX_ITEMS constant is defined previously.

Here i see two ways to improve it:

- Having a global user data buffer and the buffer member will be just a pointer to it.

- Using the buffer member as our user data buffer, so we avoid having another buffers.

But there are a lot of ways to improve the project, if you have some ideas to do so please let us know

Regards,

Carlos

0 Likes
lock attach
Attachments are accessible only for community members.
cadi_1014291
Level 6
Level 6
25 likes received 10 likes received 10 likes given

I worked a bit more and updated the buffer_t struct, now it just have a pointer to a uint8_t instead of the array of uint8_t, so now we must have an array of uint8_t with global scope to work with it, this avoid using memcpy (on the spi_xfer_async function) from the previous implementation.

This is the new main.c file

#include "project.h"

enum {

    MAX_ITEMS = 10,

};

typedef enum  {

    TX_IDLE,

    TX_TRANSFERING,

} tx_state_tag;

volatile tx_state_tag tx_state = TX_IDLE;

typedef struct {

    uint8_t *const buffer;

    uint8_t cnt;

    uint8_t left_to_xfer;

} buffer_t;

uint8_t data_spi[MAX_ITEMS] = {0x01, 0x02, 0x03, 0x04, 0x05,

                              0x06, 0x07, 0x08, 0x09, 0x0A};

buffer_t tx_buffer = {

    .buffer = data_spi,

    .cnt = 0,

    .left_to_xfer = 0,

};

void spi_xfer_async(uint8_t *data, const size_t size);

void SPI_handler(void);

int main(void)

{

    isr_SPI_StartEx(SPI_handler);

   

    CyGlobalIntEnable;

   

    SPI_Start();

    UART_Start();

   

    UART_PutChar(0x0C); // clear the screen

    UART_PutString("Test SPI with interrupts.\r\n");

    while (1) {

       

        if (TX_IDLE == tx_state) {

            spi_xfer_async(tx_buffer.buffer, 3);

        }

       

        CyDelayUs(50);

        LED_Write(~LED_Read());

       

        if (TX_IDLE == tx_state) {

            spi_xfer_async(tx_buffer.buffer, 10);

        }

       

        CyDelayUs(100);

        LED_Write(~LED_Read());

    }

}

The spi_xfer_async function:

void spi_xfer_async(uint8_t *data, const size_t size)

{

    if (NULL != data) {

        CyGlobalIntDisable;

        if (TX_IDLE == tx_state) {

            tx_state = TX_TRANSFERING;

           

            if (size <= SPI_FIFO_SIZE) {

                // fill the spi tx fifo

                for (uint8_t i = 0; i < size; i++) {

                    SPI_WriteTxData(data);

                }

            } else {

                tx_buffer.cnt = 2;

                tx_buffer.left_to_xfer = size;

                // put at least 2 bytes into the spi tx fifo

                for (uint8_t i = 0; i < 2; i++) {

                    SPI_WriteTxData(data);

                }

            }

           

        }

        CyGlobalIntEnable;

    }

}

The interrupt handler:

void SPI_handler(void)

{

    // clear interrupt flag

    volatile uint8_t sts = SPI_ReadTxStatus();

   

    // TODO: Get the data from MISO

    (void)SPI_ReadRxStatus();

    if (TX_IDLE != tx_state) {

        if (SPI_STS_BYTE_COMPLETE & sts) {

            // do we still have data to send?

            if (tx_buffer.left_to_xfer > tx_buffer.cnt) {

                SPI_WriteTxData(tx_buffer.buffer[tx_buffer.cnt]);

                tx_buffer.cnt++;

            }

        }

        // the interrupt was triggered by the SPI_DONE flag

        if (SPI_STS_SPI_IDLE & sts) {

            tx_state = TX_IDLE;

        }

    }

}

We should be careful when updating the global data_spi array doing it when a transfer is not in process.

The project is attached (PSoC Creator 4.1).

Regards,

Carlos

0 Likes