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
}
}
2 Answers 2
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
}
}
-
\$\begingroup\$ thank you! i'll set up the pps registers in the morning and give you that green check mark if it works. as for the ansel registers, i think digital is the default, but i'll go ahead and be explicit. \$\endgroup\$Woodrow Barlow– Woodrow Barlow2015年06月01日 04:10:46 +00:00Commented Jun 1, 2015 at 4:10
-
\$\begingroup\$ hrmmm. i also just noticed the address i'm using is reserved. i'll fix that too. \$\endgroup\$Woodrow Barlow– Woodrow Barlow2015年06月01日 05:04:21 +00:00Commented Jun 1, 2015 at 5:04
-
\$\begingroup\$ aha! i got the light to blink with your suggestions. \$\endgroup\$Woodrow Barlow– Woodrow Barlow2015年06月01日 15:17:46 +00:00Commented Jun 1, 2015 at 15:17
The bus capacitance affects the timing, so you need to set the master SDA start/stop hold times correctly, so that the slave can recognize the start and stop conditions.
-
\$\begingroup\$ can i set the slave to match the master instead? \$\endgroup\$Woodrow Barlow– Woodrow Barlow2015年06月01日 13:59:33 +00:00Commented Jun 1, 2015 at 13:59
-
\$\begingroup\$ I would add that an oscilloscope is usually not the best instrument to "see I2C data." Economically, try a Saleae or Rigol DS1054Z (with the data decode option) instead. \$\endgroup\$rdtsc– rdtsc2015年06月02日 17:29:01 +00:00Commented Jun 2, 2015 at 17:29
Explore related questions
See similar questions with these tags.
counter
which increments +1 eachwhile
iteration. Toggle the LED on (and resetcounter
) whenever data is received, but only toggle it off after 10,000 while-loops have commenced. \$\endgroup\$