SAME70: SPI UART to GPIO expander

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

Hello!

 

As a complete beginner with pin related stuff, I started practicing GPIO/SPI usage with MCP23X17 GPIO expander board. I'll be using the dedicated SPI pins for connections towards Linux and USART pins for GPIO expander.

However, I'm facing difficulties getting anything working from SAME70 board outwards. From using tracepoints, I can see the program goes to the second io_write(...), but hangs a minute without affecting the expander.

 

I haven't modified anything but the main.c from the default Atmel START project generated with the settings written in the code as comment.

#include <atmel_start.h>

/*
Atmel START configs:
 SPI Master Sync:
  Pins[CS, SCK, MISO, MOSI]: PB3, PB13, PB0, PB1
  Speed: 500 kHz
  Character size: 8 bits
  Baud rate: 500 kHz
*/

// Copied from diver_examples.c, just modified the signal to these two arrays
static uint8_t reset_arr[3] = {0b01000000, 0b00001001, 0b00000000};
static uint8_t led_arr[3] = {0b01000000, 0b00001001, 0b11110000};
static const uint16_t sleep_time = 1000;

static struct io_descriptor* SPI_0_expander_example(void)
{
    struct io_descriptor *io;
    spi_m_sync_get_io_descriptor(&SPI_0, &io);

    spi_m_sync_enable(&SPI_0);
    return io;
}

int main(void)
{
    /* Initializes MCU, drivers and middleware */
    atmel_start_init();
    
    struct io_descriptor *io = SPI_0_expander_example();
    
    /* Replace with your application code */
    while (1) {
        io_write(io, reset_arr, 3);
        delay_ms(sleep_time);
        io_write(io, led_arr, 3);
        delay_ms(sleep_time);
    }
}

 

I suspect the problem could be my configs for the Atmel START. It could be that I'm just misunderstanding the whole system for choosing the pins for SPI from USART, which would mean I'm missing something like CS completely or something. Or I might not have realised some settings I need to sync with the expander.

Any advice where I'm going wrong?

 

EDIT: Fixed the first mistake, used sleep() instead of delay_ms() in the program

EDIT2: Updated the problem from the above fix

 

EDIT3: I've certainly missed something... I'm getting some leds lit up and staying lit every now and then when I try to get only the left ones blinking. Am I missing some definitions for the speed or are the SPI modes somehow different? Or could the addressing the expander and it's pins be incorrect?

Last Edited: Wed. Feb 5, 2020 - 02:53 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Don't you need to write IODIRA (register 0) to set the port as output (input is default)?

/Lars

 

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

Oh. It needs to be in output mode? I completely missed that. However, I still am unable to control the leds on the expander board.

Modified the code a bit according to what I understood from the datasheet + Github along with your hint.

#include <atmel_start.h>

/*
Atmel START configs:
 SPI Master Sync:
  Pins[CS, SCK, MISO, MOSI]: PB3, PB13, PB0, PB1
  Speed: 500 kHz
  Character size: 8 bits
  Baud rate: 500 kHz
*/

 

#define IODIRA  (0x00)       // MCP23x17 I/O Direction Register
#define GPIOA   (0x12)       // MCP23x17 GPIO Port Register
#define OPCODEW (0b01000000) // Opcode for MCP23S17 with LSB (bit0) set to write (0), address OR'd in later, bits 1-3

 

static const uint16_t sleep_time = 100;

 

static void write_to_expander(const struct io_descriptor* io, const uint8_t reg, const uint8_t data)

{
    const uint8_t write_arr[3] = {OPCODEW, reg, data};
    io_write(io, write_arr, 3);
}

 

static struct io_descriptor* SPI_0_expander_example_init(void)
{
    struct io_descriptor *io;
    spi_m_sync_get_io_descriptor(&SPI_0, &io);

    spi_m_sync_enable(&SPI_0);

    write_to_expander(io, IODIRA, 0b00000000);
    write_to_expander(io, GPIOA, 0b00000000);

    return io;
}

 

int main(void)
{
    /* Initializes MCU, drivers and middleware */
    atmel_start_init();
    
    struct io_descriptor *io = SPI_0_expander_example_init();
    
    while (1) {
        // The write mode is sequential by default, so no need to address the pins
        io_write(io, 0b11111111, 1);
        delay_ms(10);
        io_write(io, 0b00000000, 1);
        delay_ms(10);
    }
}

This now hangs on the first io_write() in SPI_0_expander_example_init()'s write_to_expander().

 

From debugging, I noticed the program can't exit the loop in _spi_m_sync_trans() in Atmel START generated hpl_usart_spi_m_sync.c.

Does this mean SAME70 is unable to receive anything from the expander? In which case my soldering for the pins just could be bad.

 

