I2C protocol Programming

Thread Starter

@vajra

Joined May 2, 2018
154
I am looking basic idea to implement i2c Protocol in C programing. I am trying to make dummy code so it would help me to write original program

Consider: Master Transmit/Slave Receive
My program supposed to do following things.
  1. Master start the communication by generating a Start condition
  2. Master sends address to the Slave,
  3. After receiving data, Slave generate an ACK /NACK,
  4. Check If slave generate ACK, then again Master sends data to the Slave,
  5. check if slave generate NACK then Master closes the communication by generating a Stop condition.
Dummy code
Code:
void main (void)
{
  void i2c_init(void)                         //I2C initilaization
  int i2c_start()                             //I2C communication start
  void i2c_stop(void)                         //Stop the I2C communication
}
I started to write the function required in program. This is only few I know I have to add others but I don't have any idea about that

I am bit confused, How to convert this two statement into program
  1. generate ACK or NACK.
  2. check ACK or NACK

Could any one help me with dummy I2C program in C ?

Note I have not mention controller and slave name because I am looking general idea for all device. It may be difficult to give me advice but I am expecting logic to implement program not original program
 
Last edited:

Thread Starter

@vajra

Joined May 2, 2018
154
Everything you want to know about I2C is in the attached spec.
There are lots of examples with C coding here on AAC. Just search 'I2C'. Several links are in the 'Similar Threads' window below.
Good luck!
JohnInTX, thanks for the response!

I've been looking at different source code on google as well as forum. I'm struggling with specific question

I'm stuck with following question in programming
  1. Write i2c program to generate ACK or NACK.
  2. Write i2c program to check ACK or NACK
 

JohnInTX

Joined Jun 26, 2012
4,787
Here is a list of threads here on AAC that go through the steps of writing I2C in C, including the answers to your questions. Give them a look, write your own version of the code then post any specific problems. I believe the poster, @Parth786 went to the same school as you.
https://forum.allaboutcircuits.com/search/6963933/?q=I2C&o=relevance&c[user][0]=454552

A good link from that thread is this one:
http://www.8051projects.net/wiki/DS1307_I2C_RTC_Interfacing_Tutorial

Post your best shot and we'll take a look.
 
Last edited:

Thread Starter

@vajra

Joined May 2, 2018
154
write your own version of the code then post any specific problems.

Post your best shot and we'll take a look.
I have looked the information given by you as well as the information available on the internet. It would be difficult for me to write the complete code so I have thought of making a required routine first.

I have made the following routine for I2C Protocol. If these are correct I would be use these routines in main function.

This is my attempt to getting start
C:
//This function is used to initialize the I2C//
void I2C_Init(void)
{
    SDA = 1;
    SCL = 1;
}
//This function is used to generate I2C Start Condition//
void I2C_Start(void)
{
    SCL = 0;        // Set SCL low
    SDA = 1;        // Set SDA High
   
    Delay(1);
    SCL = 1;        //Set SCL high
    Delay(1);
    SDA = 0;        //Now Set SDA LOW, to generate the Start Condition
    Delay(1);
    SCL = 0;        //Clear the SCL to complete the cycle
}

//This function is used to generate I2C Stop Condition//
void I2C_Stop(void)
{
    SCL = 0;            // Set SCL low
    Delay(1);
    SDA = 0;            // Set SDA  low
    Delay(1);
    SCL = 1;            // Set SCL High
    Delay(1);
    SDA = 1;            // Now Set SDA High, to generate the Stop Condition
}
//This function is used to send a 8 bit data//
void I2C_Write_Byte(unsigned int Data)
{
    SDA = Data;
}

//This function is used to generate a the Positive ACK//
void I2C_ACK(void)
{
    SDA = 0;     
    Delay(1);
    SCL = 1;     
    Delay(1);
    SCL = 0;
    SDA = 1; 
}


//This function is used to generate a the NACK//
void I2C_NACK(void)
{
   SDA = 1;     
   Delay(1);
   SCL = 1;     
   Delay(1);
   SCL = 0;
   SCL = 1;  
}
 

JohnInTX

Joined Jun 26, 2012
4,787
Those functions look OK. But you are kind of starting at the bottom and working up when a better way to learn this stuff is start at the top (big picture) and work down (details). Let's look at your first post.
  • Master start the communication by generating a Start condition
  • Master sends address to the Slave,
  • After receiving data, Slave generate an ACK /NACK,
  • Check If slave generate ACK, then again Master sends data to the Slave,
  • check if slave generate NACK then Master closes the communication by generating a Stop condition.
