'ATMEGA328P SPI freezes after unspecified period

I am working on a project where I read out an MCP3008 ADC and an MCP4901 DAC. Both use SPI to communicate with the arduino.

For a few seconds everything is fine, I get all the values I need from the Arduino to the DAC, from the DAC to the ADC and then back again to the Arduino.

However, after an unspecified amount of time, the program freezes in the while loop (probably gets stuck in an infinite while loop). The timer in the program still continues with the interrupt, but the main loop is frozen.

When debugging I found out that the SPI transceiver function is the problem. After some time it stops there. This is the function:

/* spi data buffer send */
uint8_t spi_tranceiver (uint8_t data)
{
    // Load data into the buffer
    SPDR = data;
    
    // Wait until transmission complete
    while(!(SPSR & (1<<SPIF)));
      // Return received data
      return(SPDR);
}

My guess is it gets stuck inside the while loop waiting for the transmission to complete. But I cannot tell for certain. The output should be like this, but it freezes after a while:

enter image description here

Anyone got a clue?

This is the whole program:

/*
* Created: 6-4-2022 13:15:10
* Author : meule
*/ 
    
#define F_CPU 16000000
#define BAUD_RATE 9600
#define UBBRN (F_CPU/16/BAUD_RATE)-1
    
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>    
    
// ARDUINO definitions
#define MISO PORTB4
#define MOSI PORTB3 //SDI
#define SCK PORTB5
#define SS PORTB2
#define SS_2 PORTB1
#define NUMSTEPS 200
    
//Global variables :/
char sine[NUMSTEPS];
int index = 0;
    
void init_SPI()
{
    // Set SS, SS_2, MOSI and SCK output, all others input
    DDRB = (1<<SS)|(1<<SS_2)|(1<<MOSI)|(1<<SCK);
    
    //Set the slave select pin (Active low)
    PORTB |= (1 << SS);
    PORTB |= (1 << SS_2);

    // Enable SPI, Master, set clock rate fosc/16 
    SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
}
    
// Initialize the USART
void init_usart(){
        
    // Set baud registers
    UBRR0H = (unsigned char)(UBBRN>>8);
    UBRR0L = (unsigned char)UBBRN;
        
    // Enable transmitter
    UCSR0B = (1<<TXEN0) | (1<<RXEN0);
        
    // Data format of 8-bits
    UCSR0C = (1 <<UCSZ01) | (1 <<UCSZ00);
}
    
void init_timer(){
    TIMSK0 = 0b00000010; //Set compare mode A
    TCCR0A = 0b00000010; //Set CTC mode
    TCCR0B = 0b00000101; //Set prescaler to 1024
    OCR0A = 255;
}
    
void init_Sin(int amplitude, int dc_offset, int num_steps, char *sine){
    //Calculate Sine wave for the DAC to output
    for(int i = 0; i < num_steps; i++){
      sine[i] = dc_offset + amplitude * sin((i*360)/num_steps);
    }
}

/* spi data buffer send */
uint8_t spi_tranceiver (uint8_t data)
{

    // Load data into the buffer
    SPDR = data;
    
    // Wait until transmission complete
    while(!(SPSR & (1<<SPIF)));
    // Return received data
    return(SPDR);
}

void writeDAC(uint8_t data){
    PORTB &= ~(1 << SS_2);
    
    //8 bits for initializing the DAC
    char init = 0b00110000;
    //Get the 4MSB from the data
    char data1 = (data >> 4);
    //Get the 4LSB from the data
    char data2 = (data << 4);
    //Combine init with data1
    init |= data1;
    //Send data to the DAC
    spi_tranceiver(init);
    spi_tranceiver(data2);
    
    PORTB |= (1 << SS_2);
}

float ReadADC(char opcode, int vref, int resolution)
{
    // Activate the ADC for reading
    PORTB &= ~(1 << SS);

    spi_tranceiver(0b00000001);
    
    //Get the first 8 bits from the ADC
    uint8_t analogH = spi_tranceiver(opcode);
    
    //Mask the bits since I only need the 2LSB
    analogH = (analogH & 0b00000011);
    
    //Get the second 8 bits from the ADC
    uint8_t analogL = spi_tranceiver(0);
        
    //Convert the ADC values into a 16 bit value
    uint16_t total = (analogH << 8) + analogL;
    
    //Convert the value with the vref and resolution to a float
    float result = ((total*vref)/(float)resolution);
    PORTB |= (1 << SS);
    
    return result;
}

int main(void)
{
    init_SPI();
    init_usart();
    init_timer();
    init_Sin(100, 127, NUMSTEPS, sine);
    
    // Enable the global interrupt
    sei();
    
    /* Replace with your application code */
    while (1) 
    {
        char data[6];
        float val = ReadADC(0b10010000,5,1024);
        
        dtostrf(val, 5, 3, data);
        data[4] = 10;
        data[5] = 0;
        
        int i = 0;
        
        while(data[i] != 0){
            while (!(UCSR0A & (1<<UDRE0)));
            UDR0 = data[i];
            i++;
            _delay_ms(5);
        }
    }
    _delay_ms(5);
}

ISR(TIMER0_COMPA_vect){
    if(index < NUMSTEPS){
        writeDAC(sine[index]);
    }
    else{
        index = 0;
        writeDAC(sine[index]);
    }
    index++;
}


Solution 1:[1]

The function writeDAC is called from the interrupt handler. Then it calls spi_tranceiver which triggers SPI transmission (thus affecting SPIF flag)

Also this function is called from the main. Imagine this situation:

SPDR = data; - this starts a byte transmission

  • At this point the timer interrupt happens, which also calls spi_tranceiver, which overwrites the transmission buffer, waits transmission to end, clears SPIF (by reading SPDR)

while(!(SPSR & (1<<SPIF))); - This cycle newer ends because now there is no ongoing transmission and SPIF is cleared.

You should avoid to use the same peripherals in concurrent threads (main and the interrupt).

There are many approaches to fix that issue:

  1. You can bluntly disable interrupt inside spi_tranceiver and restore I flag on exit.
  2. You can perform whole transmission in SPI interrupt, using some kind of circular buffer to be filled with data.
  3. Move the code outside the interrupt into the main. E.g. insted of TIMER0_COMPA_vect interrupt (don't forget also to disable the interrupt flag in TIMSK) add something like:
// Check OCA flag is set
if (TIFR0 & (1 << OCF0A)) {
  TIFR0 = (1 << OCF0A);    // Clearing OCA flag by writing 1 to it
  writeDAC(sine[index]);   // Doing those things in the safe moment of time
  index++;
  
  if (index >= NUMSTEPS)
    index = 0;
}



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 sunriax