Thursday, December 27, 2018

I2C with Arduino: Three Mini Examples


Hi there. I noticed that I did not write anything about Arduino except one post in May 2015 where it is mentioned in one sentence. A little bit for this reason but actually to have a documentation for myself, I decided to write about Arduino. This is kind of an interim post because I could not begin to write the article I have in my mind. In this post, I will give three examples for using I2C bus with Arduino.

Arduino has a useful interface for I2C bus. I previously used I2C with PC serial port and with PIC series micro-controllers. After all, I was surprised how it's incredibly easy with Arduino. I2C bus uses two pins for communication. It is quite similar to old Akbils (short for akıllı bilet i.e. smart ticket in touch on memory (TOM) used previously in Istanbul). Two of the ICs, I used in the examples, are actually manufactured by Dallas Semiconductors (bought by Maxim) which has also manufactured TOMs used as Akbil.


Arduino UNO uses A4 and A5 pins for I2C. Their function is SDA (Serial Data) and SCL (Serial Clock) respectively. The communication in I2C happens between two devices called master and slave. Master generates the clock signal and accesses the devices by their addresses. Slave reads and/or writes data on the bus as it receives its own address in the bus. The clock signal, SCL is used for synchronization during the communication and data flows in SDA pin. I will not dig deeper into the details about the protocol (like start and end of transmission frames etc.) to keep the post short. I will start with the examples.

Sources:
https://www.arduino.cc/en/Reference/Board
https://www.arduino.cc/en/Reference/Wire
https://en.wikipedia.org/wiki/I%C2%B2C


External EEPROM with AT2432 IC
Overture: ATmega328P (i.e. Arduino UNO) has an internal EEPROM of 1KB. I learned this after I first met Arduino. I suppose no one uses this internal EEPROM due to its limited rewrite cycles. At least I did not encounter someone who uses it. It is quite easy to access this internal storage using EEPROM library (source) however I prefer an external storage.

I used Atmel's IC AT24C32. This is an 32K EEPROM consisting from 8 x 4096 bytes. Here are the datasheet from official website and same document in my Google Drive. There are three address pins like A0, A1 and A2. This means: I have written above that every slave has a I2C address. This address is 7 bits long and it is 1010XXXb for this chip. Last three bits of the address is left to the designer. If three of them are grounded the address gets 1010000b or if they all are connected to Vcc then it gets 1010111b. Thus, up to eight chips can be used on the same bus.

AT2432 EEPROM
It is obvious that I2C addresses have to be unique. In theory, 128 chips can be connected to the same bus if their addresses are different. Those chips with address pins can be used more than one in the same bus.
WP pin is used for write protection. I have grounded it because I am not going to use it. SCL and SDA pins are connected to A5 and A4 pins of Arduino respectively using 4.7K pull-up resistors. The schematic is in the next figure.
Fritzing is "almost officially" schematics editor of Arduino project. Fritzing schematics can be found in every book or project that has something to do with Arduino. I couldn't get used to it because, usually I create my schematics in PCAD and nowadays in EagleCAD. However there is currently no better option than Fritzing for Arduino schematics because it contains block diagrams of Arduino itself as well as most of its shields. Another possibility is that I could not find block diagrams for EagleCAD. In 'Breadboard' view in Fritzing, schematic can be drawn as easy as plugging the components onto a breadboard. Many Arduino books include those breadboard view schematics to look "more user-friendly". I find these schematics pretty useless, maybe because I am old fashioned. On these schematics, it is very unclear where the cables are connected or where they pass. Therefore I will put here only old style schematics. And btw, I am still learning Fritzing.

The project code is as follows:

 /* AT24C32 I2C EEPROM Example */

#define MX24C32  B1010000    // 7-bit I2C address
#include <Wire.h>

void setup()  {
  Serial.begin(9600);
  Wire.begin();
}