That's closer to the top but still not there. Take the big picture. You want to:
1) Read an arbitrary amount of data from an I2C device
2) Write an arbitrary amount of data to an I2C device.

To do that, we need some information about I2C:
  • It's a 2 wire bus.
  • There can be multiple devices on the same 2 wire bus so some form of addressing is necessary.
  • Data goes both ways (read and write) on that bus - that implies some sort of protocol or set of rules so that the devices on the bus know what to do (I2C_start, I2C_stop etc)
  • The master device is the one that generates the clock (SCL). The slave device is driven by SCL.
  • The data line (SDA) goes both ways (read, write, ACK, NAK) according to the protocol.
  • I2C provides signals that control the data flow - ACK, NAK, SCL stretching etc.
  • To perform all of that, the bus is designed so that it is pulled up passively (with pull up resistors) and pulled down actively. The bus is never driven high actively. Flow control and ACK/NAK depend on the ability for any device to pull SCL or SDA down.
  • An IDLE bus means SCL and SDA are both high (bus not driven, pulled up by the resistors).
Whew! That's a lot. Let's get to it.

2 Wires: check. Passive pullups, check. That means that your software will actively drive the pin low for a '0' and let it float for a '1'. Depending on your processor, that may mean changing TRIS bits (for PIC) or writing a '1' for 8051 or whatever.

Addressing: Every slave on the bus has a unique hardware address and in most slaves, the address is extended to contain a block of addresses within the slave. In EEPROMs the hardware address might contain a fixed identifier + a few bits that match how physical pins are strapped on the chip + the actual addres(s) of the memory itself. For an RTC like the DS1307, the slave address would be a combination that included which internal register to access (Hour, Minute, Day, Date etc). It depends on the chip. Simple chips/small EEPROMs can use a simple 7 bit address. Bigger/more advanced stuff will use additional address bytes as required to specify the destination inside the slave. The address byte(s) are sent right after the Start condition.

The bus direction (reading or writing) is determined by the LSbit of the first address byte. If 1, the entire transaction is READ, if 0, its WRITE. Note that you can't read and write on one transaction. You have to complete the transaction using stoP or separate write from read using a repeat Start (rS).

Protocol: I2C runs with a set of rules:
Clocking data - data can change only when SCL is low. Data is clocked when SCL goes from LOW-HIGH-LOW. There are 8 bits to each data byte. An additional 9th bit is appended to the data byte to generate ACK and NAK. More on that later.
Start and stoP - each data transfer is defined as between the Start and stoP conditions. S and P are unique signals on the bus. Both are generated when SCL=1:
  • S is generated when SDA goes 1-0 when SCL=1
  • P is generated when SDA goes 0-1 when SCL=1
  • After a Start, the bus is considered 'BUSY' until the stoP condition when it is again IDLE. That allows multiple transactions to take place on the bus like write-then-read.
  • A repeat Start (rS) can be used to restart transactions without releasing the bus. This is common for reading slaves when the address to read from has to be first written to the slave. The subsequent read requires an rS to change the direction of the bus from writing the address to reading the data at that address.
ACK/NAK:
  • Each byte sent by the master is ACKnowledged or NotACKnowledged (ACK or NAK) by the slave after the master sends 8 bits of data. The master then releases SDA (lets it float) and clocks SCL a 9th bit:
    • If the slave is happy and can take more data, it ACKs the 9th bit by pulling SDA down during the 9th clock.
    • If the slave wants to terminate transmission it lets SDA float high during the 9th clock - NAK.
    • After the 9th clock, the master takes control of SDA again.
  • During the 9th clock, the master reads SDA for ACK/NAK (0 or 1)
    • If ACK, the master continues it's transmission or terminates if it has nothing more to send.
    • If NAK, the master stops transmitting and generates a stoP, returning the bus to IDLE.
    • Note that if NO SLAVE is present on the bus (or at that address), a NAK will be generated by default and the master knows there is no slave there.
TRANSACTIONS:
A transaction is whatever happens between Start and stoP.

A typical WRITE transaction looks like this:
  • Verify the bus is IDLE (SDA and SCL are '1')
  • Generate the Start condition.
  • Send the address byte(s). Specify WRITE by setting the LSbit of the first address byte to 0.
  • Change SDA to an input and raise SCL. Read SDA:
    • If SDA = 1, that is a NAK. Terminate the transaction and send a stoP to free the bus.
    • If SDA = 0, that is an ACK, the slave is there and can take more data - proceed.
  • Send the 2ed address byte as needed, verify ACK/NAK.
  • Since you are writing - send more data. Read the ACK/NAK after each byte until
    • All data has been sent *OR*
    • The slave sends a NAK (no more data, please).
  • Either way, terminate the transaction by generating the stoP condition.
  • The bus is now IDLE
