'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:
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:
- You can bluntly disable interrupt inside
spi_tranceiver
and restore I flag on exit. - You can perform whole transmission in SPI interrupt, using some kind of circular buffer to be filled with data.
- Move the code outside the interrupt into the
main
. E.g. insted ofTIMER0_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 |