void loop()  {
  char hello[8] = {'H', 'e', 'l', 'l', 'o', '!', '!'};
  byte addr = 0;
  int X;
  char t;

  // This part writes 'Hello!!' on the EEPROM and it
  // has to run once. Therefore it's commented out
  //for(int i = 0; i < 7; i++)  {
  //  Wire.beginTransmission(MX24C32); // Send write command
  //  Wire.write(0x00);         // Send high addr.
  //  Wire.write(addr++);       // Send low addr. and increase var. for next char
  //  Wire.write(byte(hello[i]));     // Send the byte
  //  Wire.endTransmission();
  //  delay(100);               // wait until EEPROM is ready
  //}

  // This part sends an empty write command (without data) to
  // the address 0x0 of the EEPROM to seek at that address
  Wire.beginTransmission(MX24C32);
  Wire.write(0x00);
  Wire.write(0x00);
  Wire.endTransmission();

  delay(100);

  // 10 bytes will be read
  for(int i = 0; i < 10; i++)  {
    Wire.requestFrom(MX24C32, 1);       // request a byte from the chip
    X = Wire.available();               // is it available on the bus?
    Serial.print("X = "); Serial.println(X);
    if(X >= 1)  {
      t = Wire.read();                  // read the byte if it's available
      Serial.print("t= "); Serial.println(t);
    }
  }

  while(1);
}


I send the data to the IC, using Wire.write() function. Write command is sent by Wire.beginTransmission() function and it is followed by two bytes of address and one byte of data to be written. Wire.endTransmission() ends the I2C frame. Please refer the datasheet p.9: "A write operation requires two 8-bit data word addresses following the device address word and acknowledgment". The chip has an internal pointer, so to say. Reading occurs from where the last write operation left off. In other words, write operation has an address operand but read operation doesn't. To read from a specific address, a seek operation is done by sending a write command with address but no data. This is the general usage. More detailed information can be found on the datasheet.

Here is another similar project:
https://playground.arduino.cc/code/I2CEEPROM


Thermometer using DS1621 IC
DS1621 is an I2C thermometer integrated circuit by Dallas Semiconductors. (Note: Dallas has another chip with thermometer included, in same packaging of Akbil). This IC also supports using up to eight chips on the same bus. Tout is the temperature alarm pin. This pin is set when the temperature goes higher than the given threshold (datasheet, google drive). The schematics is as follows:
DS1621 Thermometer

There is a library for this IC but I did not used it:
https://github.com/martinhansdk/DS1621-temperature-probe-library-for-Arduino

/* DS1621 temperature sensor interfaced by Arduino
 *
 * I2C ID will be 0x48 if A0 = A1 = A2 = GND
 */

#define DS1621 B1001000
#include <Wire.h>

void setup()   {
  Serial.begin(9600);
  Wire.begin();
  Wire.beginTransmission(DS1621);
  Wire.write(0xAC);     // Send Access Config command
  Wire.write(0x02);     // Write 02 to config register
                        // Output polarity bit = 1 => Active High
  delay(10);

  Wire.beginTransmission(DS1621);
  Wire.write(0xEE);     // send "start convert" command
  Wire.endTransmission();       // Stop bit

}

void loop()  {
  byte SH, SL, X;
  // SH: High order byte of temperature
  // SL: Low order byte of temperature

  Wire.beginTransmission(DS1621);
  Wire.write(0xAA);     // Send "Read Temperature" command
  Wire.endTransmission();


  Wire.requestFrom(DS1621, 2);
  // After sending "read temperature" command
  // request two bytes from IC for temperature
  X = Wire.available();
  if(X >= 2)  {        // if 2 bytes were received
    // SH contains the integer part of the temperature
    SH = Wire.read();
    // SL contains 0x80 for 0.5 degree celcius
    SL = Wire.read();
    Serial.print(SH, HEX);
    Serial.print("    ");
    Serial.println(SL, HEX);
  }
  else
    Serial.println(X);
  //Wire.endTransmission();

  delay(500);

}
 
0xEE and 0xAC are the commands specific to this IC. The list of all commands can be found on the tenth page of the datasheet. During the initialization, method of operation is written to the config register. For example 0x02 means One Shot mode = 0 and Polarity = 1. 0xEE command starts the temperature conversion cycle and using 0xAA command, temperature values are pulled to the bus from the IC. Although there is a busy flag in config register, I implemented busy waiting with delay() function for simplicity.

There is an old project where this IC is connected to a PC through the serial port. I will explain this project in one of the following posts.






Real Time Clock (RTC) with IC DS1307

