Atmega328p SPI在未指定的期间后冻结

发布于 2025-01-25 18:58:17 字数 4708 浏览 3 评论 0原文

我正在研究一个项目,在该项目中,我读了一个MCP3008 ADC和MCP4901 DAC。两者都使用SPI与Arduino通信。

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

但是,在未指定的时间之后,该程序在段循环中冻结(可能会陷入无限循环时)。该程序中的计时器仍在中断,但主要循环被冻结。

调试时,我发现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);
}

我的猜测是它被卡在循环中,等待传输完成。但是我无法确定。 输出应该是这样的,但是在一段时间后冻结:

这是整个程序:

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

我不吻晚风 2025-02-01 18:58:17

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

也从main调用此功能。想象一下这种情况:

发生了一个字节传输

  • spdr = data; -此时,计时器中断 ,它也称为spi_tranceiver,该spi_tranceiver覆盖传输缓冲区,等待传输到结束,清除SPIF(通过读取spdr)

while(!(SPSR&amp;(1&lt;&lt; spif)))); - 此周期更新的结局,因为现在没有正在进行的传输和SPIF清除。

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

有许多方法可以解决该问题:

  1. 您可以直接禁用spi_tranceiver内的中断,并在退出时恢复我标记。
  2. 您可以使用某种圆形缓冲区中的数据中断进行整个传输,以填充数据。
  3. 将代码在中断之外移动到main中。例如,设置为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;
}



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;
}



~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文