CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

SPI slave with timeout?
Goto page Previous  1, 2
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
Ttelmah



Joined: 11 Mar 2010
Posts: 19538

View user's profile Send private message

PostPosted: Thu Aug 25, 2011 2:39 am     Reply with quote

I must admit I prefer to use true/false, to 1/0. Smile
Makes the logic of the loop much easier to understand.

It is not obvious from the code, what 'tick' represents. You say 16.6mSec/tick. Is this the actual _hardware_ register for a timer. It needs to be. Remember you are inside an interrupt, so if a code 'tick', is being used, it won't get updated.
Now, slightly puzzled. You talk about three bytes, but the two code routines you show, only transfer two bytes. There would need to be a third SPI_READ somewhere in the original routine. You don't show this, and have not duplicated it in the new code.

Best Wishes
RF_Developer



Joined: 07 Feb 2011
Posts: 839

View user's profile Send private message

PostPosted: Thu Aug 25, 2011 6:43 am     Reply with quote

If you are providing an SPI slave you specify the maximum clock rate. Its has to be that way. Its that way with SPI ICs which implement everything in hardware. If your customer won't let you specify a max speed then you are in trouble. Shocked

The problem is that any SPI slave MUST be able to format the next byte it will send inside of one half of a SPI clock cycle. In short it must be ready by the time the master samples the first bit if the next byte. The slave has no control of clock rate and the master is free to send all its bytes at full speed with NO inter-byte gap whatsoever. If you really cannot send stuff back in time you have to change the format of the messages to give you time, for example by adding a padding (blank or don't care) byte to give you time to get the data ready. Older ADCs in particular were fond of that trick, or treated the SPI message as a continuous bit stream and added as many padding bits as it needed, or conversely allow extra unused bits at the end, generally to pad the message out to a multiple of 8 bits. Note that SPI is not defined as a byte oreintated interface, but it is usually used that way. The PIC certainly does. Unfortunately SPI is not *defined* at all! Its always been up to the implmentor of the master end to sort out timing suitable for the slave, and it really has to be if you think about it.

That said, I'm a little confused about this code. With slave SPI you have to have your byte(s) to send ready in advance, BEFORE they are likely to be read by the master. So I'm assuming you've already written your SYNC byte to the SPI port. I'd always do that immediately AFTER receiving a message so that the SPI is ready to receive more at all times. I'd use the interrupt but you have to remember that interrupt latency is not 0us. It might be that at fastish SPI speeds the next byte has been and gone before the PIC has had a chance to run the ISR for the first! So, assuming that your PIC can respond quickly enough (and with a software implementation such as this I'd expect your max SPI clock to be in the order of kHz, not MHz) and you've already written your sync byte then when you read the first byte in you write the next byte out. I think you already do this, but it has to be done before the next byte comes in, otherwise its simply too late. Timing is critical and yet you don't control the SPI clock!

I'd count the bytes as we go, and write out the sync after I'd received rthe final byte. I'd only handle one byte in each run of the ISR. Its too easy to mess up the interrupts if you don't.

RF Developer
rcooke



Joined: 23 Feb 2011
Posts: 21
Location: Oceanside, CA USA

View user's profile Send private message

PostPosted: Thu Aug 25, 2011 9:13 am     Reply with quote

I apologize for not giving a clear explanation of just what our SPI slave system has to do. The master sends a "command byte" and the slave has to return a "sync byte" of 0X5A. The master then sends a "data byte" and the slave echoes back the command byte. The master sends a dummy byte so the slave can send back the contents of the register pointed to by the data byte.

I pre-load 0x5A into the SSP1BUF so answering the 1st byte isn't a problem.

