1
\$\begingroup\$

I have written a simple SPI slave on an ATMEGA328PB. It mostly works, however, I can't seem to get the first byte to be what I want. The difference between the first, second, and all other attempts is also confusing me.

The master pulls the SS line low, waits 500us, then clocks out 15 '0xFF' followed by a '0x00', waits another 500us, then releases the SS. Simultaneously the AVR responds with its 'payload'.

#define F_CPU 8000000L

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>

#define togbit(port,bit) (port) ^= (1 << (bit))  // XOR

// SPI variables
volatile uint8_t received = 0;
volatile uint8_t cnt = 0;
volatile uint8_t incoming[16] = {0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA};
volatile uint8_t payload[16] = "ABCDEFGHIJKLMNO";

uint8_t spiCom(uint8_t data)
{
    SPDR0 = data; // Put current 'payload' into outgoing buffer
    return SPDR0;  // return incoming byte
}

ISR(SPI0_STC_vect)
{
    incoming[received] = spiCom(payload[cnt]);  // Transfer a byte

    if (incoming[received] == 0xFF) // start clocking out data when receiving 0xFF padding
    {
        cnt++;
        received++;
    }

    if (received >= 16 || incoming[received] == 0x00)  // reset array position counters if buffer overflows or received NULL
    {
        received = 0;
        cnt = 0;
    }
}

void initSPI(void)
{
    volatile char IOReg;  // Ignore compiler warnings about me...I'm needed to clear a flag
    DDRB |= (1<<PINB4);  // Set PB4(MISO) as output
    SPCR0 |= (1<<SPIE)|(1<<SPE);  // Enable SPI Interrupt and Slave Mode with SCK = CK/4

    // Clear SPIF bit in SPSR by accessing the SPI registers
    IOReg = SPSR0;
    IOReg = SPDR0;

    sei();
}

int main(void)
{
    DDRB = 0x00; // all of PORTB is input
    initSPI();
    DDRB |= (1<<PINB0);  // LED I/O is output

    SPDR0 = 0xC8;  // pre-load something identifiable into SPI buffer

    while (1) 
    {
        togbit(PORTB, PINB0);  // Blink LED while waiting for SPI interrupt
        _delay_ms(1000);
    }
}

The first attempt at communication, the master receives: 0xC8,A,B,C,D...etc.

The second attempt at communication, the master receives: 0x00,A,B,C,D...etc.

The third and all subsequent communications, the master receives: A,A,B,C,D...etc.

These signals were verified by my logic analyzer.

I can almost accept how the first attempt goes, since 0xC8 is what I pre-load the SPDR register with (as a test). That said, I still found this behavior unexpected, so I must be misunderstanding something in the SPI section of the AVR's datasheet.

I cannot wrap my head around what is happening after that. If my misunderstanding has to do with the order with which the SPDR register (buffer?) is handled during communication, I still can't figure out the difference between the second and third (and all subsequent) communications.

I've read just about every SPI on AVR tutorial I could find, but can't figure out where I am going wrong. Any advice would be much appreciated.

\$\endgroup\$
3
\$\begingroup\$

The answer is in your question already. Simultaneously being the key word.

Interrupt is called on RX complete, which is when the a byte from master has already been received. This must then also mean a byte from slave has already been sent, and your code sets up the next byte to be sent.

You have to set up the first byte to be sent before the communication starts.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.