Hi-tech C code for I2C communication

Before move to code I think it is better to write a brief introduction to I2C communication.

I²C (pronounced I-squared-C) created by Philips Semiconductors and commonly written as 'I2C' stands for Inter-Integrated Circuit and allows communication of data between I2C devices over two wires. It sends information serially using one line for data (SDA) and one for clock (SCL).There needs to be a third wire which is just the ground or 0 volts. There may also be a 5volt wire is power is being distributed to the devices. Both SCL and SDA lines are "open drain" drivers. What this means is that the chip can drive its output low, but it cannot drive it high. For the line to be able to go high you must provide pull-up resistors to the 5v supply. I2C bus can have one or more I2C devices connected.



Master and Slave
The devices on the I2C bus are either masters or slaves. The master is always the device that drives the SCL clock line. Slaves are the devices that respond to the master. A slave cannot initiate a transfer over the I2C bus, only a master can do that. There can be multiple slaves on the I2C bus. Also both slaves and masters can receive/send data over I2C. But always master initiate the data transfer over I2C bus.

Always I2C communication start with the start condition and terminate with the stop condition. Between these conditions you can transfer data between master and slave(s).

Addressing
Each slave device on the I2C bus should have a unique address. Then master can address these salve device individually for better communication between master and slave device. Always master initiate data transfer with the address to select a slave device on the bus for further communication.
Address byte contain 7bit address and data direction bit (R/W). This will initiate the specific slave device to be read or write operations.


START (S) and STOP (P) bits
START (S) and STOP (P) bits are unique signals that can be generated on the bus but only by a bus master.
This can be done at any time so you can force a restart if anything goes wrong even in the middle of communication.
START and STOP bits are defined as rising or falling edges on the data line while the clock line is kept high.
 And this is the only occurrence of falling or rising SDA line while SCL line in high. Therefore this can be captured by slave devices as start/stop condition.
Also microchip device support restart condition, which is simply send stop(P) condition and start(S) condition sequentially by the master.



Data
All data blocks are composed of 8 bits (1 Byte). The initial block has 7 address bits followed by a direction bit (Read or Write). Acknowledge bits are squeezed in between each block. Each data byte is transmitted MSB first including the address byte.

Acknowledge
The acknowledge bit (generated by the receiving device) indicates to the transmitter that the the data transfer was ok. Note that the clock pulse for the acknowledge bit is always created by the bus master.
The acknowledge data bit is generated by either the master or slave depending on the data direction. For the master writing to a slave the acknowledge is generated by the slave. For the master receiving data from a slave the master generates the acknowledge bit.

This acknowledge bit can  acknowledge(ACK) or not-acknowledge(NACK) depending on operations on the I2C bus.
  • To acknowledge the receiving device must put the SDA line into low with the next clock pulse.
  • To not-acknowledge  the receiving device must high SDA line with the next clock pulse (simply do nothing on the SDA line, then it will remain high) 
 When data receiving by the master not-acknowledge bit must generate by the master to indicate to slave, "there no need to send more data to me(master)". Then master must stop the I2C bus by sending STOP(P) condition.


Data transfer from master to slave



Data transfer from slave to master




I think now you have a brief idea about I2C communication. So let's move to code. This code was written in Hitech C for most popular PIC16f877 microcontroller which is developed by microchip Inc using its inbuilt MSSP(Master Synchronous Serial Port) unit.


Source code for complete I2C (Master) communication with PIC16f877


/**
 * Initialize the I2C module as a master mode

 * SCL - RC3
 * SDA - RC4
 * 100kHz I2C click at 4Mhz crystal
 */
void initializeI2C(){
    TRISC3 = 1;//make SCL as input
    TRISC4 = 1;//make SDA as intput

    //----------configs for SSPSTAT register------------
    SSPSTAT = 0b10000000;//SMP = 1 [Slew rate control disabled for standard speed mode (100 kHz and 1 MHz)]
    //--------------------------------------------------

    //---------- configs for SSPCON register------------
    //Synchronous Serial Port Mode Select bits
    //configs as I2C Master mode, clock = FOSC / (4 * (SSPADD+1))
    SSPM0 = 0;
    SSPM1 = 0;
    SSPM2 = 0;
    SSPM3 = 1;

    SSPEN = 1;//Enables the serial port and configures the SDA and SCL pins as the source of the serial port pins

    SSPOV = 0;//No overflow

    WCOL = 0;//No collision
    //--------------------------------------------------

    //---------- configs for SSPCON2 register-----------
    SSPCON2 = 0;//initially no operations on the bus
    //--------------------------------------------------

    SSPADD = 40;// 100kHz clock speed at 4Mhz cystal

    //----------PIR1-----------
    SSPIF = 0;//clear  Master Synchronous Serial Port (MSSP) Interrupt Flag bit

    //-----------PIR2-----------
    BCLIF = 0;//clear Bus Collision Interrupt Flag bit
}