My current implementation that works (but isn't pretty) is:
Code:

//****************************************************************************
// SLAVE SPI Interrupt
//****************************************************************************
#INT_ssp
void ssp_isr(void)
{
 unsigned int8 dummy;
 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
 switch(Command_Byte) {
      case 0xA0:                   // write new supply_control register value
          Supply_Control_Reg = Data_Byte;
          dummy = spi_read(Supply_Control_Reg);
          SPI_Data_Recd = SPI_Data_Recd | SUPPLY_CNTRL_REG; // keep track of which reg was written for later
          break;
      case 0xA1:                    // write new supply fault register value
          Supply_Fault_Reg = Data_Byte;
          dummy = spi_read(Supply_Fault_Reg);
          SPI_Data_Recd = SPI_Data_Recd | SUPPLY_FALT_REG; //  keep track of which reg was written for later
          break;
      case 0xA2:                    // write new RF amp fault register value
          RF_Amp_Fault_Reg = Data_Byte;
          dummy = spi_read(RF_Amp_Fault_Reg);
          SPI_Data_Recd = SPI_Data_Recd | RF_AMP_SPPLY_REG; // keep track of which reg was written for later
          break;
      case 0xA3:                    // write new fan fault register value
          Fan_Fault_Reg = Data_Byte;
          dummy = spi_read(Fan_Fault_Reg);
          SPI_Data_Recd = SPI_Data_Recd | FAN_FALT_REG;    // keep track of which reg was written for later
          break;
      case 0xA4:                    // read of temp #1 register - READ ONLY register
          dummy = spi_read(Temp_1_Reg);
          break;
      case 0xA5:                    // read of temp #2 register - READ ONLY register
          dummy = spi_read(Temp_2_Reg);
          break;
      case 0xA6:                    // write new fan #1 speed register   value
          Fan_1_Speed_Reg = Data_Byte;
          dummy = spi_read(Fan_1_Speed_Reg);
          SPI_Data_Recd = SPI_Data_Recd | FAN_1_CNTRL_REG;// keep track of which reg was written for later
          break;
      case 0xA7:                    // write new fan #2 speed register value
          Fan_2_Speed_Reg = Data_Byte;
          dummy = spi_read(Fan_2_Speed_Reg);
          SPI_Data_Recd = SPI_Data_Recd | FAN_2_CNTRL_REG;// keep track of which reg was written for later
          break;
      case 0xB0:                     // revision level of firmware
          dummy = spi_read(REVISION_LEVEL);
          break;   
      default:
          dummy = spi_read(0x00);
          break;
        } // end of switch(Command_Byte)
  SSP1BUF = 0x5a;  // pre-load SYNC byte into SPI buffer to allow response to COMMAND byte from SPI master     
}


It's not pretty or maybe even "proper" but it works. I just need to find a way to make it work with some sort of timeout so if the master hangs before the three bytes are received our system doesn't hang waiting for the bytes.

In answer to Ttelmah's question regarding the Tick. I've got Timer1 in an interrupt set up to increment Tick at a rate of 16ms.

Thanks,

Richard
Ttelmah



Joined: 11 Mar 2010
Posts: 19538

View user's profile Send private message

PostPosted: Thu Aug 25, 2011 9:26 am     Reply with quote

OK. Now we have the third read shown.
However you haven't answered about 'tick'.
Assuming tick _is_ a hardware register:
Code:

signed int16 spi_read_with_timeout(int8 val) {
  signed int16 temp=-1;
  int1 have_byte=FALSE;
  tick=2;
  do {
    if(spi_data_is_in()) {
       temp = spi_read(val);
       have_byte=TRUE;
  } while(Tick == 2 && !have_byte);
  return temp;
}

Then just substitute this for each SPI_READ, and test the return value. If the return value is -ve then there was a timeout. Otherwise continue.

Best Wishes
rcooke



Joined: 23 Feb 2011
Posts: 21
Location: Oceanside, CA USA

View user's profile Send private message

PostPosted: Thu Aug 25, 2011 10:24 am     Reply with quote

Ttelmah,

Here's my timer routine where I'm incrementing the Tick variable:
Code:

//****************************************************************************
// Timer 1 Interrupt - used to setup a timer tick every 16.384mS
//****************************************************************************
#int_timer1
void timer1_isr() {                         // ISR gets called every 16.384mS
    Tick++;
    if(Tick > TIMER1_ROLLOVER)              // Rolls over every 0.2458 sec
        Tick = 0;
} // end of timer1_isr()


Hope this makes sense.

Richard
RF_Developer



Joined: 07 Feb 2011
Posts: 839

View user's profile Send private message

PostPosted: Thu Aug 25, 2011 11:31 am     Reply with quote

My previous comments still apply. Let's work the numbers... Your PIC is running at 32MHz. That's 8Mips.

Your SPI runs at 1.25MHz. At worst that's a byte every 6.4us. You should be OK for the first two bytes. Its the third byte where things will potentially go wrong.

The interrupt occurs at the end of the first byte. You then MUST get the SPI ready to deal with the next byte before the master clocks it. That could be as little as a bit under one bit time, should be more especially if the master is byte by byte software. Less if its fully buffered (some ARMs have this on their SPIs), or is hardware, such as an FPGA (and yes, I have done such a thing). One bit is just .8us, or 6.4 instructions at 32MHz. Its enough time to simply bash a byte into the SPI send register but realistically not enough to do much else.

So your second byte goes out OK. Timing's tight, even at 32MHz, but it goes. Then you get to the third byte. You have more time for this. A little under one byte time. But you have to a) wait for the second byte to go out (which I don't think your code does yet...) AND b) select and format the required data item. You have just 51 instructions to play with. That's not a lot, especially when you've got a switch to execute, and CCS has a number of possible coding strategies for that. The fastest would be a jump table. but deriving the index from your range of cases, 0xA0 to 0xB0 with a big gap, is not going to take zero time, and it might not use that strategy. It might use an if-else ladder type construct which takes longer to get to later cases. Overall, we cannot know for sure how long it takes to get to your code. And then it has to execute it. If you are simply stuffing raw bytes from here to there you may be OK... just... sometimes.

