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

Why is my TXBE (Buffer Empty) flag not clearing?

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
SkeeterHawk



Joined: 16 Oct 2010
Posts: 52
Location: Florissant, CO

View user's profile Send private message Visit poster's website

Why is my TXBE (Buffer Empty) flag not clearing?
PostPosted: Sun Apr 06, 2025 3:22 pm     Reply with quote

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

View user's profile Send private message Visit poster's website

Still Testing...
PostPosted: Mon Apr 07, 2025 12:08 pm     Reply with quote

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
Code:
00180:  CLRF   51B
, 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

View user's profile Send private message

PostPosted: Mon Apr 07, 2025 2:33 pm     Reply with quote

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

View user's profile Send private message Visit poster's website

PostPosted: Mon Apr 07, 2025 10:07 pm     Reply with quote

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().

Embarassed 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

View user's profile Send private message

PostPosted: Tue Apr 08, 2025 6:56 am     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Apr 08, 2025 10:30 am     Reply with quote

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.
Very Happy
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

View user's profile Send private message Visit poster's website

PostPosted: Tue Apr 08, 2025 12:20 pm     Reply with quote

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.
Very Happy
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 Wink
_________________
Life is too short to only write code in assembly...
SkeeterHawk



Joined: 16 Oct 2010
Posts: 52
Location: Florissant, CO

View user's profile Send private message Visit poster's website

Reply from CCS Support
PostPosted: Tue Apr 08, 2025 3:08 pm     Reply with quote

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...
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
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