NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » USART as SPI

March 04, 2013
by esoderberg
esoderberg's Avatar

Code posted below sets up the Nerdkit to communicate via the USART in SPI master mode. I wanted this capability so that I could have the Nerdkit acting as SPI slave on one net and SPI master on another. With only one native SPI on the chip, this seemed a good option. The datasheet gives a pretty good description of USART as SPI master, but sometimes working sample code is nice too, so here it is. This code is very similar to the native SPI code I posted in the library, but of course using the USART as SPI instead.

    #include "io_328p.h"
    #include <avr/io.h>
    #include <avr/pgmspace.h>
    #include <inttypes.h>
    #include <stdio.h>
    #include "spi.h"
    #include "delay.h"

    #define SPI_GYRO1 PB2//PD6 in vc setup
    #define SPI_GYRO2 PB1
    #define SPI_DISPLAY PD5

    #define SPI_BUFFER_SIZE 8

    volatile uint8_t spi_data_in[SPI_BUFFER_SIZE];
    // Functions
    //setup as SPI master
        void master_init(){

        //set MOSI,SCK,SS as output
        DDRD |= (1<<PD1);//TX---MOSI, 
        DDRD &= ~(1<<PD0);//RX---MISO
        DDRB |= (1<<SPI_GYRO1);
        DDRC |= (1<<PC0) | (1<<PC1) |(1<<PC2) |(1<<PC3);

        //keep spi slave inactive
        PORTB |= (1<<SPI_GYRO1);//SLV CS

        UBRR0H = 0;
        UBRR0L = 0;

        DDRD |= (1<<PD4);//SCK--/* Setting the XCKn port pin as output, enables master mode. */
      /* Set MSPI mode of operation and SPI data mode 0. */
      UCSR0C = (1<<UMSEL01)|(1<<UMSEL00)|(0<<UCPHA0)|(0<<UCPOL0);
      /* Enable receiver and transmitter. */
      UCSR0B = (1<<RXEN0)|(1<<TXEN0);
      /* Set baud rate. */
      /* IMPORTANT: The Baud Rate must be set after the transmitter is enabled
      */
      UBRR0H = 0;
      UBRR0L = 15;

        }

    //************************************************************************************************//
    void spi_read(uint8_t slave_select,uint8_t address_read, uint8_t read_bytes){// slave_select, address to read from, number of bytes to read
    uint8_t i, read;    
        switch (slave_select){

            case 1:  PORTB &= ~(1<<SPI_GYRO1);//pull CS line low for GYRO1
            break;
            case 2:  PORTB &= ~(1<<SPI_GYRO2);//pull CS line low for GYRO2
            break;
            case 3:  PORTD &= ~(1<<SPI_DISPLAY);//pull slave CS line low for DISPLAY
            break;
            default:
            break;}

        delay_us(10);

        address_read=(address_read | 0x80);//sets MSB in byte to tell slave this is a read request

        // Wait for transmission buffer open
        while( !( UCSR0A & (1<<UDRE0)) );

        //send data to begin transmit/receive and send address of byte to read
        UDR0 = address_read;

        // Wait for transmission complete
        while( !( UCSR0A & (1<<RXC0)) );
        read=UDR0;//read once each transmit to keep buffer synced

        for(i=1; i<=read_bytes; i++){delay_us(10);//allow slave to set outgoing data
        //Send consecutive  transmissions for burst read starting at previously selected register value in slave
        // Wait for transmission buffer open
        while( !( UCSR0A & (1<<UDRE0)) );
        UDR0 = address_read+i;//auto increment address to read from
        // Wait for receive complete
        while( !( UCSR0A & (1<<RXC0)) );
        spi_data_in[i-1]= UDR0; }

        switch (slave_select){

        case 1:  PORTB |= (1<<SPI_GYRO1);
        break;
        case 2:  PORTB |= (1<<SPI_GYRO2);
        break;
        case 3:  PORTD |= (1<<SPI_DISPLAY);
        default:
        break;}
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////////
    void spi_write(uint8_t slave_select,uint8_t address_write, uint8_t write_byte){//address to write to and data to write

            switch (slave_select){

        //case 0:  PORTB &= ~(1<<SPI_SLV);//pull CS line low for SLV
        //break;
        case 1:  PORTB &= ~(1<<SPI_GYRO1);//pull slave CS line low for GYRO1
        break;
        case 2:  PORTB &= ~(1<<SPI_GYRO2);//pull CS line low for GYRO2
        break;
        case 3:  PORTD &= ~(1<<SPI_DISPLAY);//pull slave CS line low for DISPLAY
        break;
        default:
        break;}

        // Wait for transmission buffer open
        while( !( UCSR0A & (1<<UDRE0)) );

        //send data to begin transmit/receive
        UDR0 = address_write;//send address to write to

        // Wait for transmission complete
        while( !( UCSR0A & (1<<RXC0)) );
        address_write=UDR0;

        // Wait for transmission buffer open
        while( !( UCSR0A & (1<<UDRE0)) );
        UDR0 = write_byte;//send byte to write

        // Wait for transmission complete
        while( !( UCSR0A & (1<<RXC0)) );
        address_write=UDR0;

        switch (slave_select){

        //case 0:  PORTB |= (1<<SPI_SLV);
        //break;
        case 1:  PORTB |= (1<<SPI_GYRO1);
        break;
        case 2:  PORTB |= (1<<SPI_GYRO2);
        break;
        case 3:  PORTD |= (1<<SPI_DISPLAY);
        default:
        break;}
    }
March 05, 2013
by pcbolt
pcbolt's Avatar

Thanks Eric...glad you posted the code.

I was curious, did you try switching between Master and Slave mode first? I don't know the exact nature of the project but it seems this would be possible (at least in theory) since you can use any pin to activate the SS pin on the remote slave. Would be pretty tricky the more I think about it. I'm guessing you'd want to be in slave mode most of the time until needing to write to the slave by switching to master mode. Timing might be a bear, but interesting nonetheless.

March 05, 2013
by esoderberg
esoderberg's Avatar

pcbolt,

I'm making an Inertial Nav Unit that I want to be able to communicate with externally as an SPI SLV. A 328p is the INU processor (basically calculating a quaternion and a few other data filtering tasks) and it will read raw data from two MPU-6000. I want to do the processor to gyro comms via SPI, but as the native SPI is already being going to be used in SLV mode to output data from the INU, it seems simplest to use the USART SPI to read the gyros. I might be able to switch modes with the native SPI as you suggest, but I want to make the external interface to the INU as simple as possible and not force some kind of timing requirement on the chip reading the INU (kind of the whole point of a dedicated 328 doing the quaternion calcs is that I'd like the update rates to be at least 500HZ so any delay kind of defeats that purpose). My other option was to use my Noter based TWI library to read the gyros, which works great 99.9% of the time, but I was never able to recover as gracefully when the bus did hang as I can with the SPI. Plus the extra speed of the SPI is helpful too in this application.