EDIT: Checked the soldering and it seemed ok. Not sure why the program hangs in that loop then...

Last Edited: Thu. Feb 6, 2020 - 09:25 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There is no good reason for it looping in _spi_m_sync_transfer, a SPI master is not depending on any signaling from the slave.

You should have warnings from the last program here:

        io_write(io, 0b11111111, 1);
        delay_ms(10);
        io_write(io, 0b00000000, 1);

You need to provide a buffer address as the 2:nd argument (even if that buffer contains a single byte).

 

        // The write mode is sequential by default, so no need to address the pins

That makes no sense, the sequential mode increments the internal address pointer after every write, you want the other mode.

bit 5 SEQOP: Sequential Operation mode bit
1 = Sequential operation disabled, address pointer does not increment.
0 = Sequential operation enabled, address pointer increments

One possible problem is the automatic chip select, I guess it will activate/deactivate for every byte while the MCP23S17 needs it active for the full transaction (i.e., for all 3 bytes in write_to_expander). You may have to control it with a normal IO pin.

/Lars

 

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

Thanks for the reply and pointing out the sequential mode.

I ended up checking the charts in the datasheet more than reading the text, coming to my own incorrect conclusion.

I'm now controlling the CS pin just as a normal GPIO pin and disabling the sequential write.

 

#include <atmel_start.h>

 

/*
Atmel START configs:
 SPI Master Sync:
  Pins[SCK, MISO, MOSI]: PB13, PB0, PB1
   CS pin(PB3) controlled manually as GPIO
  Speed: 500 kHz
  Character size: 8 bits
  Baud rate: 1 MHz
*/

 

#define IOCON   (0x0A)       // MCP23x17 Configuration Register
#define IODIRA  (0x00)       // MCP23x17 I/O Direction Register
#define GPIOA   (0x12)       // MCP23x17 GPIO Port Register
#define OPCODEW (0b01000000) // Opcode for MCP23S17 with LSB (bit0) set to write (0), address OR'd in later, bits 1-3
#define PB3 GPIO(GPIO_PORTB, 3)

 

static void write_to_expander(const struct io_descriptor* io, const uint8_t reg, const uint8_t data){
    const uint8_t write_arr[3] = {OPCODEW, reg, data};
    gpio_set_pin_level(PB3, false);
    io_write(io, write_arr, 3);
    gpio_set_pin_level(PB3, true);
}

 

static struct io_descriptor* SPI_0_expander_example_init(void)
{
    struct io_descriptor *io;
    spi_m_sync_get_io_descriptor(&SPI_0, &io);
    spi_m_sync_enable(&SPI_0);
    gpio_set_pin_direction(PB3, GPIO_DIRECTION_OUT);
    gpio_set_pin_level(PB3, true);
    write_to_expander(io, IOCON, 0b00100000);
    write_to_expander(io, IODIRA, 0b00000000);
    write_to_expander(io, GPIOA, 0b00000000);
    
    return io;
}

 

int main(void)
{
    /* Initializes MCU, drivers and middleware */
    atmel_start_init();
    
    struct io_descriptor *io = SPI_0_expander_example_init();
    
    while (1) {
        write_to_expander(io, GPIOA, 0b11001100);
        delay_ms(10);
        write_to_expander(io, GPIOA, 0b00110011);
        delay_ms(10);
    }
}

 

Some debugging: the program still seems to loop in the _spi_m_sync_transfer in file hpl_usart_spi_m_sync.c

  • The _spi_m_sync_transfer loops as txcnt and rxcnt never increases after first byte sent
  • _spi_tx_check_and_send and _spi_rx_check_and_receive return false constantly from rx/tx ready check

 

So after the first byte, the program never gets ready for the next read/write.

 

Just to make sure, as I'm choosing those pins (PB0, PB1, PB13) in Atmel START, the USART RX pin is always used as MOSI, TX pin as MISO and SCK as... well, SCK, right? The SAMA70 datasheet and the original pin functionalities point to that, but there's no clear mention of it in Atmel START. You can choose other pins too in Atmel START for USART SPI, I just don't know how could I even try to use them.

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

Well this is odd, for me it works as expected (the SPI output, I don't have a  MCP23S17).

 

/Lars

 

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

My configs:

 

... Aaaand it works. I completely missed that "enable clock" box there until now. I just assumed that if I choose it as the clock, it would be enabled. Now I can see with my own oscilloscope that the data is going out, just like in your image too. The expander still doesn't do anything though...

So the next thing is for me figuring out how exactly address and use that expander board. Thank you so far, it has actually been a huge help. I likely would have thought there's something wrong in my code, which makes it loop in the driver functions without your confirmation.