Electrical – pic16 i2c interrupt

i2cinterruptspicpic16f

I’m trying to use the Microchip Code Configurator to make an I2C communication between two PIC16F1827. My application is very similar to the example they give with an emulated EEPROM. I've used Proteus to simulate the code but the resulat wasn't satisfying. So I have a question and a problem:

  • In the master code, the EEPROM address makes two bytes, but in the slave code, it seems to be just one byte. I don’t understand why. I’ve changed the master code and it seems to be ok now, but can someone explain me why the master code and the slave code are not compatible?

  • My real problem is about the I2C interrupts while reading. Actually, when I try to make a read of one byte (I assume that the EEPROM address is already set to the correct value), two interrupts occur. The first one after the Ack, then R_nW bit is set, D_nA and ACKSTAT bits are clear. Then, after the NAck (when the master received enough bytes, in this case just one), a second interrupt occurs. But the R_nW bit is cleared by hardware. So I can’t use the “I2C1_SLAVE_READ_COMPLETED” case. Worse, the “I2C1_SLAVE_WRITE_COMPLETED” case is executed. I don’t understand why this occurs. Of course, I can change the I2C1_ISR to prevent this problem, but I don’t think that is the best to solve the problem.

I would be really grateful if someone can help me. 🙂

EDIT : Following the suggestion of Olin Lathrop, I've written the code without using the MCC. I still have the same problem.

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <xc.h>

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection->INTOSC oscillator: I/O function on CLKIN pin
#pragma config WDTE = ON    // Watchdog Timer Enable->WDT disabled
#pragma config PWRTE = OFF    // Power-up Timer Enable->PWRT disabled
#pragma config MCLRE = OFF    // MCLR Pin Function Select->MCLR/VPP pin function is digital input
#pragma config CP = OFF    // Flash Program Memory Code Protection->Program memory code protection is disabled
#pragma config CPD = OFF    // Data Memory Code Protection->Data memory code protection is disabled
#pragma config BOREN = OFF    // Brown-out Reset Enable->Brown-out Reset disabled
#pragma config CLKOUTEN = OFF    // Clock Out Enable->CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin
#pragma config IESO = OFF    // Internal/External Switchover->Internal/External Switchover mode is disabled
#pragma config FCMEN = OFF    // Fail-Safe Clock Monitor Enable->Fail-Safe Clock Monitor is disabled

// CONFIG2
#pragma config WRT = OFF    // Flash Memory Self-Write Protection->Write protection off
#pragma config PLLEN = OFF    // PLL Enable->4x PLL disabled
#pragma config STVREN = OFF    // Stack Overflow/Underflow Reset Enable->Stack Overflow or Underflow will not cause a Reset
#pragma config BORV = LO    // Brown-out Reset Voltage Selection->Brown-out Reset Voltage (Vbor), low trip point selected.
#pragma config LVP = OFF    // Low-Voltage Programming Enable->High-voltage on MCLR/VPP must be used for programming

#define RX_ELMNTS   32
unsigned uint8_t I2C_slave_address = 0x03;         // slave address

volatile unsigned char I2C_Array[RX_ELMNTS] =   // array for master to write to
{0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88,
0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0xFA,
0xEA, 0xDA, 0xCA, 0xBA, 0xFB, 0xFC, 0xFD, 0xFE,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};

unsigned int index_i2c = 0;            // used as an index pointer in array
unsigned char junk = 0;                // used to place unnecessary data
unsigned char first = 1;               // used to determine whether data address 
                                       // location or actual data
unsigned char clear = 0x00;      // used to clear array location once
                                 // it has been read

void initialize(void);                 // Initialize routine

void main(void) {
    initialize();               // call initialize routine
    while (1)                   // main while() loop
    {
        asm("CLRWDT");          // while here just clear WDT and wait for
    }                           // the ISR to be called
} // end main

