[MC68HC908QB4] Continuing SPI Woes

cancel
Showing results for 
Search instead for 
Did you mean: 

[MC68HC908QB4] Continuing SPI Woes

8,801 Views
admin
Specialist II
I cotinue having difficulties implementing my SPI app on the -908QB4.  The data sheet (attached) mentions "double-buffering" transmit data for the SPI (in sec. 15.3.4 if anybody's interested).  This would be really cool if I could get it to work, as I have to complete at total of two 16-bit read cycles in under 333 us.
 
What I observed was this:
 
I tried to use the nifty-keen double-buffering transmit by writing my first byte to the SPDR, then sitting in a loop waiting for the SPTE (transmit empty) flag to be set, then shoving my second byte into the SPDR.  Then I have the receive-full interrupt set up to begin my second read-cycle.
 
When stepping through the code, after I put the first byte into the SPDR, the SPRF flags sets immediatly, but no matter what the next line of code is, the SPRF clears without my Rx interrupt being serviced.
 
I pondered this for awhile before hitting on an idea - I don't really care with that first byte of data clocked in from my slave device is, because it's garbage to me, I just care what the second byte of Rx'd data is.  I restructured my Rx interrupt around this theory, and it works great, exept that the overflow (OVRF) flag is set, indicating a Rx overflow, and my second byte of data is lost - even though the Rx interrupt was never serviced to take care of the first byte!
 
Help me Freescale Forums, you're my only hope!
 
Message Edited by t.dowe on 2009-09-22 11:56 AM
Labels (1)
0 Kudos
21 Replies

407 Views
rocco
Senior Contributor II
Hi, Tomahawk:

I think the problem may be related to the fact that you are single stepping through the code.

When stepping through the code, after I put the first byte into the SPDR, the SPRF flags sets immediatly . . .
I would bet that it doesn't set imediatly, but that it is setting after the first transfer takes place. It appears imediate because you are stepping.

. . . but no matter what the next line of code is, the SPRF clears without my Rx interrupt being serviced.
I suspect that, because you are stepping, the debugger is reading the registers in order to display them, and that is clearing the receive flag.

What I would do is this:

Load the first byte into the SPDR.
Wait for SPTE, and then load the second byte into the SPDR.
Now wait for the SPRF bit to set from the first byte, and read SPDR to fetch the data.
Wait for the SPRF bit to set from the second byte, and read SPDR to get that data.

But if you try to step through this routine, you will get an overflow. You cannot breakpoint between the time that you load the second transmitted byte into the SPDR until after you have read the first received byte from the SPDR.

hope that helps.
0 Kudos

407 Views
admin
Specialist II
I finally got back to my problem.  I had the code set a port pin when it thinks it receives the first byte, then clear it when it receives the second byte.  I ran the code (without stepping), and I never set the port pin.  This tells me that I'm not getting the first receive interrupt.
 
Has anyone used the "double-buffered" sheme successfully?
 
Thanks,
Tomahawk
0 Kudos

407 Views
rocco
Senior Contributor II
Hi, Tomahawk:

Assuming that you are setting and clearing your 'debug' pin within the ISR, it doesn't quite mean that the interrupt isn't occurring, it means that the interrupt isn't getting into the ISR.

I use a very similar double buffered approach to interface the SPI to a digi-pot (sixteen bit). I have also used the SPI for inter-HC08 communications, for communicating with FPGAs, interfacing with LCDs and driving long chains of shift-registers. So I can attest that it works exactly as the manual states (if the manual is correct :smileywink: ).

Can you post the code and have us look at it?
0 Kudos

407 Views
admin
Specialist II
Code posted as requested.
0 Kudos

407 Views
bigmac
Specialist III

Hello Tomahawk,

It is not clear what baud rate divisor you are using, but I suspect you would be using either 2 or 8.  If this is so, I think that it is inappropriate to be using the SPI interrupt.  Here is the reason -

  1. Assuming a divisor value of 2, each SPI transaction will take 16 bus cycles for the sending and receiving of one byte.
  2. The interrupt handler would have a minimum overhead of 20 bus cycles in assembler, and may be more in C.  This is the cycles required by the ISR in addition to the actual processing cycles.  By default, nested interrupts do not occur, and should be avoided if possible.
  3. Your ISR seems rather complex, and will probably require many cycles to complete, maybe the period of several SPI transactions.  I note that you have included while loops within the ISR, probably not a good idea.

In this case, there would be insufficient time to process several bytes, so later received data would be missed, particularly with two bytes in the SPI send buffer. 

The solutions -

  1. Don't use the double buffering capability so that delays within the ISR are less critical - it does not save any bus cycles anyway, or
  2. Reduce the baud rate to delay the completion of the second byte until after the ISR has completed, or
  3. Don't use interrupts for the SPI (and only send one byte at a time in case there is a timer, or other interrupt that commences just prior to the SPI receive flag beihg set).

I would probably opt to not use interrupts - it would ultimately be quicker and simpler.  For master operation, the SPI interurrupt is really only useful for slow baud rates where other useful processing can occur during the SPI transaction.

You can easily test whether this is the cause of your problem by reducing the baud rate, and seeing if data is still missed.

Regards,
Mac

 

Message Edited by bigmac on 2006-08-23 10:05 PM

0 Kudos

407 Views
admin
Specialist II


bigmac wrote:

Hello Tomahawk,

It is not clear what baud rate divisor you are using, but I suspect you would be using either 2 or 8.  If this is so, I think that it is inappropriate to be using the SPI interrupt.  Here is the reason -

[snip]

I would probably opt to not use interrupts - it would ultimately be quicker and simpler.  For master operation, the SPI interurrupt is really only useful for slow baud rates where other useful processing can occur during the SPI transaction.

You can easily test whether this is the cause of your problem by reducing the baud rate, and seeing if data is still missed.

Regards,
Mac

 

Message Edited by bigmac on 2006-08-23 10:05 PM


Mac,

I am using a baud rate divisor of 2.  I will try out a couple of things you suggest.

Thanks,

Tomahawk

0 Kudos

407 Views
rocco
Senior Contributor II
Hi, Tomahawk:

I totally agree with Mac that you don't need interrupts, and that you could probably get it done faster with in-line code, and a clock-divider of 2 (assuming the C code is fast enough).

To do this double buffered, you just need to think of the transmitter as being two bytes ahead of the receiver. Here is an outline:

1) Write a byte to the transmitter.
2) Wait for transmitter-empty.
---
3) Write a another byte to the transmitter.
4) Wait for receiver-full.
5) Read the byte from the receiver.
6) Loop back to step-3.