If during all this, the master has started its byte then you're stuffed. Your SPI won't be ready with any data to send, and yes, you might well end up hanging as the master is ahead of you. It may well not be the master that hangs, but your code not being ready in time for the master. The master may send the last byte, but your PIC is not ready to receive it, and so... you know the rest.

The only realistic answer to this is to a) reduce the time to select and format data to an absolute minimum and b) run the SPI at a slow enough clock rate that you can guarantee to respond in time, every time, or c) get the master end to slug their sending by adding considerable inter-byte gaps. Though that may not be possible, or even politically/commercially acceptable.

RF Developer.
asmallri



Joined: 12 Aug 2004
Posts: 1635
Location: Perth, Australia

View user's profile Send private message Send e-mail Visit poster's website

PostPosted: Thu Aug 25, 2011 11:38 am     Reply with quote

Your "working" SPI code is flawed and is the reason the application hangs. After you enter the SPI interrupt handler, you then perform multiple SPI operations inside the handler. If the master has hung then so will the slave as it cannot exit the interrupt handler.

Use a state machine together with the interrupt handler. Enter the handler, perform a basic operation, initialize some activity count value (size of BYTE), update the state machine and exit. If the SPI state machine completes correctly set the activity time out value to 0;

Then in the timer interrupt handler if the activity count is not equal to zero then decrement the activity count. If, after decrementing, the value the result is 0 then the master has hung, disable the SPI interrupt, clear the activity count value, reset the state machine to the initial state, reenable the SPI interrupt.
_________________
Regards, Andrew

http://www.brushelectronics.com/software
Home of Ethernet, SD card and Encrypted Serial Bootloaders for PICs!!
Ttelmah



Joined: 11 Mar 2010
Posts: 19538

View user's profile Send private message

PostPosted: Thu Aug 25, 2011 2:45 pm     Reply with quote

Tick won't work when you are inside the interrupt.
You need to just use a hardware timer, as I originally outlined.

To RF_developer, the second byte could already have gone wrong. It'll hang at the read, if it isn't complete.

The point is the basic code is just reading the bytes without checking they are ready. This is fundamentally flawed (will fail if the bytes don't arrive), but is _fast_. It is simply fixed, by just using a hardware timer as a timeout, as already outlined, but it _needs_ to be a hardware timer, not a software timer (even if interrupt driven).
Look at what I wrote, and implement this. Don't use 'tick', use either the hardware timer register, or interrupt flag.

Best Wishes
rcooke



Joined: 23 Feb 2011
Posts: 21
Location: Oceanside, CA USA

View user's profile Send private message

PostPosted: Thu Aug 25, 2011 3:12 pm     Reply with quote

Ttelmah,

You've got me confused regarding the timer. I'm using Timer1's interrupt to increment my Tick variable at a rate of 16.3ms/tick.

I'm lost as to what you are suggesting. Can you give me an example of how you'd set it up?

Thanks again,

Richard
rcooke



Joined: 23 Feb 2011
Posts: 21
Location: Oceanside, CA USA

View user's profile Send private message

PostPosted: Sun Aug 28, 2011 12:37 pm     Reply with quote

If I have the Timer1 setup to trigger an interrupt when it rolls over will it still set the TMP1IF bit even if the ISR isn't called when I'm in the SPI interrupt?

I'm trying to find a way to create a timeout mechanism while I'm waiting for the SPI data to arrive.

Thanks,

Richard
Ttelmah



Joined: 11 Mar 2010
Posts: 19538

View user's profile Send private message

PostPosted: Sun Aug 28, 2011 2:45 pm     Reply with quote

Exactly. This is why I said to read the interrupt flag.

The _hardware_ increments the timer, and sets the flag when it rolls over, but it is the interrupt handler software that then does 'jobs' with this, like incrementing tick. This software won't be called, unless you use high priority interrupts (so the timer can interrupt the SPI), but this will cause problems, because the default behaviour is for the SPI data to arrive so fast, that there isn't time to get into another interrupt and out again. Hence you need to use the hardware, not a software tick.
Realistically I wouldn't use a timer you are already using for something else, since this will interfere with it's timings. Much better to have something like timer1, running as a 16 bit counter, and running directly off fosc/4 (so counting in instructions), and then (say) set this to 64536 (-1000), clear the interrupt flag, and do the wait for the SPI. The interrupt flag will then set in 1000 instruction times (adjust the timeout till it too quick, then increase by perhaps 50%, unless you can get a specified maximum 'between bytes' time from the manufacturer of the other unit).

Best Wishes
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Goto page Previous  1, 2
Page 2 of 2

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group