void initialize(void) {
    //uC SET UP
    OSCCON = 0b01111010;        // Internal OSC @ 16MHz
    OPTION_REG = 0b11010111;    // WPU disabled, INT on rising edge, FOSC/4
                                // Prescaler assigned to TMR0, rate @ 1:256
    WDTCON = 0b00010111;        // prescaler 1:65536 
                                // period is 2 sec(RESET value)
    PORTB = 0x00;               // Clear PORTC
    LATB = 0x00;                // Clear PORTC latches
    TRISB = 0b00010010;         // Set RC3, RC4 as inputs for I2C

    //I2C SLAVE MODULE SET UP
    SSP1STAT = 0b10000000;       // Slew rate control disabled for standard 
                                // speed mode (100 kHz and 1 MHz)
    SSP1CON1 = 0b00110110;       // Enable serial port, I2C slave mode, 
                                // 7-bit address
    SSP1CON2bits.SEN = 1;        // Clock stretching is enabled
    //SSP1CON3bits.BOEN = 1;       // SSPBUF is updated and NACK is generated 
                                // for a received address/data byte,
                                // ignoring the state of the SSPOV bit 
                                // only if the BF bit = 0
    //SSP1CON3bits.SDAHT = 1;      // Minimum of 300 ns hold time on SDA after 
                                // the falling edge of SCL
    //SSP1CON3bits.SBCDE = 1;      // Enable slave bus collision detect interrupts
    SSP1ADD = I2C_slave_address << 1; // Load the slave address
    SSP1IF = 0;                  // Clear the serial port interrupt flag
    BCL1IF = 0;                  // Clear the bus collision interrupt flag
    BCL1IE = 1;                  // Enable bus collision interrupts
    SSP1IE = 1;                  // Enable serial port interrupts
    PEIE = 1;                   // Enable peripheral interrupts
    GIE = 1;                    // Enable global interrupts
}//end initialize

void interrupt ISR() {
    if (SSP1IF) {
        if (SSP1STATbits.R_nW) {
            if (!SSP1STATbits.D_nA) {
                // read and adress
                SSP1BUF = I2C_Array[index_i2c++]; 
                SSP1CON1bits.CKP = 1; 
            }
            if (SSP1STATbits.D_nA) {
                //read and data
                SSP1BUF = I2C_Array[index_i2c++]; 
                SSP1CON1bits.CKP = 1; 
            }
        }
        if (!SSP1STATbits.R_nW) {
            if (!SSP1STATbits.D_nA) {
                //write and adress
                first = 1; 
                junk = SSP1BUF; 
                SSP1CON1bits.CKP = 1; 
            }
            if (SSP1STATbits.D_nA) {
                //write and data
                if (first) {
                    index_i2c = SSP1BUF; 
                    first = 0; 
                } else {
                    if (index_i2c < RX_ELMNTS) {
                        I2C_Array[index_i2c++] = SSP1BUF; 
                    } else {
                        junk = SSP1BUF; 
                    }
                }
                if (SSP1CON1bits.WCOL) {
                    SSP1CON1bits.WCOL = 0; 
                    junk = SSP1BUF; 
                }
                SSP1CON1bits.CKP = 1; 
            }
        }
    }
    if (BCL1IF) {
        junk = SSP1BUF; 
        BCL1IF = 0; 
        SSP1CON1bits.CKP = 1; 
    }
    SSP1IF = 0; 
}

Best Answer

The data sheet is clear on how the R/nW bit behaves.

This bit holds the R/nW bit information following the last address match. This bit is only valid from the address match to the next Start bit, Stop bit, or not ACK bit.

This implies that as soon as the master sends the NACK for data read is complete the R/nW bit is no longer valid.

You also need a handler for the ACK/NACK cases. This appears to be missing from your code along with any collision or error checking. Your could use the ACKSTAT register to figure out what mode your in.

My ultimate suggestion would be to have a global set of state variables. Use these variables from within the interrupt to know what state your in and then check the appropriate registers to figure out what state you need to go to.

For example: i'm in transmitting data to master mode. When the interrupt fires check ACKSTAT, if acknowledge was recieved get ready for sending another byte. If acknowledge was not received change mode to transmission complete.