A typical READ transaction looks like this:
  • Verify the bus is IDLE
  • Generate the Start condition.
  • Send the address byte(s). Specify READ by setting the LSbit of the first address byte to 1.
  • Get ACK/NAK as before. Continue or stop as indicated.
  • Note that in most reads, the slave address is usually one byte to select the chip. The actual read address is usually set by a previous write.
  • To read a byte:
    • Set SDA to an input
    • Generate SCL=1 and read SDA. That's the first data bit.
    • Drop SCL.
    • Generate 7 more SCL and read SDA each time to get the rest of the bits
  • To ACK/NAK the reception:
    • To ACK, pull SDL low and generate one clock on SCL
    • To NAK, float SDA so it goes high and generate one clock.
  • NAK the last byte received from the slave
  • Generate stoP.
Note that reading data usually involves:
  • Setting up an address pointer in the slave using a WRITE transaction
  • Generating a repeat Start to start a new transaction
  • Reading from the slave at the preset pointer. The slave automatically increments.
  • ACKing each byte read.
  • NAKing the last byte to end the transaction.
  • Generating stoP to return the bus to IDLE.

Wow.. that's a lot but that's I2C in a nutshell. From this description it looks like a good mix of functions would include:
  • Init: set the bus to IDLE
  • Generate
    • Start
    • stoP
    • repeat Start
    • get ACK/NAK from a slave being written
    • generate ACK/ANK to a slave being read
    • write the first byte of a a slave address with R/W- bit to specify read or write transactions
    • write a data byte to a slave - maybe combine with get ACK/ANK
    • read a data byte from a slave - maybe combine with generate ACK/NAK.
After that, it's pretty much loops and data. I've given you enough to write your own functions. If you decide to scab them off the inter-toobs, have at it but that's your choice. It's more fun to do it yourself and sooner or later, you'll have to. It's not a bad idea to learn from other's code but never just copy and paste and hope to get something working. That almost never happens.

At any rate, what I've shown is a general top down design process that will help you solve many programming problems. Start big, drill down to the details but always have a design and a plan to work from.

Hope that helps.
Have fun!
 

Associate

Joined Feb 26, 2020
1
I am looking basic idea to implement i2c Protocol in C programing. I am trying to make dummy code so it would help me to write original program

Consider: Master Transmit/Slave Receive
My program supposed to do following things.
  1. Master start the communication by generating a Start condition
  2. Master sends address to the Slave,
  3. After receiving data, Slave generate an ACK /NACK,
  4. Check If slave generate ACK, then again Master sends data to the Slave,
  5. check if slave generate NACK then Master closes the communication by generating a Stop condition.
Dummy code
Code:
void main (void)
{
  void i2c_init(void)                         //I2C initilaization
  int i2c_start()                             //I2C communication start
  void i2c_stop(void)                         //Stop the I2C communication
}
I started to write the function required in program. This is only few I know I have to add others but I don't have any idea about that

I am bit confused, How to convert this two statement into program
  1. generate ACK or NACK.
  2. check ACK or NACK

Could any one help me with dummy I2C program in C ?

Note I have not mention controller and slave name because I am looking general idea for all device. It may be difficult to give me advice but I am expecting logic to implement program not original program
START Condtion:
C:
sbit SDA=P1^7; // initialize the SDA and SCL pins of the microcontroller//
sbit SCL=P1^6;
void delay(unsigned int);
void main ()
{
SDA=1; //processing the data//
SCL=1; //clock is high//
delay();
SDA=0; //sent the data//
delay();
SCL=0; //clock signal is low//
}
Void delay(int p)
{
unsignedinta,b;
For(a=0;a<255;a++); //delay function//
For(b=0;b<p;b++);
}
STOP Condition:
C:
void main ()
{
SDA=0; // Stop processing the data//
SCL=1; //clock is high//
delay();
SDA=1; //Stopped//
delay();
SCL=0; //clock signal is low//
}
Void delay(int p)
{
unsignedinta,b;
For(a=0;a<255;a++); //delay function//
For(b=0;b<p;b++);
}
Mod edit: code tags
 
Last edited by a moderator:

JohnInTX

Joined Jun 26, 2012
4,787
@Associate
Welcome to AAC!
You have replied to an old thread and the question has likely been answered.
If you are asking a question, please use a new thread and use code tags to highlight any code you post.
 
Top