/**
 * wait until I2C bus become idel
 */
void waitUntilIdel(){
    while(R_W || SEN || RSEN || PEN || RCEN || ACKEN){
        __delay_ms(1);
    }
}

/**
 * Send 8 bit(1 byte) through I2C bus
 * @param data - 8 bit data t be sent
 */
void i2CSend(unsigned char data){
    waitUntilIdel();
    SSPBUF = data;
    //while(BF) continue;// wait until complete this bit transmision
    waitUntilIdel();//wait until any pending transaction
}

/**
 * Send start condition to I2C bus
 */
void i2CStart(){
    waitUntilIdel();
    SEN = 1;//Initiate Start condition on SDA and SCL pins. Automatically cleared by hardware
    waitUntilIdel();
}

/**
 * Send stop condition to I2C bus
 */
void i2CStop(){
    waitUntilIdel();
    PEN = 1;//Initiate Stop condition on SDA and SCL pins. Automatically cleared by hardware.
    waitUntilIdel();
}

/**
 * Send restart condition to I2C bus
 */
void i2CRestart(){
    waitUntilIdel();
    RSEN = 1;//Initiate Repeated Start condition on SDA and SCL pins. Automatically cleared by hardware.
    waitUntilIdel();
}

/**
 * read the I2C bus
 * @return Read data from I2C slave
 */
unsigned char i2CRead(){
    unsigned char temp;

    waitUntilIdel();

    //configure MSSP for data recieving
    RCEN = 1;//Enables Receive mode for I2C

    waitUntilIdel();

    while(!BF);//wait for buffer full
    temp = SSPBUF;//read the buffer
    waitUntilIdel();//wait for any transaction
    return temp;//return the bufferd byte
}

/**
 * send acknowledge condition
 */
void i2CAck(){
    waitUntilIdel();
    ACKDT = 0;//Acknowledge Data bit(0 = Acknowledge)
    ACKEN = 1;//Initiate Acknowledge sequence on SDA and SCL pins, and transmit ACKDT data bit.Automatically cleared by hardware.
    waitUntilIdel();
}

/**
 * send not acknowledge condition
 */
void i2CNAck(){
    waitUntilIdel();
    ACKDT = 1;//Acknowledge data bit(1 = Not Acknowledge)
    ACKEN = 1;//Initiate Acknowledge sequence on SDA and SCL pins, and transmit ACKDT data bit.Automatically cleared by hardware.
    waitUntilIdel();
}


I think comments on each code line is better enough to understand these functions.
The MSSP module has three associated registers. These include a status register (SSPSTAT) and two control registers (SSPCON and SSPCON2). Above functions will configure these registers to use I2C communication in Master mode.

You can download complete source code with test program.

Next post will be a practical usage of I2C communication which use the RTC (Real Time Clock) IC DS1307.

8 comments:

  1. i try your code in pic18f452 but it is not working.every time the second register is outing value 0x55.i check all the i2c protocol conditins(device ack,data ack etc)
    everything working correctly but reading from ds1307 arising problems please help me.

    ReplyDelete
    Replies
    1. Did you get other values correctly?

      Delete
    2. sir am also having same problem but i got correct values.. when i am taking indivisually it was correct but i will make a loop to take all 6 values on that time i was getting some other values help e sir please


      Delete
  2. Do you can give me project ? Thanks you.

    ReplyDelete
  3. your email is : lam.3.ngon@gmail.com

    ReplyDelete
  4. Hi milinda,
    Please tell me the reason for generating the clock at 100khz, because i am using a device whose refresh rate is 1hz. should I have to adapt to a frequency of 1hz ?

    ReplyDelete
    Replies
    1. yes, you can change the clock frequency, by changing the value of "SSPADD". the equation for the clock frequency is,

      clock = FOSC/ (4 * (SSPADD+1))

      Delete