ATMEGA328P SPI 在未指定时间后冻结

ATMEGA328P SPI freezes after unspecified period

我正在做一个项目,我在其中读取了 MCP3008 ADC 和 MCP4901 DAC。两者都使用 SPI 与 arduino 通信。

几秒钟后一切都很好,我得到了从 Arduino 到 DAC,从 DAC 到 ADC,然后再返回到 Arduino 的所有我需要的值。

但是,经过一段未指定的时间后,程序在 while 循环中冻结(可能陷入无限 while 循环)。程序中的定时器仍然继续中断,但主循环被冻结。

调试的时候发现是SPI收发功能的问题。一段时间后它停在那里。这是函数:

/* 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);
}

我的猜测是它卡在 while 循环中等待传输完成。但我不能肯定。 输出应该是这样的,但是过了一会儿就卡住了:

有人知道吗?

这是整个程序:

/*
* 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++;
}

函数 writeDAC 从中断处理程序调用。 然后它调用 spi_tranceiver 触发 SPI 传输(从而影响 SPIF 标志)

此函数也是从 main 调用的。想象一下这种情况:

SPDR = data; - 这将启动字节传输

  • 此时定时器中断发生,同时调用spi_tranceiver,覆盖传输缓冲区,等待传输结束,清除SPIF(通过读取SPDR)

while(!(SPSR & (1<<SPIF))); - 这个周期更新结束,因为现在没有正在进行的传输并且 SPIF 被清除。

您应该避免在并发线程(主线程和中断线程)中使用相同的外设。

有很多方法可以解决这个问题:

  1. 你可以在 spi_tranceiver 中直接禁用中断并在退出时恢复 I 标志。
  2. 您可以在 SPI 中断中执行整个传输,使用某种循环缓冲区来填充数据。
  3. 将中断外的代码移入main。例如。 insted of TIMER0_COMPA_vect 中断(不要忘记也禁用 TIMSK 中的中断标志)添加如下内容:
// 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;
}