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 support@ccsinfo.com

[SOLVED] PIC18F26K83 I2C slave problem
Goto page 1, 2  Next
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
jaka



Joined: 04 May 2014
Posts: 35
Location: Finland

View user's profile Send private message

[SOLVED] PIC18F26K83 I2C slave problem
PostPosted: Tue Oct 27, 2020 7:34 am     Reply with quote

Edit: problem is fixed in compiler 5.097.

Using compiler 5.096 and PIC18F26K83.

I am able to make the I2C slave work on the PIC, but it can easily 'lock up' (holding clock low). I was suspecting my board would be the cause, but now I tested it with a barebone setup and can still repeat the issue.

Reading and writing works OK as long as I use the correct slave address. If I try to communicate with another slave address, the PIC locks the SCL low and can't be communicated with anymore. I have tried Totalphase Aardvark and PICkit serial analyzer as master devices, the problem remains the same.

The problem can be reproduced with the ex_i2c_slave_k42.c example code. (I read somewhere that the I2C module on K83 is very similar as in K42). For some reason the NOCLEAR must be removed from the line
Code:
#INT_I2C1 NOCLEAR

in order to work at all. Could this provide a clue where the problem is?

I have tried the same example code with a PIC16F15355 which is also relatively new and uses #pin_select. With that one, on the same board, it works OK, no lockups.

In my 'barebones' test setup I am using Microchip 28-pin demo board (DM164120-3) and 4k7 I2C pullups.


Last edited by jaka on Wed Nov 18, 2020 1:22 am; edited 2 times in total
Ttelmah



Joined: 11 Mar 2010
Posts: 18229

View user's profile Send private message

PostPosted: Tue Oct 27, 2020 8:05 am     Reply with quote

95% of PIC's don't have a specific 'I2C' module. Instead they have the
MSSP module that can do SPI or I2C.The chips you are having problems
with have the specific I2C module. The PIC16F15355 has an MSSP module
not an I2C module. The K83, and K42 are two of only a handful of chips
having the I2C module.
Now the I2C module is meant to automatically clear the interrupt when
the buffer is read, which is why the CCS code uses the NOCLEAR option.

The behaviour with an address that doesn't match the address stored,
depends on some specific settings in the chip. It is possible that the default
settings are not correct. I'd suspect that possibly ADRIE is set. If this
is the case the chip will interrupt on the address, and the code then needs to
manually clear CSTR.

So try adding the following:
Code:

#bit ADRIE=getenv("BIT:ADRIE")

//then near the start of your main:
    ADRIE=0;
jaka



Joined: 04 May 2014
Posts: 35
Location: Finland

View user's profile Send private message

PostPosted: Wed Oct 28, 2020 12:41 am     Reply with quote

Thanks for your suggestion, Ttelmah. Disabling ADRIE didn't help.

I have had the problem a few months already, but I have had other higher priority tasks to do, so this has been on the back burner. I have been hoping that the problem would be fixed in some compiler update, but no luck so far. I have earlier tried to look how all the I2C peripheral related registers are being set up by the compiler, but couldn't really figure out what's going on.

Maybe I'll now inform CCS about the problem when I have reproduced it with a minimal implementation. I still have few weeks before I have more time on this project, maybe that'll be enough to get it sorted.
Ttelmah



Joined: 11 Mar 2010
Posts: 18229

View user's profile Send private message

PostPosted: Wed Oct 28, 2020 1:17 am     Reply with quote

Looking at the data sheet, in all the standard modes, reading or writing the
data register, should clear the interrupt. The example I2C slave does that
in every path, so it should clear.
The clock stretch, says:
Quote:

SMA = 1 and RXBF = 1(6)
- Set by hardware on 7th falling SCL edge
- User must read byte I2CRXB to release SCL
SMA = 1 and TXBE = 1 and I2CCNT!= 0
- Set by hardware on 8th falling SCL edge
- User must write byte to I2CTXB to release SCL

SMA is set when the 8th clock bit is seen.

