 |
 |
View previous topic :: View next topic |
Author |
Message |
SkeeterHawk
Joined: 16 Oct 2010 Posts: 52 Location: Florissant, CO
|
Why is my TXBE (Buffer Empty) flag not clearing? |
Posted: Sun Apr 06, 2025 3:22 pm |
|
|
I've been "beating this horse" for far too long and it has generated a few threads on this forum as I have been working through this issue, but I am not sure where to go from here.
I have been trying to use the hardware SPI port. Sounds simple, right? I've had numerous issues, and I'll take credit for those, but I have no clue what to do now. My Slave SPI port is still just echoing the last byte received, and it is setting the TXUIF (Transmit Underflow Interrupt) bit.
Quick background, I am using a PIC18F57Q84 on compiler version 5.114.
So, my TXBE (Buffer Empty) flag is set initially, which is understood. I am using the #use spi statement along with the spi_xfer() function to transfer the data. Here is the initialization:
Code: | #include <18F57Q84.h>
#use delay(internal=32000000)
#pin_select SS1IN=PIN_D0
#pin_select SDO1=PIN_C2
#pin_select SCK1IN=PIN_C3
#pin_select SDI1=PIN_C4
#use spi (STREAM = SPI, SLAVE, SPI1, MODE=3)
// SPI Bit assignments
#define Bit_SPI_Mask_Read 0b10000000 // Bit that gets set when this is a Read operation
#bit SSP = getenv("SFR:SPI1CON1").2 // Assign SSP to Bit #2 of the SSP1CON1
SSP=1;
|
Here is part of the code that is calling the transfer function:
Code: | void CheckPumpCommunication(void)
{
int8 AddressIn, // Register to hold the address byte that the Pump Master is sending
MaskedAddress; // We mask the R/W bit, and then use this register for the basic address
if(input(PumpMaster_CS)) // Check to see if the Pump Master is asserting our Chip Select input, and if it is high (not asserted) return back to the calling program
// if(SOSIF != 0) // Check the Start of Client Select bit is set, and if it is not, head back and wait some more
{
return; // Nope, no chip select here, so head on back
}
AddressIn = spi_xfer(SPI, 0xAA, 8); // Load the address register with the data that is on the SPI bus so we can see where we go from here
MaskedAddress = (AddressIn & (0xFF ^ Bit_SPI_Mask_Read)); // Mask the MSB of the address to nullify the R/W bit so we can see what the root address is
switch(MaskedAddress)
{
case 1: // There is no case "0" because that is the present/not-preent bit that is kept local to the Main Pump Board, so we are starting with the Valve Set Point byte
if ((AddressIn & Bit_SPI_Mask_Read) > 0) // Check to see if the MSB is set, meaning that this is a Read operation
{
spi_xfer(SPI, Reg_ValveSP, 8); // Send the current valve setpoint over to the Pump Master Module
}
else // If the MSBit is not set, this must be a Write operation
{
Reg_ValveSP = spi_xfer(SPI, 0xAA, 8); // Read the register that the Pump Master wants to write to the current valve setpoint
SetUpValves(); // Call the routine to set the valves to the current state
}
break; |
Here is the assembly generated for the first call to get the address:
Code: | .................... void CheckPumpCommunication(void)
.................... {
.................... int8 AddressIn, // Register to hold the address byte that the Pump Master is sending
.................... MaskedAddress; // We mask the R/W bit, and then use this register for the basic address
....................
.................... if(input(PumpMaster_CS)) // Check to see if the Pump Master is asserting our Chip Select input, and if it is high (not asserted) return back to the calling program
*
00174: BSF 4C9.0
00176: BTFSS 4D1.0
00178: BRA 017E
.................... // if(SOSIF != 0) // Check the Start of Client Select bit is set, and if it is not, head back and wait some more
.................... {
.................... return; // Nope, no chip select here, so head on back
0017A: GOTO 09A2
.................... }
.................... SOSIF = 0; // Since it was set, clear it now so that we don't get stuck in here
0017E: BCF x8A.5
.................... AddressIn = spi_xfer(SPI, 0xAA, 8); // Load the address register with the data that is on the SPI bus so we can see where we go from here
00180: CLRF 51B
00182: MOVLW AA
00184: MOVLB 5
00186: MOVWF x64
00188: MOVLB 0
0018A: RCALL 00B2
0018C: MOVF 501,W
0018E: MOVFF 500,562 |
Here is the spi_xfer() function in assembly
Code: | 000B2: MOVF 51B,F
000B4: CLRF 51B
000B6: BZ 00BE
000B8: MOVF x80,W
000BA: MOVFF 564,81
000BE: BTFSS 4B1.0
000C0: BRA 00BE
000C2: MOVFF 80,500
000C6: RETURN 0 |
What is confusing me is that you can clearly see that on line 000BA, I am moving register 564 to register 81, and 81 is the SPI1TXB...this transmit FIFO, yet the buffer empty bit is staying set, so I won't output the data that is in 564, but will echo the address that I just received and then set the underflow interrupt bit.
Originally, I had 0x00 as the data to write to the Master, but thought that may be the same as a "buffer empty", so I started writing it with 0xAA t0 make sure there was some "real" data in the register. This still isn't working. The TXBE bit is read-only, so I can't just write to it and keep going.
Since this isn't a HUGE problem for the CCS compiler, I am obviously missing something fundamental. The buffer is already empty, so I shouldn't have to toggle the CLB (Clear Buffer Control) bit. I am honestly not sure where to go from here. In the past I have written my own SPI routines to bit-bang the protocol, and this worked great. My current application has a LOT of through-put on the SPI port, and I have a half-dozen slaves, so I am trying to take advantage of the built in hardware port. Does anyone have any suggestions as to what I should try next? Thanks so much for your time!! _________________ Life is too short to only write code in assembly... |
|
 |
SkeeterHawk
Joined: 16 Oct 2010 Posts: 52 Location: Florissant, CO
|
Still Testing... |
Posted: Mon Apr 07, 2025 12:08 pm |
|
|
Hello again, fellow programmers.
Still investigating this issue. I'm about to give up on the #use spi and spi_xfer() method of doing this and go back to the setup_spi() command or write my own again if I can't get that method to work either.
Here is what I have found, and I will contact CCS with this data.
In my Symbol file, address 51B is for the Code: | 51B-51E @SPIPREWRITE_1 | . If you look at the assembly code that I posted earlier, the initial call for spi_xfer command starts out with , clearing that register. Nothing ever gets written to that register before it makes the call to spi_xfer.
Here is the assembly for that routine again:
Code: | 000B2: MOVF 51B,F
000B4: CLRF 51B
000B6: BZ 00BE
000B8: MOVF x80,W
000BA: MOVFF 564,81
000BE: BTFSS 4B1.0
000C0: BRA 00BE
000C2: MOVFF 80,500
000C6: RETURN 0 |
As soon as it enters that routine, it moves 51B through the ALU to see if it is zero. Well, it is, so when it hits the BZ statement at address 000B6, the zero bit is set, so it jumps right over the write to the transmit register and starts waiting for the receive interrupt.
I don't think that there is anything that I am doing wrong, the compiler is generating bogus code that won't work.
That is my assessment. If you see that I AM doing something wrong PLEASE enlighten me!! I am going to take this to CCS support. _________________ Life is too short to only write code in assembly... |
|
 |
jeremiah
Joined: 20 Jul 2010 Posts: 1383
|
|
Posted: Mon Apr 07, 2025 2:33 pm |
|
|
In the past when I did slave/client SPI, I used an interrupt to do it (don't see the #INT_XXX for that in your example), and in my interrupt handler I used spi_xfer_in() to read the data and spi_prewrite() to write my next value. I've never tried with plain spi_xfer() unless I am doing a master/host mode transaction (but never in slave/client mode).
I took a look at the example provided by CCS with your compiler (examples/ex_spi_slave.c) and it also uses the interrupt method with spi_xfer_in() and spi_prewrite().
I would recommend trying those options. You might even start with just using the CCS example as your slave program and see if it works mostly out of the box (prefill the memory[] array with some known values to check on the master side to see if they are returned and take note of the predefined commands in their example, you can mod those though if you want). If you can get a simple example working, then you can maybe translate that to your program.
EDIT: I don't have your chip to test with, but you might try something even simpler for you slave/client device (just for testing):
Code: |
#case
#include <18F57Q84.h>
#use delay(internal=32000000)
#pin_select SS1IN=PIN_D0
#pin_select SDO1=PIN_C2
#pin_select SCK1IN=PIN_C3
#pin_select SDI1=PIN_C4
#use spi (STREAM = SPI, SLAVE, SPI1, MODE=3)
#define PUMP_MASTER_CS PIN_D0
#int_spi1rx
void spi1rx_isr(){
unsigned int8 data = spi_xfer_in(SPI); // read in value that initiated ISR
if(!input_state(PUMP_MASTER_CS)){ // Assuming your CS goes low when active
spi_prewrite(SPI, data); // write the previous value out (junk on the very first byte)
}
}
void main(){
enable_interrupts(INT_SPI1RX);
enable_interrupts(GLOBAL);
while(TRUE);
}
|
It'll just echo the last byte received (previous transaction) so you can check that on the master/host side. The first returned byte will be junk since there was no previous transaction.
Sorry I don't have better clues. I primarily work with PIC24's which have a much different ASM output than PIC18s |
|
 |
SkeeterHawk
Joined: 16 Oct 2010 Posts: 52 Location: Florissant, CO
|
|
Posted: Mon Apr 07, 2025 10:07 pm |
|
|
jeremiah wrote: | In the past when I did slave/client SPI, I used an interrupt to do it (don't see the #INT_XXX for that in your example), |
You are right. I got a response from the CCS mentioning that same fact. I just avoided it because I have a huge switch statement after the first reception. My option (I guess) is just to set a flag in the interrupt handler and test for it in the Main and assume the FIFO buffer will handle the reception of the address while I am shifting over to receive the byte. They are referring me to their SPI expert at CCS Support and he is supposed to get back with me tomorrow.
jeremiah wrote: | and in my interrupt handler I used spi_xfer_in() to read the data and spi_prewrite() to write my next value. I've never tried with plain spi_xfer() unless I am doing a master/host mode transaction (but never in slave/client mode). |
In all the CCS documentation I have read, not one even mentioned those functions. If you look at the #use spi pre-processor...not even there. It does list spi_transfer_write (for example) and I am pretty sure that I have read Ttelmah warn against using those commands with #use spi. I am quite excited to know that these commands exist, so this gives me some place to go. I am not using full-duplex communication, so this would be fantastic if it works!!! Thanks again for the pro-tip!!
jeremiah wrote: | I took a look at the example provided by CCS with your compiler (examples/ex_spi_slave.c) and it also uses the interrupt method with spi_xfer_in() and spi_prewrite(). |
My goodness, but you are absolutely right. I had that example open in my project already, but I never noticed that it used those commands instead of the plain spi_xfer() that they adamantly recommend within the documentation. The problem I am having is with the compiled code generated by the spi_xfer() command. Again, I am embarrassed that I didn't notice the use of those commands within the example file, and I sincerely appreciate your pointing that out to me.
jeremiah wrote: | I would recommend trying those options. You might even start with just using the CCS example as your slave program and see if it works mostly out of the box (prefill the memory[] array with some known values to check on the master side to see if they are returned and take note of the predefined commands in their example, you can mod those though if you want). If you can get a simple example working, then you can maybe translate that to your program. |
Great advice, Jeremiah.
jeremiah wrote: | EDIT: I don't have your chip to test with, but you might try something even simpler for you slave/client device (just for testing): |
Thank you for the code example.
jeremiah wrote: | It'll just echo the last byte received (previous transaction) so you can check that on the master/host side. The first returned byte will be junk since there was no previous transaction. |
OK, I must say that this whole statement resonated with me. I was under the impression that it would only return the last byte received if the Buffer Empty bit was set...which is what is happening to me. Are you just referring to the data that is "transmitted" when a slave is receiving? I am in charge of both the Master and Slave side of this bus, so I can throw out an erroneous byte if I need to, but I would like to understand better what you are saying here. I would appreciate the explanation.
jeremiah wrote: | Sorry I don't have better clues. I primarily work with PIC24's which have a much different ASM output than PIC18s |
You have been SUPER helpful, Jeremiah. I sincerely appreciate your time to get through this. Have a wonderful evening, and I will update this post with what I find along with what the CCS guy says tomorrow. _________________ Life is too short to only write code in assembly... |
|
 |
jeremiah
Joined: 20 Jul 2010 Posts: 1383
|
|
Posted: Tue Apr 08, 2025 6:56 am |
|
|
SkeeterHawk wrote: |
jeremiah wrote: | and in my interrupt handler I used spi_xfer_in() to read the data and spi_prewrite() to write my next value. I've never tried with plain spi_xfer() unless I am doing a master/host mode transaction (but never in slave/client mode). |
In all the CCS documentation I have read, not one even mentioned those functions. If you look at the #use spi pre-processor...not even there. It does list spi_transfer_write (for example) and I am pretty sure that I have read Ttelmah warn against using those commands with #use spi. I am quite excited to know that these commands exist, so this gives me some place to go. I am not using full-duplex communication, so this would be fantastic if it works!!! Thanks again for the pro-tip!!
|
I don't have an exhaustive list, but in general the rule of thumb is not to mix the original spi read/write commands (spi_write, spi_read, etc, any function that doesn't take a "stream" parameter) with #use spi(). One of the reasons for this is that the #use statement does some extra setup / configuration that you don't get or leverage with the original commands. So you can get weird scenarios where some of the commands don't work the way you expect. This often applies to other peripherals too. You don't generally mix non #use operations with #use operations, unless you really really know what you are doing (in reference to knowledge of how the compiler generates the code), and even then you take the risk that the next compiler update could change how they interact if CCS adds a new feature to the #use version.
SkeeterHawk wrote: |
jeremiah wrote: | It'll just echo the last byte received (previous transaction) so you can check that on the master/host side. The first returned byte will be junk since there was no previous transaction. |
OK, I must say that this whole statement resonated with me. I was under the impression that it would only return the last byte received if the Buffer Empty bit was set...which is what is happening to me. Are you just referring to the data that is "transmitted" when a slave is receiving? I am in charge of both the Master and Slave side of this bus, so I can throw out an erroneous byte if I need to, but I would like to understand better what you are saying here. I would appreciate the explanation.
|
Sorry for the confusion, that statement was more about the "C logic" of the code. What you are referring to I think is the default operation of the hardware if you try to read when there is no data in the fifo? My intent was that since this is in the interrupt handler you by default know you have data in the fifo when that code runs, so the scenario where you are reading from an empty fifo "shouldn't" occur (though note I am not familiar with this chip, so I could be offbase there potentially).
In the C logic, I always read the buffer (since the interrupt triggers only when there is data there). If the CS is high (not asserted I am assuming), then that data is thrown away (but sometimes some chips require reading that data anyways to avoid hardware triggered interrupts...don't know if this is the case with your chip or not, but I am doing it for safety).
Then if the CS is instead low, I put that value I just read off the RX fifo onto the outgoing TX fifo (using the spi_prewrite() function) so that the next time a byte is received, the previous byte I pulled out is sent back to the master. Sort of a delayed echo test code. I am not relying on the default hardware operation though because the interrupt handler is supposed to only fire when there is data in the fifo (if I understand correctly).
Hopefully that clarifies my thoughts some. |
|
 |
Ttelmah
Joined: 11 Mar 2010 Posts: 19769
|
|
Posted: Tue Apr 08, 2025 10:30 am |
|
|
I'm on holiday at the moment, so don't have much internet access.
However the warning about using spi_zfer_in/out, was for a specific chip.
They behave very differently on chips with an SPI peripheral to those
with just an MSSP.
These functions are very different from the xpi_xfer function. They
attempt to encapsulate the whole transfer, and (for example), if you
use 'enable', will operate this for the whole transfer.
These are an area where CCS could do with improving the documentation
massively. I have suggested this to them, and suggest others do the same.
They really do need to give more details about how the functions behave
with the different peripherals, as well as details of how the options
function. |
|
 |
SkeeterHawk
Joined: 16 Oct 2010 Posts: 52 Location: Florissant, CO
|
|
Posted: Tue Apr 08, 2025 12:20 pm |
|
|
Ttelmah wrote: | I'm on holiday at the moment, so don't have much internet access. |
Thanks for checking in while you are on holiday. Enjoy your time away!!
Ttelmah wrote: | These are an area where CCS could do with improving the documentation massively. I have suggested this to them, and suggest others do the same.
They really do need to give more details about how the functions behave
with the different peripherals, as well as details of how the options
function. |
I couldn't agree more. I think I will start a new thread in hopes to bring all this information together, and then we can hand it to CCS on a platter  _________________ Life is too short to only write code in assembly... |
|
 |
SkeeterHawk
Joined: 16 Oct 2010 Posts: 52 Location: Florissant, CO
|
Reply from CCS Support |
Posted: Tue Apr 08, 2025 3:08 pm |
|
|
I got word back from CCS Support that there is indeed an issue with the spi_xfer() function when the PIC is a Slave. Here is what he had to say and the work-around he proposed for me:
Quote: | I've done some testing and I have found issues with the spi_xfer() function when the SPI is setup for slave mode. I'm working on correcting it until then you can work around it as follows:
Assign pins and use the following #use spi() statement:
#pin_select SS1IN=PIN_D0
#pin_select SDO1=PIN_C2
#pin_select SCK1IN=PIN_C3
#pin_select SDI1=PIN_C4
#use spi(STREAM = SPI, SLAVE, SPI1, enable=PIN_D0, MODE=3, bits=8)
Then for each byte of data that you want to read and write use the spi_prewrite() and spi_xfer_in() functions to do the transfers:
spi_prewrite(SPI, data);
result = spi_xfer_in(SPI, 8);
The spi_prewrite() function loads the byte you want to send to the master and the spi_xfer_in() waits for the master to clock the byte to/from the slave and then returns the value transferred to it from the master.
Richard
CCS Support |
Note that we are using the Enable pin in the #use spi preprocessor and that the transactions are now limited to 8-bits. _________________ Life is too short to only write code in assembly... |
|
 |
|
|
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
|