Using SERCOM SPI module

Go To Last Post
11 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello all,

 

I am exploring the use of the SPI SERCOM module with a ATSAMD20J18 MCU. I started with the quick start project in ASF : SPI quick start (polled)

 

I modified the main code to test the SPI with a small flash memory IC, a AT25SF081 . I also modified the baudrate to 1 MHz up from 100 kHz .

 

I am able to read and write to the memory, but I feel there is a lot of unnecessary downtime that could be avoided, I am uncertain if it is indeed avoidable and it is me that is not using the SPI functions properly, or if this is mandatory to the protocol.

 

Here is a screenshot of some data being sent to the memory :

 

 

You can see that there is a 30 us downtime between each byte that is written on the bus. I noticed that this delay is present also at 100 kHz, so it seems consistent.

 

To write to the memory :

1. Erase memory addresses that will be written, if necessary

2. Assert CS, write Write Enable byte (0x06 , deassert CS

3. Assert CS, write OPCODE for write (0x02), write 3 address bytes, write data to be written in memory, deassert CS

 

To read memory:

 

1. Assert CS, write Read Byte OPCODE (0x0B) ,  write 3 address bytes, write 1 dummy byte (0xFF), read as many memory locations as wanted (incremental addressing) , deassert CS

 

That being said, would there be a better way of doing than I am doing it in the code below, using other functions than those ones? Any help is appreciated !

 

Here is the code :

(PIN_PA13 is a LED, verifying if what was written is what was read from memory at a certain address)

#include <asf.h>
#include <delay.h>
//! [setup]
//! [buf_length]
#define BUF_LENGTH 20
//! [buf_length]
//! [slave_select_pin]
#define SLAVE_SELECT_PIN CONF_MASTER_SS_PIN
//! [slave_select_pin]
//! [buffer]
static uint8_t buffer[BUF_LENGTH] = {
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
        0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13
};
//! [buffer]

//! [dev_inst]
struct spi_module spi_master_instance;
//! [dev_inst]
//! [slave_dev_inst]
struct spi_slave_inst slave;
//! [slave_dev_inst]
//! [setup]

void configure_spi_master(void);

//! [configure_spi]
static void config_led(void)
{
    struct port_config pin_conf;
    port_get_config_defaults(&pin_conf);

    pin_conf.direction  = PORT_PIN_DIR_OUTPUT;
    port_pin_set_config(PIN_PA13, &pin_conf);
    port_pin_set_output_level(PIN_PA13, true);
}

void configure_spi_master(void)
{
//! [config]
    struct spi_config config_spi_master;
//! [config]
//! [slave_config]
    struct spi_slave_inst_config slave_dev_config;
//! [slave_config]
    /* Configure and initialize software device instance of peripheral slave */
//! [slave_conf_defaults]
    spi_slave_inst_get_config_defaults(&slave_dev_config);
//! [slave_conf_defaults]
//! [ss_pin]
    slave_dev_config.ss_pin = SLAVE_SELECT_PIN;
//! [ss_pin]
//! [slave_init]
    spi_attach_slave(&slave, &slave_dev_config);
//! [slave_init]
    /* Configure, initialize and enable SERCOM SPI module */
//! [conf_defaults]
    spi_get_config_defaults(&config_spi_master);
//! [conf_defaults]
//! [mux_setting]
    config_spi_master.mux_setting = CONF_MASTER_MUX_SETTING;
    config_spi_master.mode_specific.master.baudrate = 1000000;
    
//! [mux_setting]

    config_spi_master.pinmux_pad0 = CONF_MASTER_PINMUX_PAD0;
    config_spi_master.pinmux_pad1 = CONF_MASTER_PINMUX_PAD1;
    config_spi_master.pinmux_pad2 = CONF_MASTER_PINMUX_PAD2;
    config_spi_master.pinmux_pad3 = CONF_MASTER_PINMUX_PAD3;

//! [init]
    spi_init(&spi_master_instance, CONF_MASTER_SPI_MODULE, &config_spi_master);
//! [init]

//! [enable]
    spi_enable(&spi_master_instance);
//! [enable]

}
//! [configure_spi]

int main(void)
{
    uint8_t x[8]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
    uint8_t header[1] = {0x06};
    uint8_t erase[1] = {0x60};
    uint8_t data[5] = {0x02,0x00,0x06,0x00,0xEA};
    uint8_t datar[5] = {0x0B,0x00,0x06,0x00,0xFF};
//! [main_setup]
//! [system_init]
    system_init();
    config_led();
//! [system_init]
//! [run_config]
    configure_spi_master();
//! [run_config]
//! [main_setup]

//! [main_use_case]
//! [inf_loop]
port_pin_set_output_level(PIN_PA13,true);

    while (true) {
    
  
        if (!port_pin_get_input_level(BUTTON_0_PIN)) {
            //! [select_slave]
            
        
           spi_select_slave(&spi_master_instance, &slave, true);
            spi_write_buffer_wait(&spi_master_instance,header,1);
            spi_select_slave(&spi_master_instance, &slave, false);
            
            spi_select_slave(&spi_master_instance, &slave, true);
            spi_write_buffer_wait(&spi_master_instance,data,5);
            spi_select_slave(&spi_master_instance, &slave, false);

            delay_ms(1);
     
            spi_select_slave(&spi_master_instance, &slave, true);
            spi_write_buffer_wait(&spi_master_instance,datar,5);
            spi_read_buffer_wait(&spi_master_instance,x,1,0x00);
             spi_select_slave(&spi_master_instance, &slave, false);
            
            if (x[0] == 0xEA)
            {
                while(1)
                {
                    port_pin_toggle_output_level(PIN_PA13);
                    delay_ms(500);
                }
                
            }
            else
            {
                
                while(1)
                {
                    port_pin_toggle_output_level(PIN_PA13);
                    delay_ms(100);
                }
            }
            
            
            
            
            
    }
//! [inf_loop]
//! [main_use_case]
}
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

samuelproulx wrote:
I started with the quick start project in ASF : SPI quick start (polled) ... I feel there is a lot of unnecessary downtime that could be avoided

 

Yes, that was my experience with the ASF SPI on SAMD21/21.

 

There is certainly scope for optimisation here ...

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Is it recommended I don't use ASF for SPI use then? Or is it possible to optimize within ASF by modifying or using different functions?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Use ASF as a starting point: as ever, first get things working then tweak performance - if necessary.

 

In this particular case, I found that the ASF performance was not sufficient for the specific requirement - so I optimised.

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I see, would you have any optimisation suggestions you could make ? =) If you have found any changes that makes it anymore efficient, I would appreciate to know them if you wish to share !

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If anyone is interested, I have done some findings about the weird delay that happens between each character.

 

The function spi_write_buffer_wait() has many safety measures, logical checks to make sure the SPI module is ready to send data, what kind of data to send (8 or 9 bit), etc etc. All these checks add a delay , which sums up to approximately 30 us. I have done some tests and by removing all of those and only keeping the bare essentials, I get a delay of approximately 4 us between each character, which is a lot better.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The problem here is definitely the ASF, no doubt. I would suggest that you use the ASF to configure the SPI peripheral and write your own functions for read and write.

 

ASF sometimes does not play well with optimization, so if you are planning to use using -O2 or -O3 optimization levels, write your own functions.

 

It is not hard at all, you just need to check for the Data Register Empty bit in the Status register.

 

Since the PORT peripheral is connected to the shared peripheral bridge do not use that for time critical applications. Use the ARM IOBUS instead, this will help keep the IO latency down.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Adithya Yuri wrote:
use the ASF to configure the SPI peripheral and write your own functions for read and write.

Agreed - see #4

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Trouble with ASF on SAM D20 / R21 SPI_QUICK_START_ master read: spi_read_buffer_wait while slave write: spi_write_buffer_wait

1) master writes and slave reads multiple bytes works fine, using original examples:

