I2C Master - Works at first but breaks down over time?

I2C Master - Works at first but breaks down over time?

Postby spellbee2 » Mon Apr 25, 2016 11:29 pm

Hi all,

I'm working with a PIC18F4520 microcontroller. I'm trying to make a device that reads data from a 9DOF accelerometer from Adafruit every tenth of a second via I2C, stores the data in external SRAM via SPI, and transmits it via Bluetooth UART. Currently, however, I'm having problems with the I2C capabilities of the MSSP module of the microcontroller in reading/writing to the accelerometer.

According to the MPLAB debugger, the program gets hung up during one of the read statements, but when I hooked up an oscilloscope to check the I2C operations in depth, I found that the communication breaks down well before the read statement. Below are my oscilloscope readings, in each SDA is on the top and SCL is on the bottom.

This is the start of the Init_Sensors() function, with the first I2CRead() and the start of the following I2CWrite(). Everything works as expected.


After about 0.5ms, however, the communication breaks down, as shown here.


Here's a zoomed-in picture of the crash. You'll notice that SCL goes kinda crazy after the first 4 clock cycles, and SDA gets stuck low in the aftermath.


I currently have 2.2K pullup resistors on each line (I also tried 4.7K resistors with the same result). The microcontroller and the accelerometer are the only devices connected to the MSSP (the SPI SRAM is bitbanged elsewhere, and the BlueTooth is handled by the EUSART module). I'm also only using the SDA and SCL pins on the accelerometer - none of the interrupt pins are connected. The oddest part to me is how it consistently breaks down 0.5ms after the start of the I2C communication, so I disabled Timer1 (the 0.1sec period timer for retrieving data from the accelerometer) until after the Init_Sensors() function, with no luck.

Below is my code. I only included the microcontroller initialization and the I2C-relevant portions, but let me know if you guys need any other sections of code to solve this.

Code: Select all
#include <xc.h>
#include "i2c.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "Sensor.h"

// PIC18F4520 Configuration Bit Settings

// 'C' source line config statements

#include <xc.h>

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#pragma config OSC = INTIO67 // Oscillator Selection bits (Internal oscillator block, port function on RA6 and RA7)
#pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

#pragma config PWRT = OFF // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = SBORDIS // Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled))
#pragma config BORV = 3 // Brown Out Reset Voltage bits (Minimum setting)

#pragma config WDT = OFF // Watchdog Timer Enable bit (WDT disabled)
#pragma config WDTPS = 32768 // Watchdog Timer Postscale Select bits (1:32768)

#pragma config CCP2MX = PORTC // CCP2 MUX bit (CCP2 input/output is multiplexed with RC1)
#pragma config PBADEN = OFF // PORTB A/D Enable bit (PORTB<4:0> pins are configured as digital I/O on Reset)
#pragma config LPT1OSC = OFF // Low-Power Timer1 Oscillator Enable bit (Timer1 configured for higher power operation)
#pragma config MCLRE = ON // MCLR Pin Enable bit (MCLR pin enabled; RE3 input pin disabled)

#pragma config STVREN = ON // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = ON // Single-Supply ICSP Enable bit (Single-Supply ICSP enabled)
#pragma config XINST = OFF // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

#pragma config CP0 = OFF // Code Protection bit (Block 0 (000800-001FFFh) not code-protected)
#pragma config CP1 = OFF // Code Protection bit (Block 1 (002000-003FFFh) not code-protected)
#pragma config CP2 = OFF // Code Protection bit (Block 2 (004000-005FFFh) not code-protected)
#pragma config CP3 = OFF // Code Protection bit (Block 3 (006000-007FFFh) not code-protected)

#pragma config CPB = OFF // Boot Block Code Protection bit (Boot block (000000-0007FFh) not code-protected)
#pragma config CPD = OFF // Data EEPROM Code Protection bit (Data EEPROM not code-protected)

#pragma config WRT0 = OFF // Write Protection bit (Block 0 (000800-001FFFh) not write-protected)
#pragma config WRT1 = OFF // Write Protection bit (Block 1 (002000-003FFFh) not write-protected)
#pragma config WRT2 = OFF // Write Protection bit (Block 2 (004000-005FFFh) not write-protected)
#pragma config WRT3 = OFF // Write Protection bit (Block 3 (006000-007FFFh) not write-protected)

