Flagging info about arduino framework and SPI on AVR4809

Just to start by saying I am no expert, just to share my findings of setting up SPI transactions using the arduino framework on an AVR4809 40 pin variant.

Also to acknowledge the work done by folks to provide the arduino framework etc, I appreciate it takes a lot of time and effort.

I appreciate it’s a long post, but I hope it helps folks to setup SPI on the AVR4809

I have one 4809 setup as master and the other as slave. The four hardware pins MISO, MOSI, clock and slave select are connected as required.

As I am new to using SPI, I read up a fair bit, my application is pretty straightforward, I need the slave to provide a uint16_t to the master. So the master does the three required SPI transfers to get the high byte and low byte returned from the slave. I initially set it up using two mega2560s I had kicking around and it all worked fine.

However, when I transferred the code to the avr4809 environment, there were various compile errors, due I guess to the different chip architecture, but I thought that the arduino framework would handle that.

The slave code is interrupt based. It was apparent that SPDR is not usable/ recognised in the slave code and using the interrupt function name which worked on the avrmega2560 code generated a compiler warning when used with the 4809.

Also using the following to setup the 4809 in slave mode caused compile errors, presumably because it does not reference the SPIO.CTRLA register?:

  SPCR = (1 << SPIE) | (1 << SPE) ;

So I have provided below the code (master and slave) that worked fine on the mega2560, and below that the mods I made to the master and slave setups on the 4809s which is based on the setups provided by Microchip.

working Master 2560


#include <SPI.h>

char trigger;          // used to facilitate the SPI transaction
uint16_t azimuth;  // the variable used to compose the highbyte and lowbyte

byte LB ;            // holds the low byte returned from slave
byte HB ;            // ditto highbyte
byte dummy ;     // accepts the first unknown byte from the initial SPI transfer

int loopcount = 1;    // increments once per second in the main loop

void setup ()
{
  digitalWrite(SS, HIGH);  // Set SS high
  Serial.begin(9600);
  HB = 0;
  LB = 0;

  delay(1000);  // for serial setup

  SPI.begin();
}


void loop ()
{

  // this code sets up the SPI and sends a trigger character 'H' or 'L' to the slave
  // it receives two bytes representing low and high bytes of a 16 bit unsigned int and assembles them to form the uint16_t


  // configure the SPI settings
  SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));   // 2 meg clock

  // Select the Slave
  digitalWrite(SS, LOW);              // SS is active LOW
  
  trigger = 'A';
  dummy = SPI.transfer(trigger);      // this returns whatever happened to be in the SPDR
  delayMicroseconds(20);              // propagation delay required by SPI

  trigger = 'L';
  LB = SPI.transfer(trigger);         // this returns the low byte
  delayMicroseconds(20);              // propagation delay required by SPI

  trigger = 'H';
  HB = SPI.transfer(trigger);         // this returns the high byte
  delayMicroseconds(20);              // propagation delay required by SPI

  // deselect the Slave
  digitalWrite(SS, HIGH);

  SPI.endTransaction ();              // transaction over

  SPI.end();
  azimuth = (HB << 8) | LB ;          // push the highbyte value 8 bits left to occupy the high 8 bits of the 16 bit int

  Serial.print("loop number  ");
  Serial.println(loopcount);

  Serial.print("low byte value is  ");
  Serial.println(LB);
  Serial.print("High byte value is ");
  Serial.println(HB);
  Serial.print("azimuth is ");
  Serial.println(azimuth);
  loopcount ++;
  delay(1000);

} // end void loop

working slave

#include <SPI.h>

//  from master
volatile char SPIReceipt;

//to be returned
volatile byte highByteReturn ;
volatile byte lowByteReturn ;

