2. ATmega644 SPI Configuration

The mega644 SPI module should be configured as follows:

  • SPI enabled
  • MSB first
  • master mode
  • clock polarity 0 (clock line low when idle)
  • clock phase 0 (sample on leading ege)
  • Maximum frequency: 2.5 MHz (clock/8)

If you have the latest WinAVR installed (version 20070525 at the time this was written), the following C code will set up the SPI module, assuming you have your device set as atmega644. If you are using an older version of WinAVR, you will need to add zeroes to the ends of all of the SPI register/bit names (e.g. change SPCR to SPCR0, SPSR to SPSR0, SPE to SPE0, etc).

#include <avr/io.h>

// global flag used to help us track when an SPI transmission is in progress
unsigned char SPITransmitting;

void SPIInit()
{
    // make the MOSI, SCK, and SS pins outputs
    DDRB |= ( 1 << PB5 ) | ( 1 << PB7 ) | ( 1 << PB4 );

    // make sure the MISO pin is input
    DDRB &= ~( 1 << PB6 );

    // set up the SPI module: SPI enabled, MSB first, master mode,
    //  clock polarity and phase = 0, F_osc/8
    SPCR = ( 1 << SPE ) | ( 1 << MSTR ) | ( 1 << SPR0 );
    SPSR = 1;     // set double SPI speed for F_osc/8

    // the previous settings clear the SPIF bit of SPCR, so we use our global
    //  flag to indicate that this does not mean we are currently transmitting
    SPITransmitting = 0;
}

Most commands require data flow in only one direction: from the mega644 to the mega168. This can be achieved with an SPI transmit command.

void SPITransmit( unsigned char data )
{
    if ( SPITransmitting )                  // if we really are transmitting
        while ( ! ( SPSR & ( 1 << SPIF )))  //  wait for completion of
            ;                               //  previous transmission
    SPDR = data;                            // begin transmission
    SPITransmitting = 1;                    // flag transmission in progress
}

Reading data back from the mega168 is only slightly more complicated since we need to give the mega168 time (~3us) to prepare the data byte we’re requesting. Once it’s ready we then need to transmit an extra byte since every SPI transaction has data flow in both directions. As our byte is being sent to the mega168, the data we’re interested is being sent to us. At the end of the transmission, the value from the mega168 will be in the mega644’s SPI data register, SPDR. We need an extra function to perform our 3 microsecond delay, so we’ll include an example here:

static inline void delay_us(unsigned int microseconds) __attribute__((always_inline));
void delay_us(unsigned int microseconds)
{
    __asm__ volatile (
        "1: push r22"     "\n\t"
        "   ldi  r22, 4"  "\n\t"
        "2: dec  r22"     "\n\t"
        "   brne 2b"      "\n\t"
        "   pop  r22"     "\n\t"
        "   sbiw %0, 1"   "\n\t"
        "   brne 1b"
        : "=w" ( microseconds )
        : "0" ( microseconds )
    );
}


unsigned char SPIReceive( unsigned char data ) // data is often a junk byte (e.g. 0)
{
    if ( SPITransmitting )
        while ( ! ( SPSR & ( 1 << SPIF )))     // wait for completion of
            ;                                  //  previous transmission
    delay_us( 3 );                             // give the mega168 time to prepare
                                               //  return data
    SPDR = data;                               // start bidirectional transfer
    while ( ! ( SPSR & ( 1 << SPIF )))         // wait for completion of
        ;                                      //  transaction
    // reading SPCR and SPDR will clear SPIF, so we will use our global flag
    //  to indicate that this does not mean we are currently transmitting
    SPITransmitting = 0;
    return SPDR;
}

We can now put wrapper functions around these low-level SPI commands to communicate with the mega168. For instance, here is a command for setting motor 1:

void setMotor1( int speed )
{
    // first, we'll prepare our command byte

    unsigned char command;

    if ( speed > 255 )
        speed = 255;
    if ( speed < -255 )
        speed = -255;

    if ( speed >= 0 )
        command = 136;	// motor 1, forward
    else
    {
        command = 138;	// motor 1, reverse
        speed = -speed;
    }

    // the MSB of the speed gets tacked onto the command byte
    command |= ( (unsigned char) speed & 0x80 ) >> 7;

    // now, send the command
    SPITransmit( command );
    SPITransmit( (unsigned char) speed & 0x7F );
}