#pragma config WRTC = OFF // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) not write-protected)
#pragma config WRTB = OFF // Boot Block Write Protection bit (Boot block (000000-0007FFh) not write-protected)
#pragma config WRTD = OFF // Data EEPROM Write Protection bit (Data EEPROM not write-protected)

#pragma config EBTR0 = OFF // Table Read Protection bit (Block 0 (000800-001FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF // Table Read Protection bit (Block 1 (002000-003FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF // Table Read Protection bit (Block 2 (004000-005FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF // Table Read Protection bit (Block 3 (006000-007FFFh) not protected from table reads executed in other blocks)

#pragma config EBTRB = OFF // Boot Block Table Read Protection bit (Boot block (000000-0007FFh) not protected from table reads executed in other blocks)

//Create meaningful names for ports
//SPI Control
#define PIN_SPI_SCK PORTAbits.RA0
#define PIN_SPI_SDI PORTAbits.RA1 //MasterIn,SlaveOut
#define PIN_SPI_SDO PORTAbits.RA2 //MasterOut,SlaveIn
#define PIN_BT_CTS PORTAbits.RA3
//Interrupt pins and flags
#define PIN_BT_RTS PORTBbits.RB0
#define FLAG_INT_SSP PIR1bits.SSPIF //Accelerometer I2C interrupt
#define FLAG_INT_TMR1 PIR1bits.TMR1IF
#define _XTAL_FREQ 8000000

typedef struct{
    short mag_x;
    short mag_y;
    short mag_z;
    short acc_x;
    short acc_y;
    short acc_z;
    short gyr_x;
    short gyr_y;
    short gyr_z;
} accel_data;

//Global Variables
accel_data curr_packet;
MagGain mag_curr_gain = MAGGAIN_1_3;
GyroRange gyr_curr_range = GYRO_RANGE_250DPS;
unsigned short pkt_to_write;
unsigned short pkt_to_send;
float Accel_MG_LSB = 0.001F;
float Mag_Gauss_LSB_XY = 1100.0F;
float Mag_Gauss_LSB_Z = 980.F;

//Function Prototypes
unsigned char Init_Sensors();
accel_data getSensorData();
char DataToMem(unsigned short);
char MemtoBT(unsigned short);
int Mem_Write(const unsigned long, const unsigned char);
unsigned char Mem_Read(const unsigned long);
unsigned char UART_Init(const long int);
char BT_Send(const char* , size_t);
void UART_Send(const unsigned char);
unsigned char UART_Rec();
unsigned char I2C_Write(const unsigned char, const unsigned char, const unsigned char);
unsigned char I2C_Read(const unsigned char, const unsigned char);
void setMagGain(MagGain);