void setup()
{
  Serial.begin(9600);
  lowByteReturn = lowByte(271);      //just return 271 for test purposes
  highByteReturn = highByte(271);

  // set the control register bits for  SPI interrupt and SPI enable
  SPCR = (1 << SPIE) | (1 << SPE) ;

  // send data on MISO
  pinMode (MISO, OUTPUT);

   // interrupt for SPI servicing
  SPI.attachInterrupt();

  
} //end setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
  SPIReceipt = SPDR;
  
  if (SPIReceipt == 'A')    // a dummy transaction which loads the SPDR with the low byte
  {                         // in readiness for the 'L' transaction
    SPDR = lowByteReturn;
  }
  if (SPIReceipt == 'L')    // low byte is returned and SPDR is loaded with the high byte
  {                         // in readiness for the 'H' transaction
    SPDR = highByteReturn;
  }

  if (SPIReceipt == 'H')     // High byte is returned and SPDR is loaded with zero
  {                          // in readiness for the 'L' transaction
    SPDR = 0x00;    //fill spdr with 0
  }


}  // end of interrupt routine SPI_STC_vect


void loop()
{
  //nothing to do in loop - the interrupt returns the data to the master

}

mods required in setup of the master when coded on 4809
With these mods, the code works fine on the 4809s.

Initialisation of SPI called the routine below from setup(). The remaining code in the loop body was unchanged:

static void SPI0_init(void)
{
    PORTA.DIR |= PIN4_bm; /* Set MOSI pin direction to output */
    PORTA.DIR &= ~PIN5_bm; /* Set MISO pin direction to input */
    PORTA.DIR |= PIN6_bm; /* Set SCK pin direction to output */
    PORTA.DIR |= PIN7_bm; /* Set SS pin direction to output */

    SPI0.CTRLA = SPI_CLK2X_bm           /* Enable double-speed */
               | SPI_DORD_bm            /* LSB is transmitted first */
               | SPI_ENABLE_bm          /* Enable module */
               | SPI_MASTER_bm          /* SPI module in Master mode */
               | SPI_PRESC_DIV16_gc;    /* System Clock divided by 16 */
}

Mods required to setup of the slave (from Microchip):

static void SPI0_init(void)
{
    PORTA.DIR &= ~PIN4_bm; /* Set MOSI pin direction to input */
    PORTA.DIR |= PIN5_bm;  /* Set MISO pin direction to output */
    PORTA.DIR &= ~PIN6_bm; /* Set SCK pin direction to input */
    PORTA.DIR &= ~PIN7_bm; /* Set SS pin direction to input */

    SPI0.CTRLA = SPI_DORD_bm        /* LSB is transmitted first */
               | SPI_ENABLE_bm      /* Enable module */
               & (~SPI_MASTER_bm);     /* SPI module in Slave mode */

    SPI0.INTCTRL = SPI_IE_bm; /* SPI Interrupt enable */
}

mods required to the interrupt routine on the 4809 slave - the essential difference here is that SPDR cannot be used and SPI0.DATA takes its place. I am not sure if the same interrupt vector name can be used, it only generated a compiler warning, so maybe ok.

// SPI interrupt routine
ISR(SPI0_INT_vect)                                     // was this in arduino -> (SPI_STC_vect)
{
  SPIReceipt = SPI0.DATA;
  
  if (SPIReceipt == 'A')    // a dummy transaction which loads the SPDR with the low byte
  {                         // in readiness for the 'L' transaction
    SPI0.DATA = lowByteReturn;
  }
  if (SPIReceipt == 'L')    // low byte is returned and SPDR is loaded with the high byte
  {                         // in readiness for the 'H' transaction
    SPI0.DATA = highByteReturn;
  }

  if (SPIReceipt == 'H')     // High byte is returned and SPDR is loaded with zero
  {                          // in readiness for the 'L' transaction
    SPI0.DATA = 0x00;        // fill spdr with 0
  }

    SPI0.INTFLAGS = SPI_IF_bm; /* Clear the Interrupt flag by writing 1 */

}  // end of interrupt routine SPI_STC_vect