View previous topic :: View next topic |
Author |
Message |
rcooke
Joined: 23 Feb 2011 Posts: 21 Location: Oceanside, CA USA
|
SPI slave with timeout? |
Posted: Tue Aug 16, 2011 4:11 pm |
|
|
Hi Folks,
In our application we have a PIC18F24K22 set up as the slave SPI. Our code is working fine but there is a small possibility that if the master controller (our customer's responsibility) hangs during a transmission our board waits and waits and then won't be able to tickle the hardware WDT and the system resets. The WDT gets tickled in the main loop.
Is there a way to do slave SPI comms with a timeout? I'm using the hardware SPI module and the SPI interrupt.
Thanks,
Richard |
|
|
asmallri
Joined: 12 Aug 2004 Posts: 1635 Location: Perth, Australia
|
|
Posted: Tue Aug 16, 2011 10:46 pm |
|
|
If your SPI slave was interrupt driven then there would be no waiting on the master.
Seeing as it is not interrupt driven (or you would not have asked) you could add a timer and modify the SPI operations to include a check on the timer flag.
Personally I think the interrupt driven SPI is cleaner. _________________ Regards, Andrew
http://www.brushelectronics.com/software
Home of Ethernet, SD card and Encrypted Serial Bootloaders for PICs!! |
|
|
rcooke
Joined: 23 Feb 2011 Posts: 21 Location: Oceanside, CA USA
|
|
Posted: Wed Aug 17, 2011 8:59 am |
|
|
Actually I am using the SPI interrupt. Our issue is what happens if the master stops the transmission before all 8 bits are received? If the master (customer's computer) hangs during a transmission we'll be stuck waiting and if we don't tickle the hardware watchdog within 500ms our system resets.
Is there a way to make the SPI interrupt only wait for a specified time before returning to the main program?
Thanks,
Richard |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19538
|
|
Posted: Wed Aug 17, 2011 9:22 am |
|
|
The SPI interrupt only occurs _when 8 bits _have_ been received_. If the master stops transmitting after (say) 5 bits, you won't get an SPI interrupt at all.
The SPI interrupt never 'waits'. It only happens _after_ the byte is already in the input shift register, and transferred to the latch.
Now the _problem_ is resynchronisation when the master starts again. Not a problem if you are using the slave select (this going high resets the 8 bit counter), but otherwise difficult. What you need is something like monitor code on the slave device, which if a character is _not_ received in the timescale expected, checks the clock line, and if it is in the idle state, then resets the SPI (turn the peripheral off, and on again - just clear the SSPEN bit, and set it again).
Best Wishes |
|
|
rcooke
Joined: 23 Feb 2011 Posts: 21 Location: Oceanside, CA USA
|
|
Posted: Wed Aug 17, 2011 9:36 am |
|
|
Thanks Telmah. I think you have made the issue more understandable. I'm now going to have to figure out how to implement the monitor code.
Thanks again,
Richard |
|
|
John P
Joined: 17 Sep 2003 Posts: 331
|
|
Posted: Fri Aug 19, 2011 6:26 am |
|
|
I've never had this problem, but it sounds interesting. Is there any flag that the processor sets which says "SPI buffer is partly full"? Then you could check that a few times and if it keeps being seen, you can flush the buffer. If all you can do is check whether a full character has come in, you'd risk doing the reset when there had been a pause, but with Mr Murphy constantly on duty, you could get to the timeout just in the middle of an incoming character.
Or is there any kind of "incomplete character timeout" function? Maybe there should be. |
|
|
rcooke
Joined: 23 Feb 2011 Posts: 21 Location: Oceanside, CA USA
|
|
Posted: Fri Aug 19, 2011 8:36 am |
|
|
John,
The problem we're having is if the customer's computer (master) hangs during the 3 byte transmission to our slave. If we don't tickle the WDT within 1 sec it will reset our system. I'm going to need some sort of timeout mechanism that forces the ISR to exit if a TBD time interval has elasped. The master will re-send if we don't return the proper response. It's not ideal but the chance of the master hanging during a transmission (@1MHz SPI clock) should be pretty slim.
Thanks,
Richard |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19538
|
|
Posted: Fri Aug 19, 2011 2:49 pm |
|
|
You should not be in the ISR.
The SPI interrupt, occurs when there is _one_ character waiting to be received. Just one, and already received.
It sounds as if you are trying to handle three bytes in the ISR, which is simply 'wrong'.
The basic system should be:
Code: |
ISR - should just receive one character, and put it into a buffer. Nothing else.
Timer ISR - ticking at some useful time for your system - typically perhaps 50Hz. Use for your clocks in the system etc. Have a 'countdown' counter available here, with code like:
if (countdown) --countdown;
Main code. When it sees a first character arrive, load 'countdown' with a value greater than the worst case longest time for the whole message.
Then sit pulling characters from the buffer, and _watching_ countdown.
If countdown gets to zero, before the message is complete - implies a problem, force the error recovery.
|
Best Wishes |
|
|
rcooke
Joined: 23 Feb 2011 Posts: 21 Location: Oceanside, CA USA
|
|
Posted: Fri Aug 19, 2011 3:17 pm |
|
|
Ttelmah,
I agree that my scheme isn't pretty or "proper" but it is working. We need to receive 3 bytes. We need to send 0x5A back to the master as the 1st byte is received, call this one the Command byte.
Byte#2 then comes in (call it the Data byte) and we echo back the Command byte at the same time.
Byte#3 comes in (Dummy byte) and we have to parse the Command byte. Is it a read or a write? Then depending on the Command byte we either send back the value of the register pointed to by the Data byte or we write a new value of the register.
I preload the value 0x5A into SSP1BUF so it is ready to return to the master when the Command byte comes in.
I'm able to keep up with the test system which is using the Pickit3 serial pod at a SPI speed of 1.25MHz. The customer spec requires a speed of 1MHz so for now we are able to meet their requirements.
I will take a good long look at modifying our code to make it more proper. I know that ISRs are supposed to be a short as possible but in this application, I don't know how else to make it work as fast.
By the way our chip is the PIC18F24K22 running with a 32MHz crystal.
Thanks,
Richard |
|
|
John P
Joined: 17 Sep 2003 Posts: 331
|
|
Posted: Fri Aug 19, 2011 4:18 pm |
|
|
If I were doing this I'd want to put the incoming data into packets where each packet would start with some unique character. If there's absolutely no way to avoid having the start byte appear as data occasionally, the solution would be to send it twice when it's data, so a lone <start> always introduces a new packet.
Then there would be a timeout which the master has to use between the start of a packet and the start of the next.
If this is done, the slave knows when a packet starts. Then once a packet begins, the slave starts a timeout of its own, which is slightly shorter than the master's inter-packet timeout. If the timeout elapses before the packet is complete, the slave assumes that the master choked while sending the packet, and declares it void, and resets its SPI port even if the last character is incomplete. The slave then waits for the start of the next packet.
This might error-proof the transmission line, but it does it at the cost of slowing things down. So maybe it fails for that reason. |
|
|
rcooke
Joined: 23 Feb 2011 Posts: 21 Location: Oceanside, CA USA
|
|
Posted: Fri Aug 19, 2011 4:45 pm |
|
|
John P,
Unfortunately we don't (or can't) influence the customer and their comm scheme. We're just a small part of their overall system. They won't even tell us what the inter-byte delay can be.
In other words, it's what it is and we can't change it even if it would make a "better" system.
Thanks,
Richard |
|
|
John P
Joined: 17 Sep 2003 Posts: 331
|
|
Posted: Fri Aug 19, 2011 6:59 pm |
|
|
All right, another idea.
Send the incoming clock line to the SPI slave, and also to the input to a counter. If all is well, the count will either be divisible by 8 (if an integral number of SPI characters have been seen) or it will be changing rapidly. A number not divisible by 8 that doesn't change is an indicator that communication has stalled. Could you pry loose from the customer any information on what the recovery from a stall looks like? As in, how long it will be before the line becomes active again? That would determine how long you'd want to allow the non-divisible-by-8 state to exist before you declare an error condition. Or if it turns out that any perturbation in the clock rate is going to be the result of an error, maybe that's all you need. Get the 8 bits in 8 microseconds and you're fine, but if you're still waiting at 9 microsec, reset and wait for the next byte to start. |
|
|
rcooke
Joined: 23 Feb 2011 Posts: 21 Location: Oceanside, CA USA
|
|
Posted: Mon Aug 22, 2011 5:49 pm |
|
|
John P,
That might work but as is usual for one of our projects - we've run out of free inputs. I do like your idea though.
Richard |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19538
|
|
Posted: Tue Aug 23, 2011 2:03 am |
|
|
I think even given your rather strange circumstances, a timer is your answer.
What you need to do is basically (pseudo code):
Code: |
//Start of SPI interrupt handler
//set a hardware timer to a particular value, and clear it's interrupt flag
get the first character from the SPI (since the interrupt has triggered).
timeout = false;
//Now wait for your subsequent characters but use:
do {
if (SPI_DATA_IS_IN()) {
//here read the SPI character (singular) and reset the timer.
//perform your parsing on the input 'packet' here.
//set flag 'packet_complete' when all the data has been received.
}
else {
//If timer interrupt flag is set, then a character has not arrived
//disable the SPI, re-enable it, and set the 'timeout' flag to leave the
//interrupt.
}
} while (!timeout && !packet_complete);
|
The point is that you never read a character, without first checking that one is already in the input buffer (using spi_data_is_in), and while waiting for the character(s) you have a hardware timer counting. If it overflows, it's interrupt flag will become set, and you use this to say 'something wrong'.
You chose the timer divider (set in the main), and preset value, depending on how quickly characters should arrive. plus a margin.
Hopefully you can follow the logic, and see how the SPI handler will have to work.
Best Wishes |
|
|
rcooke
Joined: 23 Feb 2011 Posts: 21 Location: Oceanside, CA USA
|
|
Posted: Wed Aug 24, 2011 8:51 pm |
|
|
Ttelmah,
Thanks for the info. I appreciate all the help folks give on this forum. This is a great resource.
I'm still running into a real can of worms with this SPI routine. If I use this for the SPI ISR:
Code: |
#INT_ssp
void ssp_isr(void)
{
unsigned int8 dummy;
unsigned int8 packet_complete = 0;
Command_Byte = spi_read(); // slave must send the sync byte as 1st byte
Data_Byte = spi_read(Command_Byte); // slave must echo the command byte back to master
...parse code is here....
} |
This works every time. I'm able to catch the three bytes and parse and return the proper values to the master. The issue with this version is if the master starts a transfer but hangs up before the 8th bit is received our code hangs.
If I use this version of the ISR:
Code: |
#INT_ssp
void ssp_isr(void)
{
unsigned int8 dummy;
unsigned int8 packet_complete = 0;
Command_Byte = spi_read(); // slave must send the sync byte as 1st byte
Tick = 2; // set the timer - 16.66ms per tick
do {
if(spi_data_is_in()) {
Data_Byte = spi_read(Command_Byte); // slave must echo the command byte back to master
packet_complete = 1;
}
else {
packet_complete = 0;
}
} while(Tick == 2 && !packet_complete);
...parse code is here....
}
|
This version won't read all three bytes. It ends up reading the 2nd byte twice. If the master sends 0xA0, 0x01, 0x00 the slave code above will return 0x5A (sync byte), 0xA0 (echos back the command byte), but then it also gets 0xA0 again. The proper operation is the slave sends 0x5A, then echos the command byte and then echos the data byte.
Can anybody see what I've screwed up?
Thanks again,
Richard |
|
|
|