Trying the SPI_QUICK_START_SLAVE1 demo/example on one SAMD20, which  just read 20 bytes from master;

while running SPI_QUICK_START_MASTER1 demo/example on one SAMR21, which writes 20 bytes to the slave. 

The reader/receiver compare what received to what expected (sent by writer) to test whether communication is successful or not. The test passed with buffer lenght 20.

 

2) Since there is no (example) SPI_QUICK_START in the Atmel Studio 7 I'm using for the devices (SAMD20 and SAMR21) where the role reversed: master reads and slave writes,  I just modified the example above.

in the slave code, replace the line:

    while((sc =spi_read_buffer_wait(&spi_slave_instance, buffer_rx, BUF_LENGTH,
            0x00)) != STATUS_OK) {
         //Wait for transfer from the master //
    }

with write:

while(spi_write_buffer_wait(
    &spi_slave_instance,
    slave_txd_dataA,
    LENGTH) != STATUS_OK) {}

and define the LENGTH, slave_txd_dataA before that:

//multi write
    #define LENGTH 2  //or 20
    static uint8_t slave_txd_dataA[LENGTH] = {
        0x00, 0x01 //or add more for longer buffer, such as 0x02, 0x03,...
    };

 

 

and in the master code, replace 

    spi_write_buffer_wait(&spi_master_instance, buffer, BUF_LENGTH);

with reads:

spi_read_buffer_wait(
        &spi_master_instance,
        master_rxd_dataA,
        LENGTH,
        0xFF);

and define the buffer and expected data just as in the slave reading case:

#define LENGTH 2  //or 20 for longer buffer
        static uint8_t slave_txd_dataA[LENGTH] = {
            0x00, 0x01 //or more added for longer buffer such as 0x02, 0x03...
        };    
        volatile uint8_t master_rxd_dataA[LENGTH] = {0};

 

Test shown the communication failed when the buffer lenght is 20.

But, when the buffer length was changed to 1 (single byte), it pass the test;

change buffer length to 2, test run also pass; any longer buffer (>=3) will fail the test. really puzzled what is going on? Has anyone experienced this kind of problem? or has any suggestion? Very much appreciated your help!

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It's a problem with the many failed (because of timeout) spi_write_buffer_wait() calls that pre-load 2 bytes. The SERCOM SPI is not in the proper state when it finally is selected (I guess, it responds with the first two bytes twice). This works for me in the slave however (tested with D21 master and C21 slave but that should not make a difference):
 

   while (spi_write_buffer_wait(&spi_slave_instance, slave_txd_dataA, LENGTH) != STATUS_OK) {
       spi_disable(&spi_slave_instance);
       configure_spi_slave();
   }

It's perfectly ok to make a new thread btw.
Edit: disable, enable looks ok also:
 

   while (spi_write_buffer_wait(&spi_slave_instance, slave_txd_dataA, LENGTH) != STATUS_OK) {
       spi_disable(&spi_slave_instance);
       spi_enable(&spi_slave_instance);
   }
   

/Lars

Last Edited: Sat. Jan 7, 2017 - 03:55 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You could also try using the DMAC.  There are examples in the ASF documentation for the SERCOM functions, it looks daunting at first put it's pretty easy to set up once you go through the examples.