I2C Protocol

The I2C protocol is a standard communication protocol use in many onboard communication between two or more IC chips.

For example, the DS3231 real-time clock (RTC), the DS1307 RTC, the MPU-6050 3 axis gyroscope and accelerometer, and the HMC5883L triple axis compass magnetometer are some common ICs that use the I2C protocol.

In this tutorial, we will use the DS1307 RTC to demonstrate how to work with the I2C protocol. The I2C protocol uses two signals, SDA and SCL, for communication.

The first code listing uses the wire.h library This protocol has been implemented in the wire.h library so that you do not need to know the details of how the I2C protocol works.

The second code listing is a raw bit bang low-level code which requires a full understanding of the I2C protocol.

The datasheet for the DS1307 and a detailed description of the I2C protocol can be found here.

Parts needed:
  • Arduino
  • DS1307 Real-time clock chip
  • 32.768kHz crystal
  • 4.7K Ω resistor x2 (doesn't have to be exact)
  • (optional) 1K Ω resistor
  • (optional) 150 Ω resistor
  • Wires
  • Breadboard
DS1307 real-time clock      32.768kHz crystal
1 Making the connections
 
DS1307 Arduino Description
1 and 2 32.768kHz crystal
4 GND GND
5 A4 SDA
5 pull-up resistor to 5V 4.7K Ω pull-up resistor between SDA and VCC
6 A5 SCL
6 pull-up resistor to 5V 4.7K Ω pull-up resistor between SCL and VCC
7 SQW (optional) connect to a 1K Ω pull-up resistor to VCC, and connect to LED+. Connect LED- to GND thru a 150 Ω resistor.
8 5V VCC
2 Copy and upload this program to your Arduino
// Copyright May 2017 Enoch Hwang
// This is the master controller for the DS1307 Real-Time Clock (RTC) chip
// using the I2S protocol. The RTC chip is the slave
// This code uses the Wire.h library

// Connections: 
// DS1307     Arduino             Description
// pin 1 and 2                    32.768kHz crystal
// pin 3                          (optional) connect to battery +3v. connect to GND if no battery 
// pin 4                          connect to GND
// pin 5 SDA  analog pin A4       SDA (4.7K ohm pull-up resistor between SDA and VCC)
// pin 6 SCL  analog pin A5       SCL (4.7K ohm pull-up resistor between SCL and VCC)
//                                also works without these two resistors on the Arduino Uno
//                                must use A4 and A5 because the Arduino and the Wire.h library use these for the SDA and SCL
// pin 7                          (optional) 1K ohm pull-up resistor to VCC and connect to LED+. LED- to GND thru 150 ohm resistor
// pin 8                          5V

// Datasheet http://datasheets.maximintegrated.com/en/ds/DS1307.pdf
// EH

#include <Wire.h>
const int DS1307 = 0x68;  // 7-bit address of the DS1307 = 1101000

void setup() {
  Wire.begin();
  Serial.begin(9600);
  Serial.println("DS1307");
  
  // set and start the clock
  Wire.beginTransmission(DS1307);
  Wire.write(0x00);  // starting address of RTC registers
  Wire.write(0x30);  // seconds: 0 to 59. VERY IMPORTANT: Must write a 0 to bit7 (CH) to start the clock
  Wire.write(0x36);  // minutes: 0 to 59
  Wire.write(0x47);  // hours: 1 to 12 for 12 hour clock. For AM add 0x40; For PM add 0x60; 
                     //        0 to 23 for 24 hour clock. Don't add anything for 24 hour clock
  Wire.write(0x01);  // day of week: 1 to 7 where 1 = Sunday
  Wire.write(0x16);  // date: 1 to 31
  Wire.write(0x02);  // month: 1 to 12
  Wire.write(0x14);  // year: 0 to 99
  Wire.write(0x10);  // control: 0x10 produces a 1 Hz square wave on pin 7
  Wire.endTransmission();

  
  // 56 bytes of battery-back SRAM from 0x08 to 0x3F
  // writing to SRAM
  Wire.beginTransmission(DS1307);
  Wire.write(0x08);  // address of first memory location to access
  Wire.write(0xDD);  
  Wire.write(0xEE);  
  Wire.write(0xFF);  
  Wire.endTransmission();  
  
  // reading from SRAM
  Wire.beginTransmission(DS1307);  // address of DS1307 on the I2S
  Wire.write(0x08); // address of first register to read from
  Wire.endTransmission();
  Wire.requestFrom(DS1307, 3);  // request DS1307 to send 3 bytes
  Serial.println(Wire.read(), HEX);
  Serial.println(Wire.read(), HEX);
  Serial.println(Wire.read(), HEX);

}

void loop() {
  Wire.beginTransmission(DS1307);  // address of DS1307 on the I2S
  Wire.write(0); // address of first register to read from
  Wire.endTransmission();
  
  Wire.requestFrom(DS1307, 7);  // request DS1307 to send 7 bytes
  byte secs = Wire.read();    // read in the 7 bytes
  byte mins = Wire.read();
  byte hrs = Wire.read();
  byte day = Wire.read();
  byte date = Wire.read();
  byte month = Wire.read();
  byte year = Wire.read();

  const char* days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
  Serial.print(days[day-1]);
  Serial.print(" ");
  
  if(month < 10)
    Serial.print("0");
  Serial.print(month, HEX);
  Serial.print("/");
  if(date < 10)
    Serial.print("0");
  Serial.print(date, HEX);
  Serial.print("/");
  Serial.print("20");
  Serial.print(year, HEX);
  Serial.print("  ");

  int PM=0;
  int Hour12=0;
  if(hrs >= 0x40){
    // 12 hour
    hrs = hrs - 0x40;
    Hour12 = 1;
    if(hrs >= 0x20){
      // PM
      hrs = hrs - 0x20;
      PM = 1;
    }
  }
  if(hrs < 10)
    Serial.print("0");
  Serial.print(hrs, HEX);
  Serial.print(":");
  if(mins < 10)
    Serial.print("0");
  Serial.print(mins, HEX);
  Serial.print(":");
  if(secs < 10)
    Serial.print("0");
  Serial.print(secs, HEX);
  if(Hour12){
    if(PM)
      Serial.print(" PM");
    else
      Serial.print(" AM");
  }
  Serial.println("");

  delay(1000);

}

byte decToBcd(byte val) {
  return ((val/10*16) + (val%10));
}
byte bcdToDec(byte val) {
  return ((val/16*10) + (val%16));
}
3 Open up the serial monitor and upload the code. You should see the date and time displayed on the monitor.
4 This next program does not use the Wire.h library. Instead it directly generates the SCL and SDA signals, thus a full understanding of the I2C protocol is required.
// Copyright May 2017 Enoch Hwang
// This is the master controller for the DS1307 Real-Time Clock (RTC) chip
// using the I2S protocol. The RTC chip is the slave
// This is a raw bit bang low-level code
//
// Connections: 
// DS1307     Arduino             Description
// pin 1 and 2                    32.768kHz crystal
// pin 3                          (optional) connect to battery +3v. connect to GND if no battery 
// pin 4                          connect to GND
// pin 5 SDA  analog pin A4       SDA (4.7K ohm pull-up resistor between SDA and VCC)
// pin 6 SCL  analog pin A5       SCL (4.7K ohm pull-up resistor between SCL and VCC)
//                                must use one of the analog pins. the digital pins won't work
// pin 7                          (optional) 1K ohm pull-up resistor to VCC and connect to LED+. LED- to GND thru 150 ohm resistor
// pin 8                          5V
//
// for debugging:
// SDA connected to VCC will display 255:255:255
// SDA connected to GND or no connection will display 0:0:0
// sometimes need to wiggle the SDA wire to get it to work

#define SDA A4
#define SCL A5
const byte DS1307_ADDR_R = 0xD1;  // the DS1307 7-bit address 1101000 with READ  bit (1) = 11010001 = D1
const byte DS1307_ADDR_W = 0xD0;  // the DS1307 7-bit address 1101000 with WRITE bit (0) = 11010000 = D0

void sendStart(byte addr) {
  pinMode(SDA, OUTPUT);
  digitalWrite(SDA, HIGH);
  digitalWrite(SCL, HIGH);
  digitalWrite(SDA, LOW);  

  // send out the 8 bits
  for (int i=7; i>=0; i--) {
    digitalWrite(SCL, LOW);
    digitalWrite(SDA, bitRead(addr,i));
    digitalWrite(SCL, HIGH);
  }
  digitalWrite(SCL, LOW); // must have this last LOW
}

/*
void sendStart(byte addr) {
  pinMode(SDA, OUTPUT);
  digitalWrite(SDA, HIGH);
  digitalWrite(SCL, HIGH);
  digitalWrite(SDA, LOW);  

  // send out the 8 bits
  digitalWrite(SCL, LOW);
  digitalWrite(SDA, bitRead(addr,7));
  digitalWrite(SCL, HIGH);

  digitalWrite(SCL, LOW);
  digitalWrite(SDA, bitRead(addr,6));
  digitalWrite(SCL, HIGH);

  digitalWrite(SCL, LOW);
  digitalWrite(SDA, bitRead(addr,5));
  digitalWrite(SCL, HIGH);

  digitalWrite(SCL, LOW);
  digitalWrite(SDA, bitRead(addr,4));
  digitalWrite(SCL, HIGH);

  digitalWrite(SCL, LOW);
  digitalWrite(SDA, bitRead(addr,3));
  digitalWrite(SCL, HIGH);

  digitalWrite(SCL, LOW);
  digitalWrite(SDA, bitRead(addr,2));
  digitalWrite(SCL, HIGH);

  digitalWrite(SCL, LOW);
  digitalWrite(SDA, bitRead(addr,1));
  digitalWrite(SCL, HIGH);

  digitalWrite(SCL, LOW);
  digitalWrite(SDA, bitRead(addr,0));
  digitalWrite(SCL, HIGH);

  digitalWrite(SCL, LOW); // must have this last LOW
}
*/

// this also works using the shiftOut command
void sendStart_works(byte addr) {
  pinMode(SDA, OUTPUT);
  digitalWrite(SDA, HIGH);
  digitalWrite(SCL, HIGH);
  digitalWrite(SDA, LOW);
  digitalWrite(SCL, LOW);
  shiftOut(SDA, SCL, MSBFIRST, addr);   // send out the 8 bits
}

void sendStop() {
  pinMode(SDA, OUTPUT);
  digitalWrite(SDA, LOW);
  digitalWrite(SCL, HIGH);
  digitalWrite(SDA, HIGH);
  pinMode(SDA, INPUT);
}

void sendNack() {
  pinMode(SDA, OUTPUT);
  digitalWrite(SCL, LOW);
  digitalWrite(SDA, HIGH);
  digitalWrite(SCL, HIGH);
  digitalWrite(SCL, LOW);
  pinMode(SDA, INPUT);
}

void sendAck() {
  pinMode(SDA, OUTPUT);
  digitalWrite(SCL, LOW);
  digitalWrite(SDA, LOW);
  digitalWrite(SCL, HIGH);
  digitalWrite(SCL, LOW);
  pinMode(SDA, INPUT);
}

byte waitForAck() {
  pinMode(SDA, INPUT);
  digitalWrite(SCL, HIGH);
  byte ack = digitalRead(SDA);  //ACK bit should be LOW
  digitalWrite(SCL, LOW);
  return ack;
}

byte readByte() {
  pinMode(SDA, INPUT);
  byte value = 0;
  byte currentBit = 0;

  for (int i = 0; i < 8; ++i) {
    digitalWrite(SCL, HIGH);
    currentBit = digitalRead(SDA);
    value |= (currentBit << 7-i);
    delayMicroseconds(1);
    digitalWrite(SCL, LOW);
  }
  return value;
}

void writeByte(byte value) {
  pinMode(SDA, OUTPUT);
  // send out the 8 bits
  for (int i=7; i>=0; i--) {
    digitalWrite(SCL, LOW);
    digitalWrite(SDA, bitRead(value,i));
    digitalWrite(SCL, HIGH);
  }
  digitalWrite(SCL, LOW); // must have this last LOW
}

// this also works using the shiftOut command
void writeByte2(byte value) {
  pinMode(SDA, OUTPUT);
  shiftOut(SDA, SCL, MSBFIRST, value);
}

byte readRegister(byte reg) {
  byte readValue=0;

  sendStart(DS1307_ADDR_W); // set write mode
  waitForAck();
  writeByte(reg);           // send register address to read from
  waitForAck();
  sendStop();
  sendStart(DS1307_ADDR_R); // change to read mode
  waitForAck();
  readValue = readByte();   // read data from register
  sendNack();
  sendStop();
  return readValue;
}

void writeRegister(byte reg, byte value) {
  sendStart(DS1307_ADDR_W);
  waitForAck();
  writeByte(reg);   // send register address
  waitForAck();
  writeByte(value); // send data to store in register
  waitForAck();
  sendStop();
}

byte decToBcd(byte val) {
  return ((val/10*16) + (val%10));
}
byte bcdToDec(byte val) {
  return ((val/16*10) + (val%16));
}

void setup() {
  Serial.begin(9600);
  Serial.println("DS1307");
  pinMode(SCL, OUTPUT);
  //pinMode(SDA, OUTPUT); // set in the low-level functions
  
  writeRegister(0x00,0x00); // set the seconds register
                            // note: reset bit 7 of register 0 to 0 to enable the clock
  writeRegister(0x01,decToBcd(15)); // set the minutes register
  writeRegister(0x02,decToBcd(8)); // set the hours register
  writeRegister(0x07,0x10); // set control register to enable SQW to 1Hz output
  Serial.println(readRegister(0x07));
}

void loop() {
  Serial.print(bcdToDec(readRegister(2)));
  Serial.print(":");
  Serial.print(bcdToDec(readRegister(1)));
  Serial.print(":");
  Serial.println(bcdToDec(readRegister(0)));

  delay(1000);
}
5 This next program scans the I2C bus looking for I2C devices connected to it. It will print out the I2C device address if it finds one.
// Copyright May 2017 Enoch Hwang
// I2C Scanner
#include <Wire.h>
 
void setup() {
  Serial.begin(9600);
  Serial.println("I2C Scanner");

  Wire.begin(); 
}
  
void loop() {
  byte error, address;
  int nDevices;
 
  Serial.println("Scanning...");
 
  nDevices = 0;
  for(address = 1; address < 127; address++ ) {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
 
    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.println(address,HEX); 
      nDevices++;
    } else if (error==4) {
      Serial.print("Unknown error at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
 
  delay(4000);           // wait 5 seconds for next scan
}