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 out of sync

 
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: 36
Location: Florissant, CO

View user's profile Send private message

SPI out of sync
PostPosted: Thu Mar 20, 2025 8:35 pm     Reply with quote

Hello everyone.

I have been working with the PIC for over 25 years now, so I feel a little silly asking such a basic question, but here I am. I have typically written my own SPI routines in the past, but I am trying to use the #use spi command for my current project because a lot of my transactions are longer (32 bits) and I like the flexibility of being able to call out the transaction length in the command.

Both my master and my slave are both PIC18F57Q84. I am using compiler version 5.114. I have about 10 devices on this SPI bus controlled by independent chip select lines.

My problem is that the reception of the data at the slave are out of sync, and I am wondering the best way to avoid this. Looking at an oscilloscope, the data coming from the Master is perfect. I was originally trying to run it at 2MHz, and at this time, the LSB of the Master packet would end up in the third bit of the upper nybble, so if I sent 0x81, it would be received as 0x40. Slowing the bus speed down to 2KHz shifted the bits over by one, so 0x81 would now be received as 0x02 consistently. The MSBit would get lost.

I could blindly add a delay in the Master code to wait for a few uS after asserting the CS before starting the transaction, but I feel that this may be either problematic or would add latency to my transactions. I considered adding a pull-up resistor to the MISO line so that it would idle high, and I could pull it low as the slave once the CS was pulled low to "handshake" like I2C does. These are both work-arounds. This is the first big application that I am trying to use the #use spi and the spi_xfer command. Is there something that I am missing???

Here is the (what I believe is) the pertinent code from the Master:
Code:
#pin_select SDO1=PIN_C1
#pin_select SCK1=PIN_C3
#pin_select SDI1=PIN_C4
#use spi (STREAM=SPI, MASTER, SPI1, BAUD=20000, MODE=3)

