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

Hi Paul
Thank you for sharing your results on this!

I am currently working on a SPI Client implementation on an Arduino Nano Every too.
What a mess, that the SPI.h does not respect SPI Client Operation in any way. I went the same path, taking Microchips Documentation by word.

Maybe you could help me in two points please:
Did you utilize “ATMEGA328 Register Emulation” ?
And: Did you configure PORTMUX to achieve the pin mapping to Port A? (Arduino Studio has PortE as default for SPI comm on its Nano Every Layout)

Thanks

Hi Stefan,

It’s a while since I did this. In answer to your queries:

Did you utilize “ATMEGA328 Register Emulation” -

no, didn’t use.

Did you configure PORTMUX to achieve the pin mapping to Port A

No I didn’t use this (from memory). I tried to include all the relevant code in my post, so it should work as is, to transfer the high byte and low byte.

Good luck,
Paul

1 Like

Thank you a lot for your very responsive and frank support on this Paul!

I came to a solution for using SPI on Arduino Nano Every Pinout (AVR4809).
I am very grateful for you to respond quite immediately after this project was finished by yours years ago. :star_struck:

Regarding my request about the PORTMUX it was

PORTMUX.TWISPIROUTEA |= PORTMUX_SPI01_bm;

still missing to map SPI to PortE which was chosen by Arduino Board Designers for Nano Every.

Here is what I got while learning from AzimutEncoder:

//SPI-UART Adapter
//Receives from SPI and redirects unmanipulated to USB-Serial using a FIFO to buffer
//Tested for and developed on an Arduino Nano Every
//NFCbrick 2025

#include <Arduino.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define BUFFER_SIZE 4096

volatile char SPIReceiptBuffer[BUFFER_SIZE]; // FIFO-Buffer
volatile uint8_t head = 0;                   // Write Pointer
volatile uint8_t tail = 0;                   // Read Pointer
 
void setup()
{
    Serial.begin(115200);
    Serial.println("USB-Serial 115200 Baud initialized");

    SPI0_init();
    sei(); // Enable global interrupts
}

static void SPI0_init(void)
{

    PORTMUX.TWISPIROUTEA |= PORTMUX_SPI01_bm;

    pinMode(MISO, OUTPUT);
    pinMode(MOSI, INPUT);
    pinMode(SCK, INPUT);
    pinMode(SS, INPUT);

    SPI0.CTRLA = SPI_ENABLE_bm & (~SPI_DORD_bm) & (~SPI_MASTER_bm); // SPI-Modul in Slave-Mode
    SPI0.INTCTRL = SPI_IE_bm;                                       // SPI-interrupt enable
    
    Serial.println("SPI0_init completed");
}

// SPI-Interrupt-Service-Routine
ISR(SPI0_INT_vect)
{
    char received = SPI0.DATA;

    uint8_t nextHead = (head + 1) % BUFFER_SIZE; // calculate next write pointer

    // Check for full buffer
    if (nextHead != tail)
    {
        SPIReceiptBuffer[head] = received; // load buffer
        head = nextHead;                   // increment write buffer
    }

    SPI0.INTFLAGS = SPI_IF_bm; // Clear interrupt flag to re-enable receiver
}

void loop()
{
    // 
    while (head != tail)
    {
        char data = SPIReceiptBuffer[tail]; // Read from buffer
        tail = (tail + 1) % BUFFER_SIZE;    // Update read pointer

        Serial.print(data, HEX);            // Just forward raw HEX to USB-Serial
    }
}```

Nice, thanks for sharing :slight_smile: