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 被清除。
您应该避免在并发线程(主线程和中断线程)中使用相同的外设。
有很多方法可以解决这个问题:
- 你可以在
spi_tranceiver
中直接禁用中断并在退出时恢复 I 标志。
- 您可以在 SPI 中断中执行整个传输,使用某种循环缓冲区来填充数据。
- 将中断外的代码移入
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;
}
我正在做一个项目,我在其中读取了 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 被清除。
您应该避免在并发线程(主线程和中断线程)中使用相同的外设。
有很多方法可以解决这个问题:
- 你可以在
spi_tranceiver
中直接禁用中断并在退出时恢复 I 标志。 - 您可以在 SPI 中断中执行整个传输,使用某种循环缓冲区来填充数据。
- 将中断外的代码移入
main
。例如。 insted ofTIMER0_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;
}