Missing ACK in an I2C communication between PIC32 and ESP32

Thread Starter

Vilius_Zalenas

Joined Jul 24, 2022
169
Hi,

I am developing a project that involves PIC32MK1024MCM064 communication with ESP32S3 and PCF8574 IO expander (for 16x2 LCD screen) via I2C. I have absolutely no problems with the PCF8574, I am getting my data packets, LCD screen works perfectly.

However, I tested the same I2C code in communication with ESP32S3 (PIC32 as master, ESP32S3 as slave), and I faced a problem with the ACK bit (It seems that PIC32 does not release the SDA line, so the ESP32S3 can not pull it down, thus sending an ACK, but that is just a guess.) I am using 5.1k pullups, standard 100Kbit/s (actually getting 95-97 KBit, I guess this is tolerable), wiring is correct, addresses are also correct.

The strangest thing is that my friend wrote the code for ESP32S3 as an I2C slave. He gets correct data packets with ACK when testing between two ESP32S3s (one as a master, the other as a slave.)

Generally speaking, me and my friend are both getting correct code results when testing in our own environments (PIC32 <-> PCF8574 and ESP32S3 <-> ESP32S3), but we can not communicate between the different MCU`s getting ACKs. I am not experienced with I2C, but I am feeling like there is something with the configuration... Maybe some clock mismatch or something else that I dont even know... This is our last step in completing a major project, so any help, advice or observation is really appreciated. Thank you in advance.

PIC32 code:


main::
    #include <xc.h>
    #include "configurations_bits.h"                                                                
         
    #include <stddef.h>                                                                              
    #include <stdint.h>                  
    #include <stdbool.h>                  
    #include <stdlib.h>
    #include "stdio.h"
    #include <sys/attribs.h>

    #include "delay.h"                                                                              
    #include "inter_integrated_circuit_protocol.h"

    #define ESP32 (0B1100000)   //Write address
    #define LCD (0B1000000)     //Write address
   
    uint8_t Number = 0x71;  //Random value
   
        int main(void) {
           
           
            ANSELA = 0x00000000;                                                                    
            ANSELB = 0x00000000;                                                                    
            ANSELC = 0x00000000;                                                                    
            ANSELE = 0x00000000;                                                                    
            ANSELG = 0x00000000;                                                                    
         
           
            TRISCbits.TRISC11 = 0b1;           //RC11 PIN_24 ADC pin for battery
            TRISAbits.TRISA1 = 0b1;            //RA1 PIN_14 Particle detection
            TRISCbits.TRISC7 = 0b1;            //RC7 PIN_51 First GUI button
            TRISCbits.TRISC8 = 0b1;            //RC8 PIN_52 Second GUI button
            TRISEbits.TRISE15 = 0b1;           //RE15 PIN_30 Third GUI button
            TRISEbits.TRISE14 = 0b1;           //RE14 PIN_29 Fourth GUI button
            TRISDbits.TRISD5 = 0b1;            //RD5 PIN_53 Encoder channel A
            TRISDbits.TRISD6 = 0b1;            //RD5 PIN_54 Encoder channel B
            TRISGbits.TRISG6 = 0b1;            //RG6 PIN_4 ESP32 -> PIC32 communication
            TRISAbits.TRISA11 = 0b1;           //RA11 PIN_12 ESP32 -> PIC32 communication
            TRISAbits.TRISA12 = 0b1;           //RA12 PIN_11 ESP32 -> PIC32 communication
            TRISAbits.TRISA0 = 0b1;            //RA0 PIN_13 UART RX
 
         
            Inter_Integrated_Circuit_Setup ();                                                      
            Inter_Integrated_Circuit_Enable ();                                                      
         
            //First I2C operation writing something to PCF8574 IO expander (I am getting ACK)
           
            Inter_Integrated_Circuit_Start();
            Inter_Integrated_Circuit_Write(LCD);
            Inter_Integrated_Circuit_Write(Number);
            Inter_Integrated_Circuit_Stop();
           
            //Second I2C operation writing something to ESP32S3 (I am getting NACK)
           
            Inter_Integrated_Circuit_Start();
            Inter_Integrated_Circuit_Write(ESP32);
            Inter_Integrated_Circuit_Write(Number);
            Inter_Integrated_Circuit_Stop();
           
                while (true){  
                 
                }
           
    return (EXIT_FAILURE);
}
I2C.h:
//**************************************************************************
// INTER INTEGRATED CIRCUIT PROTOCOL HEADER FILE
//**************************************************************************

    void Inter_Integrated_Circuit_Setup (void);
       
    void Inter_Integrated_Circuit_Enable (void);
       
    void Inter_Integrated_Circuit_Disable (void);
       
    void Inter_Integrated_Circuit_State (void);
       
    void Inter_Integrated_Circuit_Start (void);
       
    void Inter_Integrated_Circuit_Stop (void);
       
    void Inter_Integrated_Circuit_Repeated_Start (void);
       
    void Inter_Integrated_Circuit_Write (uint8_t I2C_data);
       
    uint8_t Inter_Integrated_Circuit_Read (void);
I2C.c:
//**************************************************************************
// INTER INTEGRATED CIRCUIT PROTOCOL SOURCE FILE
//**************************************************************************
   
  #include <xc.h>                                                                                

  #include <stdint.h>

  #include "inter_integrated_circuit_protocol.h"

  #include "delay.h"

    void Inter_Integrated_Circuit_Setup (void){

        I2C1BRG = 0xEC;                                                                      
        I2C1CON = 0x00000000;                                                                    
        I2C1CONbits.SDAHT = 0b0;                                                                  
        I2C1CONbits.SIDL = 0b0;                                                                  
        I2C1CONbits.SCLREL = 0b1;                                                                
        I2C1CONbits.STREN = 0b0;                                                                  
        I2C1CONbits.DISSLW = 0b1;                                                                
        I2C1CONbits.SMEN = 0b0;                                                                    
    }

    void Inter_Integrated_Circuit_Enable (void){
       
        I2C1CONbits.ON = 0b1;                                                                    
    }
   
     void Inter_Integrated_Circuit_Disable (void){
       
        I2C1CONbits.ON = 0b0;                                                                    
    }
   
   void Inter_Integrated_Circuit_State (void){
     
        while( (I2C1CON & 0x0000001F) || (I2C1STAT & 0x00000004) );                              
   }
 
    void Inter_Integrated_Circuit_Start (void){
       
        Inter_Integrated_Circuit_State ();                                                        
        I2C1CONbits.SEN = 0b1;                                                                    
    }

    void Inter_Integrated_Circuit_Stop (void){
       
        Inter_Integrated_Circuit_State ();                                                      
        I2C1CONbits.PEN = 0b1;                                                                    
    }
   
    void Inter_Integrated_Circuit_Repeated_Start (void){
       
        Inter_Integrated_Circuit_State ();
        I2C1CONbits.RSEN = 0b1;                                                                  
    }

    void Inter_Integrated_Circuit_Write (uint8_t I2C_data){
       
        Inter_Integrated_Circuit_State ();                                                        
        while(I2C1STATbits.TBF != 0);                                                              
        while(I2C1STATbits.TRSTAT != 0);                                                          
        I2C1TRN = I2C_data;
 
    }

    uint8_t Inter_Integrated_Circuit_Read (void){
       
        uint8_t I2C_data = 0;
        Inter_Integrated_Circuit_State ();                                                        
        I2C1CONbits.RCEN = 0b1;                                                                    
        while(I2C1STATbits.RBF != 0);                                                              
        I2C_data = (I2C1RCV & 0x000000FF);                                                        
        I2C1CONbits.ACKEN = 0b1;                                                                  
        return (I2C_data);
     }
 

Attachments

Last edited:

geekoftheweek

Joined Oct 6, 2013
1,201
However, I tested the same I2C code in communication with ESP32S3 (PIC32 as master, ESP32S3 as slave), and I faced a problem with the ACK bit (It seems that PIC32 does not release the SDA line, so the ESP32S3 can not pull it down, thus sending an ACK, but that is just a guess.) I am using 5.1k pullups, standard 100Kbit/s (actually getting 95-97 KBit, I guess this is tolerable), wiring is correct, addresses are also correct.
The problem is going to be with the ESP32 and here is why...
The PIC has nothing to do with why the ESP cannot pull the SDA low for the ACK. The PIC is not actively powering the pin and the only power should be coming from the pull up resistor.

One way to think of it is say you have an incoming water supply with flow limiting devide (resistor) and a tee that splits the water into two pipes. At the end of each pipe is a valve and a pressure gauge. When you open the valve at one pipe the pressure gauge at the other end will drop to near zero or zero... water and electricity are somewhat different in how this will play out in this scenario, but they work pretty much the same. Electrically speaking it will drop to zero given the "valve" can handle the current flow.

Generally speaking with I2C as long as you are not running a faster speed than what your slave is rated for everything will work. Most devices don't require you to use exactly 100 kHz... that is it's limit. The PIC's I have worked with will work into the MHz range as long as you aren't going faster than say 1/4 of the PIC's instruction clock (I haven't really tested the breaking point, but I'm going to guess it's somewhere around there). The lowest possible speed may be listed in the datasheet, but I would assume it would be less than say 50 kHz. Sooner or later the device may see it as a circuit fault and stop working.

The 5.1k resistor should be good, but may be too much. While the overall speed of the I2C is usually not a problem, the rise and fall times of the signal is. Maybe try smaller resistors and see if that makes any change. You should actually be able to to down to
say 330 before you start to run into the limits of the ESP32, but I wouldn't suggest going that low. If it doesn't work at 1k I would start to suspect the ESP32 itself.
 
Last edited:

Irving

Joined Jan 30, 2016
3,843
Normal I2C pull-ups are 1.8k to meet bus capacitance/cable length specs, but for local comms < a metre I often use 4k7 so the 5.1k probably isn't an issue. How far apart are your devices? Even, as @geekoftheweek says, its delaying the rise time (passive pull-up) that wouldn't result in the ACK being seen as a NAK as that's an active pull-down by the master.

Are your diagrams generated by a scope/logic analyser or hand-drawn to illustrate the issue? On the face of it your code seems OK, you send an 0x40 to the PCF8574 (write address 0x20) and get an ACK, but the same code writing an 0x60 (write address 0x30) gets no response from the ESP32 i.e. the ESP32 didn't recognise the address as its address. So that means one of 4 things:
  • The ESP32 is configured at a different address = most common issue due to differences in address parlance (ie 0x30 and a write bit = 0, or write address 0x60?) - or error in your slave coding
  • The ESP32 is misreading the address because of bus timing - less common, but a slow rise time could result in a misread 1st address bit - you'll need a 'scope to look at this more closely - or error in your slave coding
  • The ESP32 can't handle the clock rate - unlikely at 100kHz as I run ESP32s at > 1MHz no problem
  • The ESP32 is faulty - extremely unlikely - but you could try configuring a different ESP32 pin as its SDA line

What ESP32 slave library are you using?

Oh, one last thought. You do have a good solid ground (GND) connection between the PIC and the ESP32 boards and not relying on USB connections, etc.?
 
Last edited:

geekoftheweek

Joined Oct 6, 2013
1,201
@Irving you may be on to something....

The strangest thing is that my friend wrote the code for ESP32S3 as an I2C slave. He gets correct data packets with ACK when testing between two ESP32S3s (one as a master, the other as a slave.)
I only noticed "friend" and "ESP32S3 as an I2C slave". It's probably the 8 bit vs 7 bit address trap that had me scratching my head a few times in the past. The ESPs use 7 bit while the PIC is 8 bit.

My reason behind the resistors is simply due to a couple times I had some issues that were cured with different resistors. I didn't have the equipment at the time to properly test, but simple trial and error made it all work. The logic analyzer wasn't showing the true story.
 

Irving

Joined Jan 30, 2016
3,843
@Irving you may be on to something....



I only noticed "friend" and "ESP32S3 as an I2C slave". It's probably the 8 bit vs 7 bit address trap that had me scratching my head a few times in the past. The ESPs use 7 bit while the PIC is 8 bit.

My reason behind the resistors is simply due to a couple times I had some issues that were cured with different resistors. I didn't have the equipment at the time to properly test, but simple trial and error made it all work. The logic analyzer wasn't showing the true story.
Logic analysers, esp the cheap 8-bit clones, rarely do. For debugging I2C a decent 4-channel 'scope and the protocol decoder s/w is much better... why 4-ch? so you can monitor other gpio ports on send and receive devices and stick a 'digitalWrite' to a spare port in the code to trigger the 'scope... and get a close look at the timing...
 

dendad

Joined Feb 20, 2016
4,451
I think there is something different with the ESP32 I2C code. I've made many VFOs with Arduinos and Si5351 chips but the code directly ported to ESP32 boards does not work correctly. The Si5351 outputs a short burst of signal then stops. My son coded up an alternative I2C driver for the ESP32 that works fine. I'm not sure what he did as I do struggle to work out how most drivers work. Hardware is more my thing over software.
If I remember correctly, other I2C devices, like the displays worked ok, so it seems to me the timing is just borderline somewhere and it does depend on the devices used.
Not all I2Cs are equal it seems.
 

Thread Starter

Vilius_Zalenas

Joined Jul 24, 2022
169
I think there is something different with the ESP32 I2C code. I've made many VFOs with Arduinos and Si5351 chips but the code directly ported to ESP32 boards does not work correctly. The Si5351 outputs a short burst of signal then stops. My son coded up an alternative I2C driver for the ESP32 that works fine. I'm not sure what he did as I do struggle to work out how most drivers work. Hardware is more my thing over software.
If I remember correctly, other I2C devices, like the displays worked ok, so it seems to me the timing is just borderline somewhere and it does depend on the devices used.
Not all I2Cs are equal it seems.
Alright, I solved it. The problem was that I was not providing the MSB in the ESP32 address definition in the PIC`s code, now everything works, I want to thank you all.
 

Irving

Joined Jan 30, 2016
3,843
Alright, I solved it. The problem was that I was not providing the MSB in the ESP32 address definition in the PIC`s code, now everything works, I want to thank you all.
So it was the classic 7-bit v 8-bit address snafu :D. You're welcome :)

I think there is something different with the ESP32 I2C code. I've made many VFOs with Arduinos and Si5351 chips but the code directly ported to ESP32 boards does not work correctly. The Si5351 outputs a short burst of signal then stops. My son coded up an alternative I2C driver for the ESP32 that works fine. I'm not sure what he did as I do struggle to work out how most drivers work. Hardware is more my thing over software.
If I remember correctly, other I2C devices, like the displays worked ok, so it seems to me the timing is just borderline somewhere and it does depend on the devices used.
Not all I2Cs are equal it seems.
That's using ESP32 in I2C master mode - I recall reading something about the V1 chips way back but V2/V3 are fine and I've never had an issue using them natively or via the Arduino Wire library. The TS was using them in slave mode which is a much less common usage for which there was a problem with an early port of the Arduino Wire library but that been long since fixed.
 
Top