It then says:
In Slave mode, if the ADRIE or WRIE bits are set, clock
stretching is initiated when there is an address match
or when there is an attempt to write to slave. This
allows the user to set the ACK value sent back to the
transmitter.

Note only 'address match'.

Everything says the chip should only respond to the address when it
matches the stored value (exception to this is is GCEN is enabled
and something transmits to address '00').
I almost wonder if you may have found an undocumented hardware
fault with this chip... Sad

I have found three threads with people having problems with the 18F26K83
holding the SCL low on I2C slaves. None had a solution.
jaka



Joined: 04 May 2014
Posts: 35
Location: Finland

View user's profile Send private message

PostPosted: Fri Oct 30, 2020 2:56 am     Reply with quote

Thanks again for your suggestions Ttelmah. I have done some more experimenting.

When trying to access another slave, that slave address gets loaded to I2C1RXB register. This causes the I2C1RXIF flag to be set, and clock is stretched. I tried to check the status of the I2C1RXIF at main while loop, and perform I2C1RXB read based on that. It releases the SCL and I2C works again.

The ADB bit of I2C1CON2 register is set to 1 by default:
Quote:
ADB: Address Data Buffer Disable bit
1 = Received address data is loaded into I2CxRXB
Transmitted address data is loaded from the I2CxTXB
0 = Received address data is loaded only into the I2CxADB
Transmitted address data is loaded from the I2CxADB0/1 registers

If I set it to 0, the I2C slave is not working at all (probably the CCS slave code is written to read the address from RXB and not ADB?)

I also noticed that the CNTIF flag is set when the SCL is held low after incorrect slave address. If I enable CNTIE, the problem is fixed, no more clock stretching on incorrect slave addresses.

However, these workarounds don't help with my original problem. The target application is safety logic controller, and I don't want to use any interrupts in the code. Actually, I was already implementing polling based I2C slave implementation some months ago. When I ran into problems with SCL held low, I reverted back to CCS standard interrupt based implementation. Only to find out that it didn't work either. It is not nice if one slave clock stretches operations to all other slave devices!

In current project state it is probably still possible to switch to UART instead of the I2C. Probably have to do that if I can't solve the I2C issue soon. Another possibility is to reserve a separate I2C bus only for communication with this one chip, perhaps using an I2C mux.
jaka



Joined: 04 May 2014
Posts: 35
Location: Finland

View user's profile Send private message

PostPosted: Fri Oct 30, 2020 6:39 am     Reply with quote

Correction to my first post. The NOCLEAR didn't have any effect on operation. I had by mistake #INT_I2C2 NOCLEAR on the line which didn't work. Because I was earlier testing also the operation of the second I2C module if that would work any better. There was a comment on the changelog for 5.094:
Quote:
Fixed an issue with #use i2c() on K83 devices not being able to use second I2C peripheral

But at least in slave mode the problem remains the same with I2C2.
jaka



Joined: 04 May 2014
Posts: 35
Location: Finland

View user's profile Send private message

PostPosted: Tue Nov 03, 2020 12:28 am     Reply with quote

Quick update, two encouraging news:

1)
Yesterday I found this Microchip example: https://mplabxpress.microchip.com/mplabcloud/example/details/519
And ported it to PIC18F26K83 (only slave portion of it). It is also broken, but in different way. Reads from the slave sometimes return 0x00 and not correct data. But it doesn't hold the SCL low for incorrect addresses. And it works in polling mode without interrupts also OK, again not holding SCL low for incorrect addresses.

2)
Today received message from CCS support that the problem is fixed to the #use i2c() library. It will be in next compiler release, maybe this week.

I'll give an update when I try the new release.
jaka



Joined: 04 May 2014
Posts: 35
Location: Finland

View user's profile Send private message

[SOLVED] PIC18F26K83 I2C slave problem
PostPosted: Wed Nov 18, 2020 1:17 am     Reply with quote

Compiler release 5.097 fixes the problem!