Note that you only need to check for transmitter-empty when you start double buffering.

The transmitter will be an extra byte ahead, so you will need to skip step-3, and go to step-4 instead, after you receive your second-to-last byte. In other words, you will get TWO received bytes after you write your last transmitter byte.

Hope that helps.
0 Kudos

407 Views
admin
Specialist II


rocco wrote:
Hi, Tomahawk:

I totally agree with Mac that you don't need interrupts, and that you could probably get it done faster with in-line code, and a clock-divider of 2 (assuming the C code is fast enough).

To do this double buffered, you just need to think of the transmitter as being two bytes ahead of the receiver. Here is an outline:

1) Write a byte to the transmitter.
2) Wait for transmitter-empty.
---
3) Write a another byte to the transmitter.
4) Wait for receiver-full.
5) Read the byte from the receiver.
6) Loop back to step-3.

Note that you only need to check for transmitter-empty when you start double buffering.

The transmitter will be an extra byte ahead, so you will need to skip step-3, and go to step-4 instead, after you receive your second-to-last byte. In other words, you will get TWO received bytes after you write your last transmitter byte.

Hope that helps.

So, for a two-byte transaction I should:

1) Write a byte to the transmitter.
2) Wait for transmitter-empty.
---
3) Write a another byte to the transmitter.
4) Wait for receiver-full.
5) Read the byte from the receiver.
6) Wait for receiver-full again
7) Read the byte from the receiver again.

Right?

Thanks,
Tomahawk

0 Kudos

407 Views
rocco
Senior Contributor II
You got it !
0 Kudos

407 Views
rocco
Senior Contributor II
Hi, Tomahawk. I just thought I should add this warning:

Because of the buffering, even though you read those two bytes from the SPI after having sent two bytes, they were both sent by your external device before having received both of those bytes from you.

So if the two bytes you sent were a request for data from the external device, you would need to send further bytes in order to receive the requested data.

I hope that made sense . . .
0 Kudos

407 Views
rocco
Senior Contributor II
Hi, Tomahawk:

I have scanned everything, and have to say it is very easy to read. I have only looked at the below routine closely, and I have commented it. It may have the answer, but I'm not sure. I will look at the ISR next.
0 Kudos

407 Views
admin
Specialist II


rocco wrote:

I have scanned everything, and have to say it is very easy to read.


BTW, rocco, thanks for the compliment.  I try.  :smileywink:

In other news, my timing requirments have changed slightly, so I can probably do away with using interrupts.  So, even with my baud rate divisor of 2, can I use the double-buffered scheme, or should I just avoid it?

Thanks again,
Tomahawk

0 Kudos

407 Views
bigmac
Specialist III

Hello Tomahawk,

The double buffering could probably be utilised provided you disable interrupts for the duration of the SPI transaction.  However, as I have previously said, the total number of cycles would not be significantly reduced because you still need to receive the two bytes before the read process is complete.

Regards,
Mac

 

Message Edited by bigmac on 2006-08-24 02:36 AM

0 Kudos

407 Views
bigmac
Specialist III

Hello Tomahawk,

I think I should clarify my .last post -


bigmac wrote:

