Electronic – I2C Module Not Responding

i2cmicrochipmicrocontrollerpicserial

I am writing a basic I2C slave as part of my code on a 20-pin 8-bit PIC16f MCU. As best as I can tell, this microcontroller has a module that watches the I2C bus and only updates my registers if there are incoming message addressed to the PIC — I can't interact with any other messages. The master is a CPU running a linux kernel and some other software. From the CPU, I can run a I2C probe script that just tries to read from every possible device address on the I2C chain (bus).

I'm having trouble getting the PIC to respond to any probing. I've written the simplest I2C code I can manage — it should just blink the LEDs whenever the device gets any sort of I2C message directed at it.

I hooked the lines up to the oscilloscope and was able to confirm that traffic is coming through the clock and data lines when I probe the chain.

My code is below. My compiler is the free version (without optimizations) of Microchip's XC8. I'm using the newest version (v1.34) for Windows.

Am I missing any initialization or configuration steps necessary to get my PIC to see incoming I2C messages?

#include <xc.h>                 /* XC8 General Include File */
#include <pic16lf1709.h>        /* Definitions of I/O pins */

#pragma config WDTE = OFF       // disable watchdog timer, for simplicity

// I2C address is 7 bits: 1111110
#define I2C_ADDRESS 0x7E

typedef unsigned char byte;

void main(void) {

    /* configure MSSP module */
    TRISBbits.TRISB4    = 1;    // set SDA to input
    TRISBbits.TRISB6    = 1;    // set SCL to input
    SSPCON1bits.SSPEN   = 1;    // enable SSP module
    SSPCON1bits.SSPM    = 0x6;  // SSP is in I2C slave mode, 7-bit addressing
    SSP1ADD             = I2C_ADDRESS<<1;  // set the device address (left-aligned)
    SSPCON1bits.CKP     = 1;    // release clock
    TRISC               = 0x00; // set LEDs to output
    PORTC               = 0xFF; // initialize LEDs to OFF

    while(1) {

        byte ssp_buf;           // for the data we read from the bus

        if(SSPSTATbits.BF) {    // if the I2C buffer is not empty
            PORTC = 0x00;       // turn on LEDs for a moment
            for(int i=0; i<100; i++) _delay(250);
            PORTC = 0xFF;       // turn them back off
            ssp_buf = SSPBUF;   // read the buffer
                                // BF flag is cleared by hardware
        }

        SSPCON1bits.CKP = 1;    // finally, release clock

    }

}

Best Answer

There are several I/O connection features that are in use by different PICs. The PIC that you're using has the following features (as copied from the heading of the relevant chapters of the data sheet):

11.0 I/O PORTS

Each port has six standard registers for its operation. These registers are:

  • TRISx registers (data direction)

  • PORTx registers (reads the levels on the pins of the device)

  • LATx registers (output latch)

  • INLVLx (input level control)

  • ODCONx registers (open-drain)

  • SLRCONx registers (slew rate

Some ports may have one or more of the following additional registers. These registers are:

  • ANSELx (analog select)

  • WPUx (weak pull-up)

Ports that support analog inputs have an associated ANSELx register. When an ANSEL bit is set, the digital input buffer associated with that bit is disabled. Disabling the input buffer prevents analog signal levels on the pin between a logic high and low from causing excessive current in the logic input circuitry.

11.3.5 ANALOG CONTROL

The ANSELB register (Register 11-12) is used to configure the Input mode of an I/O pin to analog. Setting the appropriate ANSELB bit high will cause al digital reads on the pin to be read as ‘0’ and allow analog functions on the pin to operate correctly. The state of the ANSELB bits has no effect on digital out put functions. A pin with TRIS clear and ANSELB set wil still operate as a digital output, but the Input mode will be analog. This can cause unexpected behavior when exe- cuting read-modify-write instructions on the affected port.

Note: The ANSELB bits default to the Analog mode after Reset. To use any pins as digital general purpose or peripheral inputs, the corresponding ANSEL bits must be initialized to ‘0’ by user software.

12.0 PERIPHERAL PIN SELECT (PPS) MODULE

The Peripheral Pin Select (PPS) module connects peripheral inputs and outputs to the device I/O pins. Only digital signals are included in the selections. All analog inputs and outputs remain fixed to their assigned pins. Input and output selections are independent as shown in the simplified block diagram Figure 12-1.

12.2 PPS Outputs

Each I/O pin has a PPS register with which the pin output source is selected. With few exceptions, the port TRIS control associated with that pin retains control over the pin output driver. Peripherals that control the pin output driver as part of the peripheral operation will override the TRIS control as needed. These peripherals include:

  • EUSART (synchronous operation)

  • MSSP (I2C)

  • COG (auto-shutdown)

12.3 Bidirectional Pins

PPS selections for peripherals with bidirectional signals on a single pin must be made so that the PPS input and PPS output select the same pin. Peripherals that have bidirectional signals include:

  • EUSART (synchronous operation)

  • MSSP (I2C)

You should read the relevant parts of the data sheet, and set up all I/O configuration registers needed for your configuration. In this case, setting up ANSELB to enable the digital inputs, and setting up the PPS registers to route the input & output functions of the I2C module to the desired pins is missing. Also, the PPS registers are protected from undesired changes, so there is a locking mechanism you have to use before you could access the registers.

After making the changes above, the code should look something like this. Note that since the I2C messages aren't being properly acknowledged yet, the device may not be "visible" to the master, but you should at least see the light blink.

#include <xc.h>                 /* XC8 General Include File */
#include <pic16lf1709.h>        /* Definitions of I/O pins */

#pragma config WDTE = OFF

// I2C address is 7 bits: 1111110
#define I2C_ADDRESS 0x6E

typedef unsigned char byte;

void main(void) {

    /* configure ports */
    TRISBbits.TRISB4    = 1;    // set SDA to input
    TRISBbits.TRISB6    = 1;    // set SCL to input

    ANSELBbits.ANSB4    = 0;    // make sure SDA is set to digital

    TRISC               = 0x00; // set LEDs to output
    PORTC               = 0xFF; // initialize LEDs to OFF

    /* configure peripheral pin select */
    PPSLOCK             = 0x55;
    PPSLOCK             = 0xAA;
    PPSLOCK             = 0x00; // PPS is now unlocked
    SSPDATPPS           = 0x0C; // RB4 input is SDA (pg 140)
    SSPCLKPPS           = 0x0E; // RB6 input is SCL (pg 140)
    RB4PPS              = 0x11; // RB4 output is SDA (pg 141)
    RB6PPS              = 0x10; // RB6 output is SCL (pg 141)
    PPSLOCK             = 0x55;
    PPSLOCK             = 0xAA;
    PPSLOCK             = 0x01; // PPS is now locked

    /* configure MSSP module */
    SSPCON1bits.SSPEN   = 1;    // enable SSP module
    SSPCON1bits.SSPM    = 0x6;  // SSP is in I2C slave mode, 7-bit addressing
    SSP1ADD             = I2C_ADDRESS<<1;  // set the device address (left-aligned)
    SSPCON1bits.CKP     = 1;    // release clock

    while(1) {

        byte ssp_buf;

        if(SSPSTATbits.BF) {    // if the I2C buffer is not empty
            PORTC = 0x00;       // turn on LEDs for a moment
            for(int i=0; i<100; i++) _delay(250);
            PORTC = 0xFF;       // turn them back off
            ssp_buf = SSPBUF;   // read the buffer
                                // BF flag is cleared by hardware
        }

        SSPCON1bits.CKP = 1;    // finally, release clock

    }

}