SPI reset problem

Go To Last Post
19 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm using an Atmega644a that communicates with an Arduino Uno (Atmega328p) over SPI for logging purposes. The Arduino is the SPI slave. It works fine unless I reset or flash the Atmega644a; after this I always have to reset the Arduino for the SPI logging to keep working. I don't have this problem when I reset the Atmega with a watchdog timer, after this the SPI logging still works. Any thoughts on how to fix this?

 

My SPI code on the master is as follows:

 

#define SCK PINB7
#define MOSI PINB5
#define SS PINB4

void spi_master_init() {
    DDRB |= (1 << SS) | (1 << MOSI) | (1 << SCK);
    SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
}

uint8_t spi_print_char(char data) {
    /*transmit the byte to be sent */
    SPDR = data;
    /* wait for the transfer to complete */
    while (!(SPSR & (1<<SPIF)));
    /* return byte read from buffer */
    return SPDR;
}

void spi_print_string(char *s) {
    while(*s) spi_print_char(*s++);
    spi_print_char('\r');
    spi_print_char('\n');
}

 

The code for the Arduino Slave is taken from here: https://gist.github.com/chrismey.... It uses SPI interrupts to fill a buffer, and when it receives the "\n" character it prints it to the serial output.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In other words, when you reset the SPI master the slave gets stuck?

ɴᴇᴛɪᴢᴇᴎ

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Exactly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In this case, I guess we should look for the problem in the slave code.

I've looked at your link, but it'd be better to work on the exact slave code you're using.

ɴᴇᴛɪᴢᴇᴎ

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have made no modifications to the slave code that I posted before

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The code you posted was the master's. Or did you mean https://gist.github.com/chrismey... :


// Written by Nick Gammon
// February 2011
/**
 * Send arbitrary number of bits at whatever clock rate (tested at 500 KHZ and 500 HZ).
 * This script will capture the SPI bytes, when a '\n' is recieved it will then output
 * the captured byte stream via the serial.
 */

#include <SPI.h>

char buf [100];
volatile byte pos;
volatile boolean process_it;

void setup (void)
{
  Serial.begin (115200);   // debugging

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);
  
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);
  
  // get ready for an interrupt 
  pos = 0;   // buffer empty
  process_it = false;

  // now turn on interrupts
  SPI.attachInterrupt();

}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;  // grab byte from SPI Data Register
  
  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;
    
    // example: newline means time to process buffer
    if (c == '\n')
      process_it = true;
      
    }  // end of room available
}  // end of interrupt routine SPI_STC_vect

// main loop - wait for flag set in interrupt routine
void loop (void)
{
  if (process_it)
    {
    buf [pos] = 0;  
    Serial.println (buf);
    pos = 0;
    process_it = false;
    }  // end of flag set
    
}  // end of loop

 

ɴᴇᴛɪᴢᴇᴎ

Last Edited: Sat. Mar 26, 2016 - 12:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes that's the one. That's the same one as I posted no? 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just wanted to make sure it was indeed the exact same code. :-)

ɴᴇᴛɪᴢᴇᴎ

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The fact that you can reset the slave anytime and the master will cope just fine suggested me that the problem was with the slave not coping well with an interrupted transmission, or something like that. However, I might be wrong: it would help to know which of the slave or the master actually is left hanging in the wrong state…

 

On the master side, the only problem I can figure would be a failed setup. For example:

• Bit 4 – MSTR: Master/Slave Select
This bit selects Master SPI mode when written to one, and Slave SPI mode when written logic zero. If SS is
configured as an input and is driven low while MSTR is set, MSTR will be cleared
, and SPIF in SPSR will
become set. The user will then have to set MSTR to re-enable SPI Master mode.

Admittedly, this is from the ATmega328 datasheet, not the Atmega644a. I don't see how that could happen though: your master code sets DDRB before SPCR. Perhaps you can double-check that the generated assembly does to.

 

On the slave side, the problem would have to come from an interrupted transmission.

I'm not sure how the ATmega SPI hardware deals with that issue. For example, what happens if SS is released in the middle of a transmission? I can't find information about that in the ATmega datasheet. We could imagine the interrupt never triggers because the end of transmission is never reached…

If the HW does the right thing (which is likely), what in your slave code could go wrong?
Assuming the interrupt triggers alright, the ISR would have no impact only if the buffer was full. In other words, the highlighted condition would always be false:

ISR (SPI_STC_vect)
{
byte c = SPDR;  // grab byte from SPI Data Register

  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;

    // example: newline means time to process buffer
    if (c == '\n')
      process_it = true;

    }  // end of room available
}  // end of interrupt routine SPI_STC_vect

… thus process_it would never be set to true, thus the main loop() would never send anything to Serial and the communication would look stalled.