BTW if you're familiar with the MPU-6000 it can if fact calculate a quaternion internally and is a great sensor, but it doesn't compensate for steady state turns which I need it to; plus its quaternion processing feature is not open source.

Eric

March 05, 2013
by pcbolt
pcbolt's Avatar

Eric -

Very interesting. I use inertial motion sensors quite a bit at work. Of course those are prepackaged systems and cost upwards of $100K, so I only have to deal with the user interface. I've never heard of the MPU-6000 so I did a quick look-up and that is one hell of a chip (not too bad cost-wise either). The sensor system I use is integrated with GPS, so the INU only needs to provide dead reckoning during GPS interruptions. To get accurate distance values from the accelerometers requires a great deal of signal filtering/processing. You most likely have researched this but Kalman filters are used most frequently.

Now that I know the nature of your project, I wholeheartedly agree that the USART SPI is the way to go. You'll still have some timing to worry about, but it is far less complex.

Good luck with the project and I hope you post a video when you get everything up and running.

March 06, 2013
by Ralphxyz
Ralphxyz's Avatar

Eric where are you purchasing the MPU-6000?

I can find the MPU-6050 breakout boards on ebay. But cannot find the MPU-6000.

Ralph

March 06, 2013
by esoderberg
esoderberg's Avatar

Ralph,

The MPU-6000 can be had for $7.50 MPU-6000

I have my own PCB design with a logic level shifter (5v to 3.3v) so haven't really looked for a commercial breakout.

Eric

March 08, 2013
by esoderberg
esoderberg's Avatar

Re-posted code has the same functionality as above but is cleaned up quite a bit with a few Macros so only the SLV port definition need be changed with different hardware setups(another Noter inspired bit of code from preprocessor thread).

#include "io_328p.h"
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include <stdio.h>
#include "spi.h"
#include "delay.h"

//slave select PORT,PIN --- Adjust as required per hardware setup  --- 
#define SLV1    B,2//SLV1 on PORTB pin 2
#define SLV2    B,1//SLV2 on PORTB pin 1
#define SLV3    D,5//SLV3 on PORTD pin 5

#define SLV_SET2(port,pin) DDR ## port |= _BV(pin)//sets CS pins as output
#define SLV_SELECT2(port,pin) PORT ## port &= ~_BV(pin)//pulls CS pin low
#define SLV_RELEASE2(port,pin) PORT ## port |= _BV(pin) //sets CS pin high