The double buffering could probably be utilised provided you disable interrupts for the duration of the SPI transaction.  However, as I have previously said, the total number of cycles would not be significantly reduced because you still need to receive the two bytes before the read process is complete.


I meant that all interrupts would need to be disabled using the in-line assembly instruction sei , and then re-enabled using cli instruction when the transactions are complete.  I had actually assumed that you would not  be using an ISR for the SPI.

The reason is that, once the second character is loaded to the send buffer you have no control over when the second transaction occurs, and you have a 16 cycle window to read the first byte from the receive buffer, before the second byte is received.  So you cannot afford for any interrupt to delay reading the first byte.  This critical timing occurs because there is double buffering for send, but not for receive.

If you send only a single byte at a time (you wait until the first byte is read before commencing the second byte) the timing becomes non-critical, so you would not need to disable interrupts.  The timing penalty would only be the few cycles necessary to write the second byte to the send buffer.

If your data acquisition requirements are time critical to this extent, there may be more advantage to use in-line assembly for all SPI operations, rather than to utilise the double buffered send.

Regards,
Mac


 

0 Kudos

407 Views
rocco
Senior Contributor II
Mac is right, if ANY interrupt occurs during the double-buffered transfer, the second byte will be lost. But both solutions that Mac pointed out will work.

The real advantage of the double-buffered SPI is that it allows for contiguous transfers, meaning the SCLK line has a constant frequency over any number of bytes. A nice clean clock for devices that require it. Though it also improves throughput, the improvement is not necessarily significant.

If the speed of communication with the external device is paramount, or if no other interrupts are enabled, then I would leave it double-buffered. But I would also bracket the routine with an SEI and CLI, as Mac said, to prevent any interruptions.

If a few lost cycles between bytes won't hurt, then the single-buffer approach would be much safer. An interrupt during the transfer would cause the delay between bytes to be extended, but it won't cause you to loose any data.

Here is the sequence for a single-buffer approach. Notice that it is also a little simpler:

1) Write a byte to the transmitter.
2) Wait for receiver-full.
3) Read the byte from the receiver.
4) Loop back to step-1.

Or for a two-byte transaction:

1) Write a byte to the transmitter.
2) Wait for receiver-full.
3) Read the byte from the receiver.
4) Write a another byte to the transmitter.
5) Wait for receiver-full again.
6) Read the byte from the receiver again.
0 Kudos

407 Views
admin
Specialist II
Guys,
 
My two-byte transfers conisis of 1) sending the register address I want the data from, and 2) receiving the byte of data from the register specified in (1).
 
I got the single-buffered solution to work just fine, but I wanted to squeeze some more performance out of the reading process (since I need to do a total of six two-byte transfers at a time).  I wish the data sheet would have pointed out the potential for interrupt-induced loss :smileysad: .  I'll play around a little with disabling interrupts and see what happens.
 
Thanks guys,
Tomahawk
0 Kudos

407 Views
admin
Specialist II
I've decided to just go with the single-buffered solution:  it's cleaner, easier, and I optomized my code enough to make the timing acceptable for my SPI transfers.  Thanks for everyone's help.
 
-Tomahawk
0 Kudos

407 Views
admin
Specialist II


bigmac wrote:

Hello Tomahawk,

The double buffering could probably be utilised provided you disable interrupts for the duration of the SPI transaction.  However, as I have previously said, the total number of cycles would not be significantly reduced because you still need to receive the two bytes before the read process is complete.

Regards,
Mac

 

Message Edited by bigmac on 2006-08-24 02:36 AM



Mac,

I have no problem completely disabling SPI interrupts and doing everything from main code.  The slave part I'm communicating with requires 16 bit (two byte) transactions anyway, so I believe I will get some benefit out of using the double-buffered scheme.  The problem is how to do it in C.  Any suggestions/code snippets/etc.?

Thanks,
Tomahawk

0 Kudos

407 Views
admin
Specialist II


rocco wrote:
Hi, Tomahawk:

I have scanned everything, and have to say it is very easy to read. I have only looked at the below routine closely, and I have commented it. It may have the answer, but I'm not sure. I will look at the ISR next.


rocco,

Thanks for looking at that routine, but the I don't really care about the about the init routine, it's the regular read routines where I want to make use of the double-buffering.

-Tomahawk

0 Kudos

407 Views
RedMountain
Contributor I
After your code adjustment you're having problems with the overflow bit being set, and you're not seeing the second byte. Section 15.3.6.1 talks about overflow. The SPI is capturing 8 bits and then it waits for you to get them. If another 8 bits are read on the SPI before you read the first, those bits are lost, and the MCU sets the overflow flag to let you know that you don't have all of the information that was sent. To fix this, just read the contents of the SPI Data Register after the first byte is recieved, and write them to a temporary register - I'd just write them to the value that you'll be storing the second byte in as soon as you recieve it. This way, the recieve register will be cleared (along with the flag indicating that the receive register is full, SPRF), and the second byte will (should) be properly captured for access.
0 Kudos