It seems that the only change in configuration registers is setting the ADB bit to 0. Apparently the code is then changed to read the address from ADB instead of RXB (there seems to be changes in code generated by i2c_read() but I didn't look it in detail).

The slave I2C now works properly in polled mode also. Accesses to other slaves won't cause unwanted clock stretching. You must poll if I2CIF or ACKTIF bits are being set.
rshowri



Joined: 13 Apr 2022
Posts: 2

View user's profile Send private message

PostPosted: Wed Apr 13, 2022 3:05 pm     Reply with quote

With compiler 5.099 version, I'm still seeing i2c slave issues with non-matching slave address. Can you please let me know how to fix this issue ?
jaka



Joined: 04 May 2014
Posts: 35
Location: Finland

View user's profile Send private message

PostPosted: Thu Apr 14, 2022 12:31 am     Reply with quote

Are you using PIC18F26K83 also, or some other chip? Are you using the I2C slave with interrupts or in polling mode?

Even though I reported that compiler version 5.097 fixes the problem, there were still some occasional cases where the I2C slave would lock up. I did stress testing by doing long reads at different clock rates from the master. It was a long story, I'll try to list here the essentials which I tested, and which of those helped:

- Do not use 'SMBUS' parameter in #use i2c! I have used this parameter in the past with some other PICs to change the I2C pins to use voltage levels according to SMBUS spec. I read that advice from this forum years ago. However, using the SMBUS parameter on PIC18F26K83 does something else, which causes it to halt with SCL low quite easily. I tried to look at the manual, help etc. It isn't specified anywhere what the SMBUS really does! If you want to use the lower voltage thresholds, you need to set them up by writing into the registers, something like this:
Define the required registers:
Code:

#byte RC3I2C=getenv("SFR:RC3I2C")      // For setting I2C pin voltage levels, slew rate, pullup
#byte RC4I2C=getenv("SFR:RC4I2C")      // For setting I2C pin voltage levels, slew rate, pullup

Then in your init function:
Code:

RC3I2C = 0x42;                   // I2C SCL PIN: I2C slew rate limiting, no pull-ups, SMBUS 2.0 voltage thresholds
RC4I2C = 0x42;                   // I2C SDA PIN: I2C slew rate limiting, no pull-ups, SMBUS 2.0 voltage thresholds


- I couldn't get the polled mode to work reliably, so I eventually changed to use interrupts for I2C slave. Even though I would like to eliminate interrupts altogether in my application.

- The I2C module has some kind of timeout functionality built-in, but I didn't get that to work. I2C1BTO register allows to set source for timeout, which should reset the i2c module. Suggestion is to use timer2. Configured timer2 to free-running timer which is reset with high input. Set input to timer2 clock input, and set that input to same pin as SCL via PPS. Set timer2 to 8 ms period. The timer part works, and if SCL is low, it sets BTOIF flag of I2C1ERR register. However, it doesn't seem to reset the I2C module, or at least that doesn't help.

- Use similar approach as the with the timeout functionality above to set up a timer to monitor SCL pin. But instead of relying on the I2C module timeout feature, manually check for timer timeout, and clear CSTR bit. Something like this:
Set PPS registers and define the CSTR bit:
Code:
#pin_select SCL1OUT = PIN_C3
#pin_select SCL1IN  = PIN_C3
#pin_select SDA1OUT = PIN_C4
#pin_select SDA1IN  = PIN_C4
#pin_select T4CK    = PIN_C4  // We define timer 4 clock input to same pin as I2C SDA. It allows to use timer 4 as timeout for SDA being low too long
#pin_select T2CK    = PIN_C3  // T2CK and T2CKI are the same. We define timer 2 clock input to same pin as I2C SCL. It allows to use timer 2 as timeout for SCL being low too long
#bit CSTR = getenv("BIT:CSTR")            // For clearing CSTR (clock stretch)

In your init code (note that if you use SMBUS voltage levels for I2C, be sure to use set_input_level_c() function to set the pins C3 and C4 as TTL! Otherwise the timer input might not detect 'high' state properly!)
Code:

   // Setup timer 2 to use Fosc/4 (16 MHz/4 = 4 Mhz) as clock source, so one tick is 250 ns
   // Prescaler of 128 causes one timer tick to be 32 us
   // Set mode to free running, with hardware reset when T2IN is high
   // The T2IN is configured to I2C SCL with PPS registers, this way the timer runs when SCL is low
   // and is reset when SCL is high
   // With period of 255 ticks this sets I2C timeout to 8.16 ms
   setup_timer_2(T2_DIV_BY_128 | T2_CLK_INTERNAL | T2_RESET_WHEN_HIGH | T2_RESET_FROM_T2IN, 255, 1);
   setup_timer_4(T4_DIV_BY_128 | T4_CLK_INTERNAL | T4_RESET_WHEN_HIGH | T4_RESET_FROM_T4IN, 255, 1);

In your main loop:
Code:

   // Detect SCL timeout
   if (interrupt_active(INT_TIMER2)) {        // Timer 2 is set such that if SCL is low for more than 8.16 ms, the timer 2 interrupt flag is set.
      CSTR = 0;                               // Clear CSTR bit of I2C1CON0 register to release SCL
      clear_interrupt(INT_TIMER2);
   }


You could also use same approach for checking SDA stuck low, but I didn't need this after I removed the SMBUS parameter. You can also remove the timer 4 related lines from the above code snippets.
Code:

   // Check for SDA low timeout.
   // Seems that this isn't needed when I2C peripheral is correctly set up (no SMBUS defined)
     if (interrupt_active(INT_TIMER4)) {
      disable_interrupts(INT_I2C1);
      i2c_init(0);
      delay_us(5);
      i2c_init(1);
      RC3I2C = 0x42;            // I2C SCL PIN: I2C slew rate limiting, no pull-ups, SMBUS 2.0 voltage thresholds
      RC4I2C = 0x42;            // I2C SDA PIN: I2C slew rate limiting, no pull-ups, SMBUS 2.0 voltage thresholds
      clear_interrupt(INT_TIMER4);
      enable_interrupts(INT_I2C1); 
   }


I also tried a lot of different other things, like checking various error bits of the I2C peripheral and different ways of resetting the I2C peripheral. But in the end, these three actions fixed all problems:
- Get rid of SMBUS in #use i2c
- Use I2C interrupts
- Use timer to check SDA low, and clear CSTR based on that

In over one year, we haven't observed a single lockup anymore.
Ttelmah



Joined: 11 Mar 2010
Posts: 18229

View user's profile Send private message

PostPosted: Thu Apr 14, 2022 1:30 pm     Reply with quote

Honestly a polled I2C slave is never reliable. Unless you are sitting in a tight
loop to do the polling, you may well simply exceed the time that the master
allows between I2C_start, and the following write. This also becomes a
problem where the slave is running at a slower clock rate than the master.
The SMBUS option is useful, when talking to another device that cannot
meet I2C levels (so things like a 3.3v slave with a 5v master), but should
not be used unless this is the case.
rshowri



Joined: 13 Apr 2022
Posts: 2

View user's profile Send private message

PostPosted: Thu Apr 14, 2022 3:42 pm     Reply with quote

Thank you jaka for the detailed response.
I'm using PIC18LF25K42 with I2C interrupt mode.
Clearing CSTR bit does help when non-matching slave address is used. Both SCL and SDA are high if I clear CSTR bit. But I didn't use timer interrupts in the code.

Can you please check the below code and let me know you if this is OK.
Code:

#include <18LF25K42.h>
#use delay(internal=8MHz)

#define SCL_PIN     PIN_C3
#define SDA_PIN     PIN_C4

#pin_select SCL1OUT = SCL_PIN
#pin_select SDA1OUT = SDA_PIN

#use I2C(SLAVE, scl=SCL_PIN, sda=SDA_PIN, address=0xA0)

#bit CSTR = getenv("BIT:CSTR")   // For clearing CSTR (clock stretch)

unsigned int8 address=0, buffer[256];

#INT_I2C1 NOCLEAR
void i2c_interrupt(void)
{
   unsigned int8 state, incoming;

   state = i2c_isr_state();

   if(state <= 0x80)                      //Master is sending data
   {
      if(state == 0x80)
         incoming = i2c_read(2);          //Passing 2 as parameter, causes the function to read the SSPBUF without releasing the clock
      else
         incoming = i2c_read();

      if(state == 1)                      //First received byte is address
         address = incoming;
      else if(state >= 2 && state != 0x80)   //Received byte is data
         buffer[address++] = incoming;
   }

   if(state >= 0x80)                      //Master is requesting data
   {
      i2c_write(buffer[address++]);
   }
   
   CSTR = 0;
}

void main(void)
{
   unsigned int16 i;
   
   for(i=0;i<256;i++)
      buffer[i] = i;
   
   enable_interrupts(GLOBAL);
   enable_interrupts(INT_I2C1);
   
   while(TRUE)
   {
   }
}
jaka



Joined: 04 May 2014
Posts: 35
Location: Finland

View user's profile Send private message

PostPosted: Fri Apr 15, 2022 6:07 am     Reply with quote

Ttelmah wrote:
Honestly a polled I2C slave is never reliable. Unless you are sitting in a tight
loop to do the polling, you may well simply exceed the time that the master
allows between I2C_start, and the following write. This also becomes a
problem where the slave is running at a slower clock rate than the master.


I thought that the I2C peripheral would take care of this. Shouldn't it detect the I2C start, and then read the byte, all on it's own? If it is something that requires an action, it would clock strecth until the firmware has taken the required action. Of course it should run in reasonably tight loop, otherwise the transfer rates would slow down. And the master can have some timeout, as you said, which shouldn't be exceeded.

Ttelmah wrote:
The SMBUS option is useful, when talking to another device that cannot
meet I2C levels (so things like a 3.3v slave with a 5v master), but should
not be used unless this is the case.


This was what I was trying to accomplish (or in my case, master is 3v3 and my PIC slave is 5v). But it seems that the SMBUS option shouldn't be used at all, at least before CCS specifies what that does. The compiler manual isn't very helpful here. It says this for SMBUS: 'Bus used is not I2C bus, but very similar'

With PIC16F1789, MSSP in I2C slave mode, the SMBUS option doesn't do anything! It doesn't set the voltage levels to SMBUS. You have to set the voltage levels yourself, by setting CKE bit in SSPSTAT register to 1.
jaka



Joined: 04 May 2014
Posts: 35
Location: Finland

View user's profile Send private message

PostPosted: Fri Apr 15, 2022 7:41 am     Reply with quote

rshowri wrote:
Thank you jaka for the detailed response.
I'm using PIC18LF25K42 with I2C interrupt mode.
Clearing CSTR bit does help when non-matching slave address is used. Both SCL and SDA are high if I clear CSTR bit. But I didn't use timer interrupts in the code.

Can you please check the below code and let me know you if this is OK.



I don't see anything alarming in your code. I don't think I needed to clear CSTR in I2C interrupt in my code (I don't currently have access to it). The compiler should handle that, but obviously it doesn't, if it helps you.

Do you still see occasional problems with non-matching slave address, with CSTR clearing in interrupt?
PCM programmer



Joined: 06 Sep 2003
Posts: 21674

View user's profile Send private message

PostPosted: Fri Apr 15, 2022 12:12 pm     Reply with quote

I notice you have NOCLEAR in the i2c interrupt, but then you never
manually clear the interrupt.
rshowri wrote:


#INT_I2C1 NOCLEAR
void i2c_interrupt(void)
{
unsigned int8 state, incoming;

state = i2c_isr_state();

if(state <= 0x80) //Master is sending data
{
if(state == 0x80)
incoming = i2c_read(2); //Passing 2 as parameter, causes the function to read
//the SSPBUF without releasing the clock
else
incoming = i2c_read();

if(state == 1) //First received byte is address
address = incoming;
else if(state >= 2 && state != 0x80) //Received byte is data
buffer[address++] = incoming;
}

if(state >= 0x80) //Master is requesting data
{
i2c_write(buffer[address++]);
}

CSTR = 0;
}
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 1, 2  Next
Page 1 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