#define SLV_SET(x) SLV_SET2(x)
#define SLV_SELECT(x) SLV_SELECT2(x)
#define SLV_RELEASE(x) SLV_RELEASE2(x)

#define SPI_BUFFER_SIZE 8

volatile uint8_t spi_data_in[SPI_BUFFER_SIZE];
// Functions
//setup as SPI master
    void master_init(){

//set SS as output via Macro
    SLV_SET(SLV1);
    SLV_SET(SLV2);
    SLV_SET(SLV3);

//Init SPI SLVs to inactive state via Macro setting SS pin high
    SLV_RELEASE(SLV1);
    SLV_RELEASE(SLV2);
    SLV_RELEASE(SLV3);

//Baud must be set to zero to init USART SPI master
    UBRR0H = 0;
    UBRR0L = 0;

//SCK--/* Setting the XCKn port pin as output, enables master mode. */  
    DDRD |= (1<<PD4);

/* Set MSPI mode of operation and SPI data mode 0. */
    UCSR0C = (1<<UMSEL01)|(1<<UMSEL00)|(0<<UCPHA0)|(0<<UCPOL0);

/* Enable receiver and transmitter. */
    UCSR0B = (1<<RXEN0)|(1<<TXEN0);

//IMPORTANT: The Baud Rate must be set after the transmitter is enabled
    UBRR0H = 0;
    UBRR0L = 15;

    }

//************************************************************************************************//
void spi_read(uint8_t slave_select,uint8_t address_read, uint8_t read_bytes){// slave_select, address to read from, number of bytes to read
uint8_t i, read;    
switch (slave_select){
        case 1:  SLV_SELECT(SLV1);//pull CS line low for GYRO1
        break;
        case 2:  SLV_SELECT(SLV2);;//pull CS line low for GYRO2
        break;
        case 3:  SLV_SELECT(SLV3);;//pull slave CS line low for DISPLAY
        break;
        default:
        break;}

    address_read=(address_read | 0x80);//sets MSB in byte to tell slave this is a read request

    // Wait for transmission buffer open
    while( !( UCSR0A & (1<<UDRE0)) );

    //send data to begin transmit/receive
    UDR0 = address_read;

    // Wait for transmission complete
    while( !( UCSR0A & (1<<RXC0)) );
    read=UDR0;

    for(i=1; i<=read_bytes; i++){delay_us(10);//allow slave to set outgoing data
    //Send consecutive  transmissions for burst read starting at previously selected register value in slave
    // Wait for transmission buffer open
    while( !( UCSR0A & (1<<UDRE0)) );
    UDR0 = address_read+i;
    // Wait for receive complete
    while( !( UCSR0A & (1<<RXC0)) );
    spi_data_in[i-1]= UDR0; }

    switch (slave_select){

    case 1:     SLV_RELEASE(SLV1);
    break;
    case 2:     SLV_RELEASE(SLV2);
    break;
    case 3:     SLV_RELEASE(SLV3);
    default:
    break;}
}

/////////////////////////////////////////////////////////////////////////////////////////////////////
void spi_write(uint8_t slave_select,uint8_t address_write, uint8_t write_byte){//address to write to and data to write

    switch (slave_select){
        case 1:  SLV_SELECT(SLV1);//PORTB &= ~(1<<SPI_GYRO1);//pull slave CS line low for GYRO1
        break;
        case 2:  SLV_SELECT(SLV2);//PORTB &= ~(1<<SPI_GYRO2);//pull CS line low for GYRO2
        break;
        case 3:  SLV_SELECT(SLV3);//PORTD &= ~(1<<SPI_DISPLAY);//pull slave CS line low for DISPLAY
        break;
        default:
        break;}

    // Wait for transmission buffer open
    while( !( UCSR0A & (1<<UDRE0)) );

    //send data to begin transmit/receive
    UDR0 = address_write;//send address to write to

    // Wait for transmission complete
    while( !( UCSR0A & (1<<RXC0)) );
    address_write=UDR0;

    // Wait for transmission buffer open
    while( !( UCSR0A & (1<<UDRE0)) );
    UDR0 = write_byte;//send byte to write

    // Wait for transmission complete
    while( !( UCSR0A & (1<<RXC0)) );
    address_write=UDR0;

    switch (slave_select){
        case 1:  SLV_RELEASE(SLV1);//PORTB |= (1<<SPI_GYRO1);
        break;
        case 2:  SLV_RELEASE(SLV1);//PORTB |= (1<<SPI_GYRO2);
        break;
        case 3:  SLV_RELEASE(SLV1);//PORTD |= (1<<SPI_DISPLAY);
        default:
        break;}
}

Post a Reply

Please log in to post a reply.

Did you know that the microcontroller's crystal oscillator can be used to keep accurate time? Learn more...