void main(void) {

    //Internal Oscillator Control - set 8MHz
    OSCCONbits.IRCF2 = 1;
    OSCCONbits.IRCF1 = 1;
    OSCCONbits.IRCF0 = 1;
    OSCTUNEbits.PLLEN = 1;
    //Timer1 - used to fetch data from Accel every 0.1 seconds
    //Timer1 Registers Prescaler= 4 - TMR1 Preset = 15536 - Freq = 10.00 Hz - Period = 0.100000 seconds
    T1CONbits.T1CKPS1 = 1; // bits 5-4 Prescaler Rate Select bits
    T1CONbits.T1CKPS0 = 1; // bit 4
    T1CONbits.T1OSCEN = 1; // bit 3 Timer1 Oscillator Enable Control bit 1 = on
    T1CONbits.T1SYNC = 1; // bit 2 Timer1 External Clock Input Synchronization Control bit...1 = Do not synchronize external clock input
    T1CONbits.TMR1CS = 0; // bit 1 Timer1 Clock Source Select bit...0 = Internal clock (FOSC/4)
    T1CONbits.TMR1ON = 0; // bit 0 enables timer
    TMR1H = 0x3C; // preset for timer1 MSB register
    TMR1L = 0xB0; // preset for timer1 LSB register

    // Interrupt Registers
    PIR1bits.TMR1IF = 0; // clear timer1 interrupt flag TMR1IF
    PIE1bits.TMR1IE = 0; // disable Timer1 interrupts
    //Define I/O tri-state buffers
    ADCON1 = 0x0F;
    TRISA = 0b00000010; //RA1 is input, rest output
    //TRISB = 0b11111111; //All PORTB I/Os are inputs
    TRISB = 0b11111101; //DEBUG CODE - PORTB1 used for LED output
    LATBbits.LATB1 = 1;
    TRISCbits.TRISC7 = 1; //Configure UART pins as inputs, enable serial
    TRISCbits.TRISC6 = 1;
    RCSTAbits.SPEN = 1;
    TXSTAbits.SYNC = 0;
    TXSTAbits.TXEN = 0;
    //Configure interrupts
    INTCON = 0b11010000; //Enable interrupts and peripheral interrupts
    INTCONbits.INT0E = 0;
    INTCON2 = 0b00110000; //Enable INT0 on falling edge, rest on rising edge
    INTCON3 = 0b00000000; //NOTE: change to 0b00001000 if enabling INT1 (RB1)
    //MSSP Control
    SSPCON1 = 0b00101000; //Sets mode to I2C master mode
    SSPCON2 = 0b00000000;
    T1CONbits.TMR1ON = 1; // bit 0 enables timer
    INTCONbits.INT0E = 1;
    PIE1bits.TMR1IE = 1; // enable Timer1 interrupts

void interrupt isr() {
    if(FLAG_INT_TMR1 == 1){
        FLAG_INT_TMR1 = 0;
        TMR1H = 0x3C;
        TMR1L = 0xB0;
        curr_packet = getSensorData();
        pkt_to_write += 1;
    }else if(FLAG_INT_BT){
        FLAG_INT_BT = 0;
        pkt_to_send += 1;
        if(pkt_to_send >= pkt_to_write){
            pkt_to_write = 0;
            pkt_to_send = 0;

unsigned char Init_Sensors(){
    char id = I2C_Read(GYRO_ADDRESS,GYRO_REG_WHO_AM_I);
    //if ((id != GYRO_L_ID) && (id != GYRO_H_ID)) return (char)0;
    //Accelerometer - I had already commented this out when I was testing it.
    //I've left it commented out for consistency's sake, but it's safe to assume fixing
    //the main problem will fix this, since it deals with the same I2C functions as above and below.
// if(id != 0x57) return (char)0;
    I2C_Write(MAG_ADDRESS,MAG_REG_MR_REG,0x00); //<-- I2C Communication Freezes Here
    if(id != 0x10) return (char)0;
    PIN_BT_CTS = 0;
    return (char)1;

Below are my I2C_Write and I2C_Read functions, based on the i2c.h library.
Code: Select all
unsigned char I2C_Write(const unsigned char devAddr, const unsigned char regAddr, const unsigned char writeData){
    WriteI2C(devAddr << 1);
    return 0;

unsigned char I2C_Read(const unsigned char devAddr, const unsigned char regAddr){
    unsigned char readData;
    WriteI2C(devAddr << 1);
    WriteI2C((devAddr << 1) | 0x01);
    readData = ReadI2C();
    return readData;

And here's my header file with all my register definitions, for those curious. These were taken from the Adafruit libraries for the accelerometers, though I made some slight edits with the names.
Code: Select all
// This is a guard condition so that contents of this file are not included
// more than once.

#include <xc.h> // include processor files - each processor file is guarded.

/* Constants */
#define SENSORS_GRAVITY_EARTH (9.80665F) /**< Earth's gravity in m/s^2 */
#define SENSORS_GRAVITY_MOON (1.6F) /**< The moon's gravity in m/s^2 */
#define SENSORS_GRAVITY_SUN (275.0F) /**< The sun's gravity in m/s^2 */
#define SENSORS_MAGFIELD_EARTH_MAX (60.0F) /**< Maximum magnetic field on Earth's surface */
#define SENSORS_MAGFIELD_EARTH_MIN (30.0F) /**< Minimum magnetic field on Earth's surface */
#define SENSORS_PRESSURE_SEALEVELHPA (1013.25F) /**< Average sea level pressure is 1013.25 hPa */
#define SENSORS_DPS_TO_RADS (0.017453293F) /**< Degrees/s to rad/s multiplier */
#define SENSORS_GAUSS_TO_MICROTESLA (100) /**< Gauss to micro-Tesla multiplier */

//Device/Register Info
#define GYRO_ADDRESS (0x6B) // 1101011x
#define GYRO_POLL_TIMEOUT (100) // Maximum number of read attempts
#define GYRO_L_ID 0xD4
#define GYRO_H_ID 0xD7
#define GYRO_SENSITIVITY_250DPS (0.00875F) // Roughly 22/256 for fixed point match
#define GYRO_SENSITIVITY_500DPS (0.0175F) // Roughly 45/256
#define GYRO_SENSITIVITY_2000DPS (0.070F) // Roughly 18/256
#define ACCEL_ADDRESS (0x19) // 0011001x
#define MAG_ADDRESS (0x1E) // 0011110x

#define true 1
#define false 0
//Register names for different sensors

//Gyroscope // DEFAULT TYPE
      #define GYRO_REG_WHO_AM_I 0x0F // 11010100 r
      #define GYRO_REG_CTRL_REG1 0x20 // 00000111 rw
      #define GYRO_REG_CTRL_REG2 0x21 // 00000000 rw
      #define GYRO_REG_CTRL_REG3 0x22 // 00000000 rw
      #define GYRO_REG_CTRL_REG4 0x23 // 00000000 rw
      #define GYRO_REG_CTRL_REG5 0x24 // 00000000 rw
      #define GYRO_REG_REFERENCE 0x25 // 00000000 rw
      #define GYRO_REG_OUT_TEMP 0x26 // r
      #define GYRO_REG_STATUS_REG 0x27 // r
      #define GYRO_REG_OUT_X_L 0x28 // r
      #define GYRO_REG_OUT_X_H 0x29 // r
      #define GYRO_REG_OUT_Y_L 0x2A // r
      #define GYRO_REG_OUT_Y_H 0x2B // r
      #define GYRO_REG_OUT_Z_L 0x2C // r
      #define GYRO_REG_OUT_Z_H 0x2D // r
      #define GYRO_REG_FIFO_CTRL_REG 0x2E // 00000000 rw
      #define GYRO_REG_FIFO_SRC_REG 0x2F // r
      #define GYRO_REG_INT1_CFG 0x30 // 00000000 rw
      #define GYRO_REG_INT1_SRC 0x31 // r
      #define GYRO_REG_TSH_XH 0x32 // 00000000 rw
      #define GYRO_REG_TSH_XL 0x33 // 00000000 rw
      #define GYRO_REG_TSH_YH 0x34 // 00000000 rw
      #define GYRO_REG_TSH_YL 0x35 // 00000000 rw
      #define GYRO_REG_TSH_ZH 0x36 // 00000000 rw
      #define GYRO_REG_TSH_ZL 0x37 // 00000000 rw
      #define GYRO_REG_INT1_DURATION 0x38 // 00000000 rw

//Accelerometer // DEFAULT TYPE
      #define ACCEL_REG_CTRL_REG1 0x20 // 00000111 rw
      #define ACCEL_REG_CTRL_REG2 0x21 // 00000000 rw
      #define ACCEL_REG_CTRL_REG3 0x22 // 00000000 rw
      #define ACCEL_REG_CTRL_REG4 0x23 // 00000000 rw
      #define ACCEL_REG_CTRL_REG5 0x24 // 00000000 rw
      #define ACCEL_REG_CTRL_REG6 0x25 // 00000000 rw
      #define ACCEL_REG_REFERENCE 0x26 // 00000000 r
      #define ACCEL_REG_STATUS_REG 0x27 // 00000000 r
      #define ACCEL_REG_OUT_X_L 0x28
      #define ACCEL_REG_OUT_X_H 0x29
      #define ACCEL_REG_OUT_Y_L 0x2A
      #define ACCEL_REG_OUT_Y_H 0x2B
      #define ACCEL_REG_OUT_Z_L 0x2C
      #define ACCEL_REG_OUT_Z_H 0x2D
      #define ACCEL_REG_FIFO_CTRL_REG 0x2E
      #define ACCEL_REG_FIFO_SRC_REG 0x2F
      #define ACCEL_REG_INT1_CFG 0x30
      #define ACCEL_REG_INT1_SOURCE 0x31
      #define ACCEL_REG_INT1_THS 0x32
      #define ACCEL_REG_INT1_DURATION 0x33
      #define ACCEL_REG_INT2_CFG 0x34
      #define ACCEL_REG_INT2_SOURCE 0x35
      #define ACCEL_REG_INT2_THS 0x36
      #define ACCEL_REG_INT2_DURATION 0x37
      #define ACCEL_REG_CLICK_CFG 0x38
      #define ACCEL_REG_CLICK_SRC 0x39
      #define ACCEL_REG_CLICK_THS 0x3A
      #define ACCEL_REG_TIME_LIMIT 0x3B
      #define ACCEL_REG_TIME_LATENCY 0x3C
      #define ACCEL_REG_TIME_WINDOW 0x3D
      #define MAG_REG_CRA_REG 0x00
      #define MAG_REG_CRB_REG 0x01
      #define MAG_REG_MR_REG 0x02
      #define MAG_REG_OUT_X_H 0x03
      #define MAG_REG_OUT_X_L 0x04
      #define MAG_REG_OUT_Z_H 0x05
      #define MAG_REG_OUT_Z_L 0x06
      #define MAG_REG_OUT_Y_H 0x07
      #define MAG_REG_OUT_Y_L 0x08
      #define MAG_REG_SR_REG_Mg 0x09
      #define MAG_REG_IRA_REG 0x0A
      #define MAG_REG_IRB_REG 0x0B
      #define MAG_REG_IRC_REG 0x0C
      #define MAG_REG_TEMP_OUT_H 0x31
      #define MAG_REG_TEMP_OUT_L 0x32

    typedef enum
      MAGGAIN_1_3 = 0x20, // +/- 1.3
      MAGGAIN_1_9 = 0x40, // +/- 1.9
      MAGGAIN_2_5 = 0x60, // +/- 2.5
      MAGGAIN_4_0 = 0x80, // +/- 4.0
      MAGGAIN_4_7 = 0xA0, // +/- 4.7
      MAGGAIN_5_6 = 0xC0, // +/- 5.6
      MAGGAIN_8_1 = 0xE0 // +/- 8.1
    } MagGain;

    typedef enum
      GYRO_RANGE_250DPS = 250,
      GYRO_RANGE_500DPS = 500,
      GYRO_RANGE_2000DPS = 2000
    } GyroRange;

I am pretty much at my wits end with this, so any advice on what to try next is greatly appreciated. Thanks in advance!
Posts: 2
Joined: Mon Apr 25, 2016 11:26 pm
PIC experience: EE Student

Re: I2C Master - Works at first but breaks down over time?

Postby drh » Tue Apr 26, 2016 3:26 pm

Did you set the I2C speed in the SSPADD register?
User avatar
Verified identity
Posts: 53
Joined: Tue May 27, 2014 3:31 pm
Location: Laguna Niguel, Calif.
PIC experience: Professional 5+ years with MCHP products

Re: I2C Master - Works at first but breaks down over time?

Postby spellbee2 » Wed Apr 27, 2016 4:29 am

Shortly after posting this actually, I discovered that I forgot to set the I2C speed in the SSPADD register. I for some reason thought i2c.h's Open_I2C() function would take care of that, but it looks like I was wrong. Funny, I spend a week troubleshooting everything down to the pullup resistor values, and it was just one line of code I forgot. :P

Thanks for the help!
Posts: 2
Joined: Mon Apr 25, 2016 11:26 pm
PIC experience: EE Student

Re: I2C Master - Works at first but breaks down over time?

Postby ric » Fri Apr 29, 2016 12:11 am

Also, your read routine doesn't seem to be following I2C rules.
Each Master I2C read byte should be followed by an ACK or NAK cycle.
Intermediate bytes get ACKs, and the final byte should get a NAK, just before sending the STOP.
You are only reading a single byte, then sending a STOP. You have a call to send an ACK commented out.
In your case, there should be a call to send a NAK there.
My new leslie-special website (for fans of The Great Race) :)
User avatar
Verified identity
Posts: 347
Joined: Sat May 24, 2014 2:35 pm
Location: Melbourne, Australia
PIC experience: Professional 5+ years with MCHP products

Return to SSP (IIC, SPI)

Who is online

Users browsing this forum: No registered users and 1 guest