DS1307 is a RTC chip again by Dallas Semiconductors. This IC does not have any address bits because it is meaningless to use more than one RTC in same circuit. There is a VBAT input pin for battery supply. The clock continues to tick on battery even if Arduino is powered off. I had made a note to myself "do not connect the battery to Vcc" but I cannot remember why. This IC can be thought as counter with 64 byte of RAM. The counter increases seconds, if seconds overflow minutes are increased, if minutes overflow hours are increased and so on.

DS1307 Real Time Clock
There are ready-to-use kits of this IC and even kits with this IC and AT2432 (the IC, I discussed in the first example) on the same board. There is also a library for this IC. I wrote my own code but I borrowed two function from this library.


This IC requires a 32.768 KHz crystal oscillator to run. X1 and X2 pins are connected to this oscillator. Pin 7 is square wave output. It is floating just because it is unused. There are plenty of comments in the code:


/* The code for DS1307 RTC circuit. Do not connect the battery
 * to Vcc. Do not forget the pull-up resistors on SDA and SCL
 */

#include <Wire.h>
#define DS1307 B1101000

// These two functions were borrowed from the source
// code of RTC library of jeelabs:
// https://jeelabs.org/2010/02/05/new-date-time-rtc-library/
static uint8_t bcd2bin (uint8_t val) { return val - 6 * (val >> 4); }
static uint8_t bin2bcd (uint8_t val) { return val + 6 * (val / 10); }

void setup()  {
  Serial.begin(9600);
  Wire.begin();

  // Begin initialization
  Wire.beginTransmission(DS1307);
  Wire.write(0);        // send first "0" as an address byte
  Wire.endTransmission();

  Wire.requestFrom(DS1307, 1);
  int ss = Wire.read(); // read a character
  Serial.println(ss);
  // End initialization
  // If the chip returns a character then it's working


  /*   //  Set the clock first if it is not set yet
  Wire.beginTransmission(DS1307);
  Wire.write(0);
  Wire.write(0x0);    // seconds
  Wire.write(0x21);   // minutes
  Wire.write(0x0);    // hours
  Wire.write(0);      // day of the week
  Wire.write(0x24);   // day
  Wire.write(0x12);   // month
  Wire.write(0x18);   // year
  Wire.write(0x10);   // config register: SQ Wave out @1Hz
  Wire.endTransmission();
  // */

}

void loop()  {
  Wire.beginTransmission(DS1307);
  Wire.write(0);        // send "0" as an address byte
  Wire.endTransmission();

  Wire.requestFrom(DS1307, 7);  // Read 7 bytes from RTC
  /* Those 7 bytes are:
   * 00H: CH Bit + Seconds BCD (Bit 7 of 00H is the Clock Halt
   *      (CH) bit. It stops the oscillator when set.
   *      "Please note that the initial power-on state of all
   *      registers is not defined. Therefore, it is important
   *      to enable the oscillator (CH bit = 0) during initial
   *      configuration.")
   * 01H: Minutes BCD
   * 02H: Hours BCD (Bit 6 of the hours register selects 12H
   *      mode when set and in 12H mode, bit 5 represents AM
   *      when reset and PM when set. If bit 6 is reset, 4.
   *      and 5. bits are tens place of hours in 24H mode.
   * 03H: Week of the day. Unused in this code.
   * 04H: Day of the month BCD
   * 05H: Month (BCD)
   * 06H: Year (BCD)
   * 07H: Control register
   */

  // Read the seconds but discard CH bit:
  uint8_t ss = bcd2bin(Wire.read() & 0x7F);
  uint8_t mm = bcd2bin(Wire.read());   // Read minutes
  uint8_t hh = bcd2bin(Wire.read());   // Read hours in 24H
  Wire.read();                         // ignore the week day
  uint8_t d = bcd2bin(Wire.read());    // day
  uint8_t m = bcd2bin(Wire.read());    // month
  uint16_t y = bcd2bin(Wire.read());   // year

  Serial.print(d); Serial.print(".");
  Serial.print(m); Serial.print(".");
  Serial.print(y); Serial.print("   ");
  Serial.print(hh); Serial.print(":");
  Serial.print(mm); Serial.print(":");
  Serial.println(ss);

  delay(1000);  // read every second.
}