I see two possibilities for this to happen:

  1. the buffer gets full before any "\n" has been received. For example, because buf is not big enough to hold both an interrupted message (received before master reset) and another full one (received after master reset). Hard to tell, cause you haven't posted the part of your code creating the messages. To make sure this isn't the case, make sure buf is at least twice as big as the longest message.
  2. the buffer receives a full message (with a "\n") and sets process_it to true before the last message has finished being sent on Serial and process_it is reset (this could happen because serial is slow and process_it is reset at the end of your handling code). In this case, that new message would be ignored and the buffer would keep on filling.
if (process_it)
    {
    buf [pos] = 0;
    Serial.println (buf);
    pos = 0;
    process_it = false;
    }  // end of flag set

Anyway, that slave code looks really bad. It doesn't handle concurrent access (between the ISR and the main loop) correctly. Other things could go wrong although they would more likely end up with mangled messages than a hanged slaved.

An easy "fix" (not a real fix!) would be to move the process_it reset at the beginning, like so:

if (process_it)
    {
    process_it = false;
    buf [pos] = 0;
    Serial.println (buf);
    pos = 0;    
    }  // end of flag set

ɴᴇᴛɪᴢᴇᴎ

Last Edited: Sat. Mar 26, 2016 - 02:09 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the help. The messages I'm sending are very short and I also tried increasing the buffer size but same problem. The arduino is actually in the correct state after the master resets: it's still processing the SPI data but the data is all wrong after a reset (it receives the correct number of characters but the wrong ones). Since it then also doesn't get a "\n" character (most of the times) it finally stops processing the data after the buffer has been filled to 100. I tried software resetting the Arduino after the Atmega master resets but still no difference, it really needs a reset from the reset pin for the SPI to start working again.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Alright. So I suspect what happens is that, if the master resets in the middle of a transmission, the slave then assumes what it gets after the master comes back online is the remainder of the interrupted transmission. In other words, the slave interprets the first bits of the new transmission as being the last bits of the interrupted one.

If someone more knowledgeable about HW SPI behavior could confirm that, it'd be nice.

 

Anyway, as written earlier, there are still issues that need to be fixed with the slave code.

Suppose for example that you receive new data while sending the last message to Serial: the first bytes will be appended at the tail of the buffer,  then Serial finishes and reset pos, thus the next bytes are now written at the beginning of the buffer. So your new message loses all its first bytes:

if (process_it)
    {
    process_it = false;
    buf [pos] = 0;           // new bytes arrive while slave is writing to Serial:
    Serial.println (buf);    // first bytes go to buf[pos], buf[pos+1], etc.
    pos = 0;                 // next  bytes go to buf[0], buf[1], etc.
}

In fact, it could be even worse: the first byte arriving could overwrite your ending 0, thus println() wouldn't know where its string ends…

If you need that functionality in you project, I'd strongly suggest to redesign the slave code entirely.

 

I tried software resetting the Arduino after the Atmega master resets but still no difference, it really needs a reset from the reset pin for the SPI to start working again.

If my above hypothesis about HW behavior is right, I suppose you'd have to reset the slave SPI. Perhaps emptying SPDR would be enough. I suppose clearing and setting the SPE bit in the SPCR register would work. In any case, your slave code would need a way to realize what happened.

ɴᴇᴛɪᴢᴇᴎ

Last Edited: Sat. Mar 26, 2016 - 05:11 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes you are right, that piece of code is far from ideal. Regarding the reset problem you were right: when I clear and set the SPE bit in SPCR the slave correctly receives the data from the master. Clearing SPDR did not work. That solution is a bit of a nuisance though since then I need an extra pin going from the master to slave to "reset" it every time the master has a power reset.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Perhaps you can share the reset line between the master and the slave…

But how often do you expect the master to reset while the slave does not?

ɴᴇᴛɪᴢᴇᴎ

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sharing the reset line between the 2 does not have an impact on power-on reset which is the only case when the problem occurs. But in normal operation there shouldn't be any power on resets so it's not that much of a problem, I guess I can live with it. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Then I guess you could delay the slave power on a bit, one way or another.
 

ɴᴇᴛɪᴢᴇᴎ

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes but the problem is that the power sources are independent: master is powered by batteries and the Arduino is powered by the USB port of a Raspberry Pi.

Last Edited: Sat. Mar 26, 2016 - 06:54 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Then I see no other solution but controlling the slave reset from the master…

ɴᴇᴛɪᴢᴇᴎ

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes indeed, I think you're right. Thanks again for all the help!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Surely you would use /CS like any other SPI peripheral.

This will synchronise your Slave. And also allow you to have other Slaves on the bus.

David.