void ReadValveModuleRegister(int8 AddressInput)
{
   int8  RegisterAddress,                                                  //  Temp register to hold the address of the register that we are addressing
         InFromSPI8,                                                       //  Byte that we receive from the module
         NybbleToASCII,                                                    //  Byte to hold the nybble that we will want to convert to send to the module
         TempReg8;                                                         //  8-bit temp register in case that is what we are receiving

   int16 InFromSPI16;                                                      //  16-bit register that we received from the module

   switch(AddressInput)
   {
      case 1:                                                              //  Current valve settings for all valves
         RegisterAddress = (AddressInput | Bit_SPI_Mask_Read);             //  Make the output byte equal to the address of the register with the MSB set so that the recipient will know that we want to read what it has to offer
         spi_xfer(SPI, RegisterAddress, 8);                                //  Send the register address of the register that we care about for this transaction to the module
         InFromSPI8 = spi_xfer(SPI, 0x00, 8);                              //  Go and get the first 8 bits off the bus
         NybbleToASCII = (InFromSPI8 >> 4);                                //  Shift the byte right four to shift the most significant nybble to the least significant nybble
         ConvertNybbleToASCIIandOuput(NybbleToASCII);                      //  Output the MS Nybble to the PC
         TempReg8 = (InFromSPI8 & 0x0F);                                   //  Take the data that we got off the SPI bus again, and mask the MSNybble, so we can output the LSNybble
         ConvertNybbleToASCIIandOuput(TempReg8);                           //  Convert the LSNybble to ASCII and output it to the PC
         break;

void WriteValveModuleRegister(int8 AddressInput)
{
   int8  RS232Read,                                                        //  Temp register to hold the data just received
         TempReg8,                                                         //  8-bit temp register in case that is what we are receiving
         ConvertingNybble;                                                 //  Holding register to hold the nybble that we are currently converting

   int16 TempReg16;                                                        //  16-bit temp register in case that is what we are receiving

   switch(AddressInput)
   {
      case 1:                                                              //  Valve setpoints for all valves
         RS232Read = fgetc(Computer);                                      //  Go and fetch the first character off of the bus
         ConvertingNybble = ConvertASCII2HEXnybble(RS232Read);             //  Call the routine to convert the ASCII character we just received to a HEX nybble
         TempReg8 = ConvertingNybble;                                      //  Write the converted nybble to the temp register
         TempReg8 = (TempReg8 << 4);                                       //  Shift the register left four to get it ready for the next nybble
         RS232Read = fgetc(Computer);                                      //  Go and fetch the eighth character off of the bus
         ConvertingNybble = ConvertASCII2HEXnybble(RS232Read);             //  Call the routine to convert the ASCII character we just received to a HEX nybble
         TempReg8 = (TempReg8 + ConvertingNybble);                         //  Add the converted nybble we just received to the nybble we were already holding
         spi_xfer(SPI, 0x01, 8);                                           //  Send the address for this register over there first so that the recipient knows who we are talking to
         spi_xfer(SPI, TempReg8, 8);                                       //  Write the temp8 register over the SPI bus to send it over to the Valve Module
         fprintf(Computer, "K\n\r");                                       //  Just send a K over the bus to let the PC know that we received the transaction
         break;


//  This is the main landing point when checking the UART to talk to the computer
void CheckUART (void)
{
   int8  /*MethodAccumulator,                                                //  Accumulator for the method number*/
         ConvertedByteIn,                                                  //  Byte in that is the converted result of the byte that we just received in
         TruncatedAddress,                                                 //  Register that holds the register address minus the offset
         RS232Read;

   int16 /*EE_Address,                                                       //  Temporary holding register for the EEPROM address*/
         RegisterAddressIn;                                                //  Register to hold the register address that the ReceiveAndConvertThree function received

//   restart_wdt( );                                                         //  Restart the watchdog timer before going into the UART routine since it is the routine that can take the longest
 
   if (kbhit(Computer) == FALSE)                                           //  Check to see if there is a serial transaction waiting
   {
      return;
   }
   RS232Read = fgetc(Computer);                                            //  Pick up the character received from the port

   switch (RS232Read)
   {
      case 'W':                                                            //  The landing place if the bus master wants to write some info to us
         output_high(LED_Red);                                             //  *********************FOR TESTING!!!!!!!!!!!!!!!!!!******************
         restart_wdt( );                                                   //  Just in case this reception takes a little while for some reason, do a quick reset of the WDT
         RegisterAddressIn = ReceiveAndConvertThree();                     //  Receive the register address that the PC wants to write to
         {
            TruncatedAddress = (RegisterAddressIn - 100);                  //  Subtract 100 from the address so that we will just have the base offset for the Valve Module #1 commands
            if(TruncatedAddress == 0)                                      //  Check to see if this address is writing if the module is present or not
            {
               ConvertedByteIn = ReceiveAndConvertByte();                  //  Since this is register #0, go and get the next byte off the bus and see if we are enabling or disabling the module
               if(RS232Read == 0)                                          //  Do the following if the device is not present
               {
                  Reg_PerfPresFlags = (Reg_PerfPresFlags & (0xFF ^ Bit_PerfPres_ValveModule1));  //  Clear the flag for this module so that it is not present
               }
               else                                                        //  Otherwise, the device is present
               {
                  Reg_PerfPresFlags = (Reg_PerfPresFlags | Bit_PerfPres_ValveModule1);  //  Made sure that the flag for this module to be present is set
               }
               fprintf(Computer, "K\n\r");                                 //  Just send a K over the bus to let the PC know that we received the transaction
            }
            else                                                           //  The PC is trying to write to an address in upper memory, so let's make sure that the device is there
            {
               if((Reg_PerfPresFlags & Bit_PerfPres_ValveModule1) > 0)     //  Check to see if the module is present, and if so do the following
               {
                  output_low(ValveModule1_CS);                             //  Assert the chip select for this module
                  WriteValveModuleRegister(TruncatedAddress);              //  Write to the Valve Module #1 register at the truncated address since the module is present
                  output_high(ValveModule1_CS);                            //  Put the chip select high again so we can talk to something else next time

               }
               else
               {
                  fprintf(Computer, "X\n\r");                              //  Just send a X over the bus to let the PC know that this register is assigned to a module that is not present
               }
            }

            //  Assert the chip select for Valve Module #1
           
            //  De-Assert the chip select for Valve Module #1
            return;                                                        //  I think that it is right to return from here, um...right?  We'll see...bwa-ha-ha
         }

      case 'R':
               if((Reg_PerfPresFlags & Bit_PerfPres_ValveModule1) > 0)     //  Check to see if the module is present, and if so do the following
               {
                  output_low(ValveModule1_CS);                             //  Assert the chip select for this module
                  ReadValveModuleRegister(TruncatedAddress);               //  Read the Regeneration Module register at the truncated address
                  output_high(ValveModule1_CS);                            //  Put the chip select high again so we can talk to something else next time
               }
               else
               {
                  fprintf(Computer, "X\n\r");                              //  Just send a X over the bus to let the PC know that this register is assigned to a module that is not present
               }
            }
            //  Assert the chip select for Valve Module #1

            //  De-Assert the chip select for Valve Module #1
            return;                                                        //  I think that it is right to return from here, um...right?  We'll see...bwa-ha-ha
         }
         break;
}




Here is the code from the Slave:
Code:
#use spi (STREAM = SPI, SLAVE, DI=PIN_C4, DO=PIN_C2, CLK=PIN_C3, MODE=3)
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
   {
      return;                                                              //  Nope, no chip select here, so head on back
   }
   AddressIn = spi_xfer(SPI, 0x00, 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, 0x00, 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;

One last fundamental question is regarding the tri-state operation of the MISO pin since I have historically controlled it all myself. Does it go high impedance between every transaction, or when does it go Hi-Z? I want to make sure that I don't have multiple slaves butting heads on this pin.

Thanks so much for your insight into getting these two #use spi routines to "play nice" with each other. Again, I can start adding "band aids", but I want to make sure that this communication is consistent, and this is so simple that I think that there may be something fundamental that I may be missing. THANKS SO MUCH for your help!!!!!
_________________
Life is too short to only write code in assembly...
Ttelmah



Joined: 11 Mar 2010
Posts: 19763

View user's profile Send private message

PostPosted: Fri Mar 21, 2025 1:57 am     Reply with quote

A couple of things:

First you need to read the data sheets for the devices you are talking to.
Many do require a delay between CS and data being sent. Check this.

Second, you say the data looks right at the master. However the chip you
are using is one with programmable slew rate limitation. That your behaviour
goes wrong when you boost speed, suggests you may not be setting the
slew rate to fast on both the CS outputs and the SPI lines. My suspicion is
that possibly the SPI setup is correctly turning up the slew rate on these
lines, but the CS outputs are still set to slow slew rates, hence there is
actually a delay in the operation of these. A careful look a the edges of these
signals is called for, and possibly turning the rates up on these lines.
Look at the sticky at the top of the forum, and at the set_slow_slew command.
The default is normally for slow slew to be enabled when the chip wakes.

As a comment, you can enable the software pull-up on the MISO line. No
reason at all not to, and in fact this really should always be done on every
line that may at times be undriven. This is only a few uA. No line should
ever be left in an undriven state.
Slaves normally only drive their outputs when enabled, so when no slave
is enabled, you have this pin floating. Presumably you are connecting
the selects to the hardware slave select input on the slaves?. If these are
the same chip, these need the slave select inputs also set up with PPS.
Otherwise the slave outputs will not be disabled. Critical......
The code you post does not look as if this is the case. It looks as if you
are polling the select, not using the hardware line. If this is the case all
the slaves will be driving their outputs.

A huge amount also depends on the layout of the data lines when you try to
use faster clock rates.
SkeeterHawk



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

View user's profile Send private message

PostPosted: Fri Mar 21, 2025 9:47 am     Reply with quote

Thank you very much for the concise reply, Ttelmah. Your suggestions are fantastic, and I will look into all of the points you made. I will plan to update this thread once I get to the bottom of this.

Thanks again!
_________________
Life is too short to only write code in assembly...
SkeeterHawk



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

View user's profile Send private message

Found a Hardware Problem with #pin_select
PostPosted: Thu Mar 27, 2025 11:47 am     Reply with quote

I took @Ttelmah advice and integrated the Slave Select pin into my code, so I am sure this helped. I was still having issues and was planning on adding some small delays here and there to make it work, but before I did I spent a lot of time reading posts on this forum about setting up the SPI module. This is the first chip that I have used that has the Peripheral Pin Select Module, so I was trying to use the #pin_select pre-processor command like a good little programmer to assign these pins. Here is what I had in my slave when I first posted:
Code:
#pin_select SDO1=PIN_C2
#pin_select SCK1=PIN_C3
#pin_select SDI1=PIN_C4
#use spi (STREAM = SPI, SLAVE, ENABLE=PIN_D0, MODE=3)


Trying to add the SS pin in, I added this statement above that:
Code:
#pin_select SS1IN=PIN_D0


Reading on the forum, someone mentioned that you need to assign the pins using the pin select command, and that would assign those to SPI1 in hardware, and then you had to add the command SPI1 to the #use spi pre-processor to enable the hardware SPI module. Every time I tried to add that into my #use spi statement, the compiler would "bark at me". I FINALLY got to the bottom of it!! My dumba$s was using the assignment for the clock as the output and since I was assignign it as a Slave, it had to be an input. Going back to the supplied processor.h file, I found that I had the wrong statement. Here is what it was supposed to be:
Code:
#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)

The compiler likes that code, and now (amazingly) my code works!!! I assume that I was forcing it to software SPI by not having that command in my #use spi statement. I still haven't verified that the MISO line is going high impedance when the SS goes high, but I am making progress again!! Thanks to everyone for being a part of this fantastic forum/community!
_________________
Life is too short to only write code in assembly...
Ttelmah



Joined: 11 Mar 2010
Posts: 19763

View user's profile Send private message

PostPosted: Thu Mar 27, 2025 12:45 pm     Reply with quote

Well done.
The lesson is to never 'assume'. Very Happy
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