Thursday, October 28, 2010

Tutorial: Arduino and the I2C bus – part two

This is chapter twenty-one of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – A tutorial on the Arduino universe.

The first chapter is here, the complete series is detailed here. Please note from November 1, 2010 files from tutorials will be found here.

Welcome back fellow arduidans!

Today we are going to continue learning about the I2C bus and how it can work for us. If you have not already, perhaps read the first I2C article before continuing.

First of all, there are some limitations of I2C to take into account when designing your projects. One of these is the physical length of the SDA and SCL lines. If all your devices are on the same PCB, then there is nothing to worry about, however if your I2C bus is longer than around one metre, it is recommended that you use an I2C bus extender IC. These ICs reduce electrical noise over the extended-length bus runs and buffer the I2C signals to reduce signal degradation and chance of errors in the data. An example of such an IC is the NXP P82B715 (data sheet). Using a pair of these ICs, you can have cable runs of 20 to 30 metres, using shielded twisted-pair cable. Below is a good example of this, from the aforementioned NXP data sheet:

Several applications come to mind with an extended I2C bus, for example remote temperature monitoring using the the ST Microelectronics CN75 temperature sensor from part one; or controlling several I/O ports using an I2C expander without the expense or worry of using a wireless system. Speaking of which, let’s do that now…

Example 21.1

A very useful and inexpensive part is the PCF8574 I/O expander (data sheet.pdf). This gives us another eight outputs, in a very similar method to the74HC595; or can be used as eight extra inputs. In fact, if you were to use more than one 74HC595 this IC might be preferable, as you can individually address each chip instead of having to readdress every IC in line as you would with shift registers. So how do we do this? First, let’s consult the pinout:

There should not be any surprises for you there. A2~A0 are used to select the last three bits of the device address, P0~P7 are the I/O pins, and INT is an interrupt output which we will not use. To address the PCF8574 we need two things, the device address, and a byte of data which represents the required output pin state. Huh? Consider:

So if we set pins A0 to A2 to GND, our device address in binary will be 0100000, or 0×20 in hexadecimal. And the same again to set the output pins, for example to turn them all on we send binary 0 in hexadecimal which is 0; or to have the first four on and the second four off, use 00001111 which is Ox0F. Hopefully you noticed that those last two values seemed backwards – why would we send a zero to turn all the pins on?

The reason is that the PCF8574 is a current sink. This means that current runs from +5v, through into the I/O pins. For example, an LED would have the anode on the +5V, and the cathode connected to an I/O pin. Normally (for example with a 74HC595) current would run from the IC, through the resistor, LED and then to earth. That is a current source. Consider the following quick diagram:

Luckily this IC can handle higher volumes of current, so a resistor will not be required. It sounds a bit odd, but like anything is easy once you spend a few moments looking into it. So now let’s use three PCF8574s to control 24 LEDs. To recreate this masterpiece of blinkiness you will need:

Here is the schematic:

… and the example board layout:


and the example sketch. Note that the device addresses in the sketch match the schematic above. If for some reason you are wiring your PCF8574s differently, you will need to recalculate your device addresses:

/*  Example 21.1
Texas Instruments PCF8574N demonstration sketch
element-14 part number 7527718; RS part number 517-0687
http://tronixstuff.com/tutorials > chapter 21
CC by-sa v3.0 */
#include "Wire.h"
#define redchip 0x20 // device addresses for PCF8547Ns on each LED colour bank
#define yellowchip 0x22 //  addresses in this example match the published schematic in the tutorial
#define greenchip 0x21
// you will need to change addresses if you vary from the schematic
int dd=20; // used for delay timing void setup()
{
Wire.begin();
allOff(); // the PCF8574N defaults to high, so this functions turns all outputs off} // remember that the IC "sinks" current, that is current runs fro +5v through the LED and then to I/O pin
// this means that 'high' = off, 'low' = on.
}
void testfunc()
{
Wire.beginTransmission(redchip);
Wire.send(0);
Wire.endTransmission();
delay(dd+50);
Wire.beginTransmission(redchip);
Wire.send(255);
Wire.endTransmission();
delay(dd+50);
Wire.beginTransmission(yellowchip);
Wire.send(0);
Wire.endTransmission();
 delay(dd+50);
Wire.beginTransmission(yellowchip);
Wire.send(255);
Wire.endTransmission();
delay(dd+50);
Wire.beginTransmission(greenchip);
Wire.send(0);
Wire.endTransmission();
delay(dd+50);
Wire.beginTransmission(greenchip);
Wire.send(255);
Wire.endTransmission();
delay(dd+50);
}
void testfunc2()
{
for (int y=1; y<256; y*=2)
{
Wire.beginTransmission(redchip);
Wire.send(255-y); // we need the inverse, that is high = off
Wire.endTransmission();
delay(dd);
Wire.beginTransmission(redchip);
Wire.send(255);
Wire.endTransmission();
delay(dd);
}
for (int y=1; y<256; y*=2)
{
Wire.beginTransmission(yellowchip);
Wire.send(255-y);
Wire.endTransmission();
delay(dd);
Wire.beginTransmission(yellowchip);
Wire.send(255);
Wire.endTransmission();
delay(dd);
}
for (int y=1; y<256; y*=2)
{
Wire.beginTransmission(greenchip);
Wire.send(255-y);
Wire.endTransmission();
delay(dd);
Wire.beginTransmission(greenchip);
Wire.send(255);
Wire.endTransmission();
delay(dd);
}
} void testfunc3()
{
Wire.beginTransmission(redchip);
Wire.send(0);
Wire.endTransmission();
Wire.beginTransmission(yellowchip);
Wire.send(0);
Wire.endTransmission();
Wire.beginTransmission(greenchip);
Wire.send(0);
Wire.endTransmission();
delay(dd+50);
allOff();
delay(dd+50);
} void allOff()
{
Wire.beginTransmission(redchip);
Wire.send(255);
Wire.endTransmission();
Wire.beginTransmission(yellowchip);
Wire.send(255);
Wire.endTransmission();
Wire.beginTransmission(greenchip);
Wire.send(255);
Wire.endTransmission();
} void loop()
{
for (int z=0; z<10; z++)
{
testfunc();
}
for (int z=0; z<10; z++)
{
testfunc2();
}
for (int z=0; z<10; z++)
{
testfunc3();
}
}

And finally our demonstration video:


That was a good example of controlling many outputs with our humble I2C bus. You could literally control hundreds of outputs if necessary – a quite inexpensive way of doing so. Don’t forget to take into account the total current draw of any extended circuits if you are powering from your Arduino boards.

The next devices to examine on our I2C bus ride are EEPROMs - Electrically Erasable Programmable Read-Only Memory. These are memory chips that can store data without requiring power to retain memory. Why would we want to use these? Sometimes you might need to store a lot of reference data for use in calculations during a sketch, such as a mathematical table; or perhaps numerical representations of maps or location data; or create your own interpreter within a sketch that takes instruction from data stored in an array.

In other words, an EEPROM can be used to store data of a more permanent use, ideal for when your main microcontroller doesn’t haven enough memory for you to store the data in the program code. However, EEPROMs are not really designed for random-access or constant read/write operations – they have a finite lifespan. But their use is quite simple, so we can take advantage of them.

EEPROMS, like anything else come in many shapes and sizes. The model we will examine today is the Microchip 24LC256 (data sheet.pdf). It can hold 256 kilobits of data (that’s 32 kilobytes) and is quite inexpensive. If you are using an Arduino LilyPad or Pro Mini 3.3V, etc., this EEPROM can also run on 3.3V. This model also has selectable device addresses using three pins, so we can use up to eight at once on the same bus. An example:

The pinouts are very simple:

Pin 7 is “write protect” – set this low for read/write or high for read only. You could also control this in software if necessary. Once again we need to create a slave I2C device address using pins 1, 2 and 3 – these correlate to A2, A2 and A0 in the following table:

So if you were just using one 24LC256, the easiest solution would be to set A0~A2 to GND – which makes your slave address 1010000 or 0×50 in hexadecimal. There are several things to understand when it comes to reading and writing our bytes of data. As this IC has 32 kilobytes of storage, we need to be able to reference each byte in order to read or write to it. There is a slight catch in that you need more than one byte to reference 32767 (as in binary 32767 is 11111111 0100100 [16 bits]).

So when it comes time to send read and write requests, we need to send two bytes down the bus – one representing the higher end of the address (the first 8 bits from left to right), and the next one representing the lower end of the address (the final 8 bits from left to right) – see figure 6.1 on page 9 of the data sheet.

An example – we need to reference byte number 25000. In binary, 25000 is 0110000110101000. So we split that up into 01100001 and 10101000, then covert the binary values to numerical bytes with which to send using the Wire.send(). Thankfully there are two operators to help us with this. This first is >>, known asbitshift rightThis will take the higher end of the byte and drop off the lower end, leaving us with the first 8 bits. To isolate the lower end of the address, we use another operator &, known as bitwise and. This unassuming character, when used with 0XFF can separate the lower bits for us. This may seem odd, but will work in the examples below.

Writing data to the 24LC256

Writing data is quite easy. But first remember that a byte of data is 11111111 in binary, or 255 in decimal. First we wake up the I2C bus with

Wire.beginTransmission(0x50); // if pins A0~A2 are set to GND

then send down some data. The first data are the two bytes representing the address (25000) of the byte (12) we want to write to the memory.

Wire.send(25000 >> 8);  // send the left-hand side of the address down
Wire.send(25000 & 0xFF); // send the right-hand side of the address down

And finally, we send the byte of data to store at address 25000, then finish the connection:

Wire.send(12);
Wire.endTransmission();

There we have it. Now for getting it back…

Reading data from the 24LC256

Reading is quite similar. First we need to start things up and move the pointer to the data we want to read:

Wire.beginTransmission(0x50); // if pins A0~A2 are set to GND
Wire.send(25000 >> 8);  // send the left-hand side of the address down
Wire.send(25000 & 0xFF); // send the right-hand side of the address down
Wire.endTransmission();

Then, ask for the byte(s) of data starting at the current address:

Wire.beginTransmission(0x50); // if pins A0~A2 are set to GND
Wire.requestFrom(0x50,1);
Wire.receive(incomingbyte);   In this example, incomingbyte is a byte variable used to store the data we retrieved from the IC. 

Example 21.2

Now we have the theory, let’s put it into practice with the test circuit below, which contains two 24LC256 EEPROMs. To recreate this you will need:

Here is the schematic:

… the board layout:

and the example sketch. Note that the device addresses in the sketch match the schematic above. If for some reason you are wiring your 24LC256s differently, you will need to recalculate your device addresses. To save time with future coding, we have our own functions for reading and writing bytes to the EEPROM –readData() and writeData(). Consider the sketch for our example:

/*
Example 21.2  Reading and writing data to Microchip 24LC256 EEPROMS over I2C
tronixstuff.com/tutorials > Chapter 21  CC by-sa v3.0
*/
#include      // for I2C
#define chip1 0x50    // device address for left-hand chip on our breadboard
#define chip2 0x51    // and the right // always have your values in variables
unsigned int pointer = 69; // we need this to be unsigned, as you may have an address > 32767
byte d=0; // example variable to handle data going in and out of EERPROMS void setup()
{
Serial.begin(9600); // for screen output
Wire.begin();   // wake up, I2C!
}
void writeData(int device, unsigned int add, byte data)
// writes a byte of data 'data' to the chip at I2C address 'device', in memory location 'add'
{
Wire.beginTransmission(device);
Wire.send((int)(add >> 8));   // left-part of pointer address
Wire.send((int)(add & 0xFF)); // and the right
Wire.send(data);
Wire.endTransmission();
delay(10);
}
byte readData(int device, unsigned int add) // reads a byte of data from memory location 'add' in chip at I2C address 'device'
{
byte result;  // returned value
Wire.beginTransmission(device); //  these three lines set the pointer position in the EEPROM
Wire.send((int)(add >> 8));   // left-part of pointer address
Wire.send((int)(add & 0xFF)); // and the right
Wire.endTransmission();
Wire.requestFrom(device,1); // now get the byte of data...
result = Wire.receive();  return result; // and return it as a result of the function readData
}
void loop()
{
Serial.println("Writing data...");
for (int a=0; a<20; a++)
{
writeData(chip1,a,a);
writeData(chip2,a,a); // looks like a tiny EEPROM RAID solution!
}
Serial.println("Reading data...");
for (int a=0; a<20; a++)
{
Serial.print("chip1 pointer ");
Serial.print(a);
Serial.print(" holds ");
d=readData(chip1,a);
Serial.println(d, DEC);
}
for (int a=0; a<20; a++)
{
Serial.print("chip2 pointer ");
Serial.print(a);
Serial.print(" holds ");
d=readData(chip2,a);
Serial.println(d, DEC);
}
}

And the output from the example sketch:

Although the sketch in itself was simple, you now have the functions to read and write byte data to EEPROMS. Now it is up to your imagination to take use of the extra memory.

Well that is all we have time for in this instalment. Stay tuned for upcoming Arduino tutorials, as well as some more part reviews (I2C and general). In the next instalments we will look at more GPS applications, as well as introduce a GSM module (our Arduino will be able to communicate via SMS…)

If you have any questions about the processes or details in this article, please ask in our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, there is the odd competition or give-away –  and we can all learn something.

Otherwise, have fun, stay safe, be good to each other – and make something!

Monday, October 25, 2010

Breaking up an automatic room deodoriser – round two

Hello readers!

Today we are going to tear down another automatic room deodoriser. Why?

Well the first attempt beat me, so it was time to even the score and try again with another type. The supermarket had the following units for $7.99, which seemed a little too cheap:

The “satisfaction guarantee” gave me a chuckle, the thought of writing to SC Johnson complaining that their products were not that hackable would be interesting. But would it be hackable at all? Let’s find out. The packaging promises a squirt of scent when the unit detects motion, then holds out for 30 minutes until the next release. The word motion hints that there would be a PIR inside the unit. However the instructions mention that the unit does not work that well in dark or bright rooms – which is odd, as PIRs usually work in the dark. Hmm. This unit is somewhat smaller than the previous attempt, yet still offers us a pair of alkaline AA cells:

Moving on, time to start the disassembly process. The rear shows four screws, easily removed:

revealing the fun things:

The motor drive is reduced twice, which then has a geared arm which causes the vertical motion to pressure the cylinder to release the scent. The whole mess of gears was lubricated generously, the whole lot literally came out with the touch of a finger. Removing the gears and goop reveals the motor and control boards, which clipped out easily:

Interesting – a labelled motor. Very good, what looks like to be a 3V DC motor. The control board is made up of two PCBs, a smaller module that holds a control IC of some sort, and the larger, lesser-densely populated board with the button, status LED and “motion detector”. Let’s have a close-up of that PCB:

So we have the button, which causes the motor to run; a yellow LED which blinks once every five seconds; and out motion detector in the black casing. The motion detector seemed rather familiar, so I removed the black housing around it with some pliers, which revealed this:

Huh – that looks just like an LED. The metal object inside the clear casing was even identical to what you would see inside an LED. However, foolishly I broke it off the PCB when removing the housing, so could not get any voltage to it. From reading the instructions earlier on – that mention the light/dark issue, causes me to ponder if this is some sort of light-dependent sensor? Does anyone know about these? If so, please leave a comment at the bottom of this article and we can discuss it. Or it’s just an LED and a scam!

However the motor was definitely not a scam, quite the opposite in fact. Curious to see what is driving it, I hooked up Mr Fluke to see what happens:

Click here to see the video (Youtube seems to be malfunctioning today)

No surprises there, almost three volts DC forward voltage. After applying forward current the circuit applies a quick reverse current to release, thereby causing the gears and arm to ‘squeeze’ down on the scent cylinder. So now we have a circuit board that runs on 3V, which can output 3V for a few seconds every 30 minutes – or at the press of a button.

With regards to current, another measurement was taken:

Click here to see the video

When free-running, the motor draws around 45 milliamps – and the stall current (that is, the current drawn when I force the spindle to stop) is around 675 milliamps. That is quite a strong little motor, and worth the effort. In general, this has been a good tear down, we scored some AA cells, a good motor and gears, some stink spray, and a timing circuit that could have uses elsewhere. So overall a win – the score has evened with the deodoriser world!

If you have any questions about the processes or details in this article, please ask in our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, there is the odd competition or give-away –  and we can all learn something.

High resolution photos available on flickr.

Otherwise, have fun, stay safe, be good to each other – and make something!

- John Boxall, tronixstuff.com

Friday, October 22, 2010

Kit Review – Silicon Chip Low Capacitance Meter adaptor for DMMs

Hello readers

Time again for another kit review. In the spirit of promoting all things electronic and Australian, today we are going to look at a kit that was published in our electronics magazine Silicon Chip (March 2010) – their Low-capacitance meter adaptor for DMMs. Simply put, it converts capacitance (from a theoretical 1 picofarad) to millivolts, which you can then read with almost any digital multimeter. This is useful as even more expensive multimeters (such as my Fluke 233) only measure down to 1 nanofarad (1000 picofarads). Although this kit is available on the Australian market, the retailers will export to those abroad. If you are outside Australia and having trouble sourcing one, send me an email. Moving on…

Here is our unassuming finished product:

Please note that this is not an open-source product, so you need to either purchase the kit of parts, or a back-issue of Silicon Chip magazine, March 2010 for the schematic and instructions. Now it is time to get started. But before that, how does it work?

Without giving too much away, a very rough explanation would be that a square wave signal is formed, then cleaned up through a Schmitt trigger-inverter. This square wave is then split into two, one signal passing through the capacitor under test and some resistors, and the other signal passing through a calibration cariable capacitor and the same value resistors – thereby both signals pass through two different RC circuits. Finally the two signals are fed through a XOR gate, which creates a series of positive pulses that are a function of the capacitor under test.

Kit assembly was not that difficult, like anything just take your time, read the instructions carefully, and don’t rush things. If you are happy with your through-hole soldering skills, and have a power drill, this kit will be easy for you to work with. Unusually for some kits, this one comes with almost everything you need:

The quality of the included housing is very good, there are metal threaded inserts for the screws; and even through the ICs are simple 74xx-series, sockets have been included. Resistors are metal film, the trimpots are enclosed multiturns – all very nice. I am a little disappointed with the housing/adhesive label combination however, in the past various kits from Jaycar would have a box with a nice silk-screened, hole-punched front panel. Such is life. The PCB is solder-masked and silk-screened, however a little less denser than PCBs from other kit suppliers:

And thus brings a slight issue with the housing and the PCB – either the PCB is too wide, or the box is too narrow. A quick clip of the PCB with some cutters will fix that:

The instructions are quite good – they are a reprint of the magazine article, and slightly modified by the kit production company. Furthermore, the silk-screening on the PCB makes things a breeze. The simple passives were easy to install, however take care not to overheat the variable capacitor, their casings can melt rather quickly:

Following that, the ICs were inserted, and the rotary switch. From experience, one should trim the shaft down to about a 25mm length before soldering it into the board. Take very good care when placing the rotary switch, there is a lump on the switch which matches the small circle at 8 o’clock on the PCB diagram. Finally, don’t forget to alter the switch so it only has four selections. Soldering it in can look difficult, but is not. Just push it into the PCB, checking it is flush, even and all the way in. Then bend a couple of the pins over, invert the PCB and solder away – as such:

Now it is time to start on the enclosure. Each end has two banana-type sockets, the left are the full binding-post, and the right are just sockets. Carefully mark where you want to start the holes – the positions are vertically half-way, and horizontally 15mm in from the edge, however double-check yourself. Always check the fit of the socket while drilling, as it is easy to go too far and make the holes too large – at which point you’ll have to buy another enclosure.

Once you have the sockets fitted – on the left:

and on the right:

… you will need to solder the socket rear to the PCB pins (left) and a small link to the PCB pins (right). It is important to get a good, solid connection – as these sockets may come under a lot of use later on. Next it is time to start on the housing. If you can, photocopy the label so you have a drilling template:

You will notice in the above photo one of my favourite tools, a tapered reamer. Using that, you can carefully turn a small hole into a larger hole, without risking making a mess with a drill. Again, cut the rotary switch’s shaft before soldering:

And as punishment for using twitter at the same time, I had ended up drilling the back instead of the front. D’oh. However cosmetic appearance is secondary to functionality, so all is well. Next was to install the PP3 battery snap. The battery will be a tight fit, so a length of heatshrink has been supplied in order to avoid the battery case shorting with the PCB pin:

And finally we have finished soldering:

Now it is time for calibration. And for me to get a little cranky, which is quite rare as I am somewhat easygoing. Calibration requires three 1% tolerance capacitors, 100 pF, 1000 pF and 10000 pF. And they are not included with the kit. And can not be purchased from any of the kit retailers. So they had to be ordered from Farn… element-14 at a reasonable expense. Considering the kit production company also imports, wholesales and retails electronic components, they could have bought a volume of these special capacitors and added a few dollars to the price of the kit. Cheapskates! A curse on Jaycar. So here are the little buggers:

From top to bottom:

  • Silvered-mica 100 picofarad 1% tolerance, element-14 # 1264880, RS # 495745;
  • Polystyrene 1000 picofarad 1% tolerance, element-14 # 9520651, RS # 495868 (silvered mica) and
  • Polystyrene 10000 picofarad 1% tolerance, element-14 # 3358951, RS # 495953 (silvered mica)

However it is worth the effort to chase them down. There is no point using this kit if you calibrate with normal capacitors; their tolerance can be as much as 20 percent either way. Thankfully the calibration process is quite simple. You will need a small, plastic flat-blade screwdriver to make the adjustments, as your body has stray energy which can alter the capacitance measurements.

Before starting, connect your multimeter to the output sockets and set the range to millivolts – then adjust the variable capacitor until you have the meter display as close to zero as possible. This is used to ‘null out’ stray capacitance. Next, set the dial to A, connect the 100 pF capacitor to the input posts, and adjust VR3 until the meter displays one volt DC – this represents 100.0 picofarads:

I could not for the life of me get this to 1 volt. After fitting the case at the end, I tried again with the case on with the same result. It is very important to get the capacitor as close as possible to the binding posts, with such small values stray capacitance can affect the result. However in my line of work, one-tenth of a picofarad is not relevant. For now. Next, set the dial to B, connect the 1000 pF capacitor, and adjust VR2 until the meter displays 1 volt – this represents 1000 picofarads:

Excellent – spot on. Unfortunately the leads on my 10000 pF capacitor were not long enough to attach into the binding posts, so that step had to be passed. I will have to re-order the correct part next week and calibrate then. However the other two setting are basically working perfectly, which is a good indication for the general performance of the kit. Kudos to Jim Rowe from Silicon Chip magazine for this design.

Before closing up the enclosure, I decided to wrap the battery with some paper, as having it  rub up against other parts is not a good idea:

Now for a test run – time to measure the smallest capacitors I have in stock, first a 4.7 picofarad ceramic:

and next, a 12 picofarad ceramic:

Excellent, we can call these readings a success. I was also quite amazed that the tolerance of the cheap ceramic capacitors was so low. Note that in real-life, you may not be able to have the capacitor under test directly connected to the binding posts. In these cases you will need a short set of heavy-gauge leads to the test capacitor. If you do this, you will need to adjust the variable capacitor to reset the display to account for stray capacitance in the leads.

In conclusion, this kit has proved very successful, with regards to assembly, the quality of components and instructions, and of course the final result. I made a few errrors with regards to the housing, but that didn’t affect the final result. And for less than fifty Australian dollars, I have a very low value capacitance meter. However in due course I would consider the purchase of a full LCR meter for greater accuracy and ease of frequent use (some can measure down to 0.1 picofarad). But for the time being, this has been an excellent, educational  and affordable solution.

You can purchase the kit directly from Little Bird Electronics. As always, thank you for reading and I look forward to your comments and so on. Furthermore, don’t be shy in pointing out errors or places that could use improvement. Please subscribe using one of the methods at the top-right of this web page to receive updates on new posts. Or join our Google Group.

High resolution images are available on flickr.

[Note - The kit was purchased by myself personally and reviewed without notifying the manufacturer or retailer]

Otherwise, have fun, be good to each other – and make something! :)

Tuesday, October 19, 2010

Tutorial: Arduino and the I2C bus

This is chapter twenty-one of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – A tutorial on the Arduino universe.

The first chapter is here, the complete series is detailed here. Please note from November 1, 2010 files from tutorials will be found here.

Welcome back fellow arduidans!

Today we are going to start investigating the I2C data bus, and how we can control devices using it with our Arduino systems. The I2C bus can be a complex interface to master, so I will do my best to simplify it for you. In this article we will learn the necessary theory, and then apply it by controlling a variety of devices. Furthermore it would be in your interest to have an understanding of the binary, binary-coded decimal and hexadecimal number systems.

But first of all, what is it?

I2C is an acronym for “Inter-Integrated Circuit”. In the late 1970s, Philips’ semiconductor division (now NXP) saw the need for simplifying and standardising the data lines that travel between various integrated circuits in their products. Their solution was the I2C bus. This reduced the number of wires to two (SDA – data, and SCL – clock).

Why would we want to use I2C devices?

As there are literally thousands of components that use the I2C interface! For example, click these supplier links to have a look at the range: NXPFarnell,DigikeyMouser, and RS

And our Arduino boards can control them all. There are many applications, such a real-time clocks, digital potentiometers, temperature sensors, digital compasses, memory chips, FM radio circuits, I/O expanders, LCD controllers, amplifiers, and so on. And you can have more than one on the bus at any time, in fact the maximum number of I2C devices used at any one time is 112.

From a hardware perspective, the wiring is very easy. Those of you with an Arduino Duemilanove, Uno or 100% compatible board, you will be using pins A4 for SDA (data) and A5 for SCL (clock).

If you are using an Arduino Mega, SDA is pin 20 and SCL is 21, so note that shields with I2C need to be specifically for the Mega. If you have another type of board, check your data sheet or try the Arduino team’s hardware website.  And finally,if you are using a bare DIP ATmega328-PU microcontroller, you will use pins 27 for SDA and 28 for SCL.

The bus wiring is simple:

If you are only using one I2C device, the pull-up resistors are not required, as the ATmega328 microcontroller in our Arduino has them built-in.  However if you are running a string of devices, use two 10 kilo ohm resistors. Like anything, some testing on a breadboard or prototype circuit will determine their necessity. The maximum length of an I2C bus is around one metre, and is a function of the capacitance of the bus. This distance can be extended with the use of a special IC, which we will examine during the next I2C chapter.

Each device can be connected to the bus in any order, and devices can be masters or slaves. In our Arduino situation, the board is the master and the deviceson the I2C bus are the slaves. We can write data to a device, or read data from a device. By now you should be thinking “how do we differentiate eachdevice on the bus?”… Each device has a unique address. We use that address in the functions described later on to direct our read or write requests to the correct device. It is possible to use two identical devices on an I2C bus, but that will be discussed in a later article.

As like most devices, we make use of an Arduino library, in this case <wire.h>. Then use the function Wire.begin(); inside of void setup() and we’re ready to go.

Sending data from our Arduino to the I2C devices requires two things: the unique device address (we need this in hexadecimal) and at least one byte of data to send. For example, the address of the part in example 20.1 (below) is 00101111 (binary) which is 0X3F in hexadecimal. Then we want to set the wiper value, which is a value between 0 and 127, or 0×00 and 0x7F in hexadecimal. So to set the wiper to zero, we would use the following three functions:

Wire.beginTransmission(0x2F);      // part address is 0x2F or 0101111b

This sends the device address down the SDA (data) line of the bus. It travels along the bus, and “notifies” the matching device that it has some data coming…

Wire.send(0x3F); //

This sends the byte of data to the device – into the device register (or memory of sorts), which is waiting for it with open arms. Any other devices on the bus will ignore this. Note that you can only perform one I2C operation at a time! Then when we have finished sending data to the device, we “end transmission”. This tells the device that we’re finished, and frees up the I2C bus for the next operation:

Wire.endTransmission();

Some devices may have more than one register, and require more bytes of data in each transmission. For example, the DS1307 real-time clock IC has eight registers to store timing data, each requiring eight bits of data (one byte):

However as the I2C bus is a serial bus, we cannot directly change any register we like – the entire lot need to be rewritten every time. So in this case we would use eight wire.send(); functions every time. Each device will interpret the byte of data sent to it, so you need the data sheet for your device to understand how to use it.

Receiving data from an I2C device into our Arduino requires two things: the unique device address (we need this in hexadecimal) and the number of bytes of data to accept from the device. Receiving data at this point is a two stage process. If you review the table above from the DS1307 data sheet, note that there is eight registers, or bytes of data in there. The first thing we need to do is have the I2C device start reading from the first register, which is done by sending a zero to the device:

Wire.beginTransmission(device_address);
Wire.send(0);
Wire.endTransmission();

Now the I2C device will send data from the first register when requested. We now need to ask the device for the data, and how many bytes we want. For example, if a device held three bytes of data, we would ask for three, and store each byte in its own variable (for example, we have three variables of type byte: a, b, and c. The first function to execute is:

Wire.requestFrom(device_address, 3);

Which tells the device to send three bytes of data back to the Arduino. We then immediately follow this with:

*a = Wire.receive();
*b = Wire.receive();
*c = Wire.receive();

We do not need to use Wire.endTransmission() when reading data. Now that the requested data is in their respective variables, you can treat them like any ordinary byte variable.

For a more detailed explanation of the I2C bus, read this explanatory document by NXP. Now let’s use our I2C knowledge by controlling a range of devices…

Example 20.1

A new part for today, the Microchip MCP4018T digital linear potentiometer. The value of this model is 10 kilo ohms. Inside this tiny, tiny SMD part is a resistor array consisting of 127 elements and a wiper that we control by sending a value of between 0 and 127 (in hexadecimal) down the I2C bus. This is a volatile digital potentiometer, it forgets the wiper position when the power is removed. However naturally there is a compromise with using such a small part, it is only rated for 2.5 milliamps – but used in conjunction with op amps and so on. For more information, please consult the data sheet.

As this is an SMD part, for breadboard prototyping purposes it needed to be mounted on a breakout board. Here it is in raw form:

Above the IC is a breakout board. Consider that the graph paper is 5mm square! It is the incorrect size, but all I have. However soldering was bearable. Put a drop of solder on one pad of the breakout board, then hold the IC with tweezers in one hand, and reheat the solder with the other hand – then push the IC into place. A few more tiny blobs of solder over the remaining pins, and remove the excess with solder wick. Well … it worked for me:

Our example schematic is as follows:


As you can see, the part is simple to use, your signal enters pin 6 and the result of the voltage division is found on pin 5. Please note that this is not a replacement for a typical mechanical potentiometer, we can’t just hook this up as a volume or motor-speed control! Again, please read the data sheet.

Control is very simple, we only need to send one byte of data down, the hexadecimal reference point for the wiper, e.g.:

Wire.beginTransmission(0x2F);      // part address is 0x2F or 0101111b
Wire.send(0x3F); //
Wire.endTransmission();

Here is a quick demonstration that moves the wiper across all points:

/*
Example 20.1  Microchip MCP4018 digital potentiometer demonstration sketch
 http://tronixstuff.com/tutorials > chapter 20  CC by-sa v3.0
*/
int dt = 2000; // used for delay duration
byte rval = 0x00; // used for value sent to potentiometer
#include "Wire.h"
#define pot_address 0x2F // each I2C object has a unique bus address, the MCP4018 is 0x2F or 0101111 in binary
void setup()
{
Wire.begin();
Serial.begin(9600);
}
void potLoop()
// sends values of 0x00 to 0x7F to pot in order to change the resistance
// equates to 0~127
{
for (rval=0; rval<128; rval++)
{
Wire.beginTransmission(pot_address);
 Wire.send(rval);
  Wire.endTransmission();
 Serial.print(" sent -  ");
Serial.println(rval, HEX);
delay(dt);
}
}
  void loop()
{
  potLoop();
}

and a video demonstration:



Example 20.2

Now we will read some data from an I2C device. Our test subject is the ST Microelectronics CN75 temperature sensor. Again, we have another SMD component, but the CN75 is the next stage larger than the part from example 20.1. Thankfully this makes the soldering process much easier, however still requiring some delicate handiwork:

First, a small blob of solder, then slide the IC into it. Once that has cooled, you can complete the rest and solder the header pins into the breakout board:

Our example schematic is as follows:


Pins 5, 6 and 7 determine the final three bits of the device address – in this case they are all set to GND, which sets the address to 1001000. This allows you to use multiple sensors on the same bus. Pin 3 is not used for basic temperature use, however it is an output for the thermostat functions, which we will examine in the next chapter.

As a thermometer it can return temperatures down to the nearest half of a degree Celsius. Although that may not be accurate enough, it was designed for automotive and thermostat use. For more details please read the data sheet. The CN75 stores the temperature data in two bytes, let’s call them A and B. So we use

Wire.requestFrom(cn75address, 2)

with the second paramater as 2, as we want two bytes of data. Which we then store using the following functions:

*a = Wire.receive(); // first received byte stored here
*b = Wire.receive(); // second received byte stored here

where *a and *b are variables of the type byte.

And as always, there is a twist to decoding the temperature from these bytes. Here are two example pieces of sample data:

Example bytes one: 00011001 10000000
Example bytes two: 11100111 00000000

The bits in each byte note particular values… the most significant bit (leftmost) of byte A determines whether it is below or above zero degrees – 1 for below zero. The remaining seven bits are the binary representation of the integer part of the temperature; if it is below zero, we subtract 128 from the value of the whole byte and multiply by -1. The most significant bit of byte B determines the fraction, either zero or half a degree. So as you will see in the following example sketch, there is some decision making done in showCN75data():

/* Example 20.2
ST Microelectronics CN75 Digital Temperature sensor demonstration sketch
CC by-sa v3.0
*/
#include "Wire.h"
#define cn75address 0x48 // with pins 5~7 set to GND, the device address is 0x48
void setup()
{
Wire.begin(); // wake up I2C bus  Serial.begin(9600);
}
void getCN75data(byte *a, byte *b)
{
 // move the register pointer back to the first register
 Wire.beginTransmission(cn75address); // "Hey, CN75 @ 0x48! Message for you"
Wire.send(0);        // "move your register pointer back to 00h"
Wire.endTransmission(); // "Thanks, goodbye..."
// now get the data from the CN75
Wire.requestFrom(cn75address, 2); // "Hey, CN75 @ 0x48 - please send me the contents of your first two registers"
 *a = Wire.receive(); // first received byte stored here
*b = Wire.receive(); // second received byte stored here
}
void showCN75data()
{
byte aa,bb;
 float temperature=0;
 getCN75data(&aa,&bb);
if (aa>127) // check for below zero degrees
{
 temperature=((aa-128)*-1);
if (bb==128)    // check for 0.5 fraction
{
 temperature-=0.5;
 }
 }   else // it must be above zero degrees
{
 temperature=aa;
 if (bb==128)   // check for 0.5 fraction
 {
temperature+=0.5;
 }
 }
Serial.print("Temperature = ");
Serial.print(temperature,1);
Serial.println(" degrees C");
delay(1000);
}
void loop()
{
 showCN75data();
}

And here is the result from the serial monitor:

Example 20.3

Now that we know how to read and write data to devices on the I2C bus – here is an example of doing both, with a very popular device – the Maxim DS1307 real-time clock IC. Maxim have written an good data sheet. For those of you new to the world of tronixstuffwe use this part quite often, for example with our Arduino RTC shield and modifications, or blinky – the one-eyed clock. It is an 8-pin DIP IC that allows timing with accuracy down to a few seconds a day:

Furthermore, it also has a programmable square-wave generator. Connection and use is quite simple:

However some external components are required: a 32.768 kHz crystal, a 3V battery for time retention when the power is off, and a 10k ohm pullup resistor is required if using as a square-wave generator. You can use the SQW and timing simultaneously. If we have a more detailed look at the register map for the DS1307:

We see that the first seven registers are for timing data, the eighth is the square-wave control, and then another eight RAM registers. In this chapter we will look at the first eight only. Hopefully you have noticed that various time parameters are represented by less than eight bits of data – the DS1307 uses binary-codeddecimal. But don’t panic, we have some functions to do the conversions for us.

However, in general  - remember that each bit in each register can only be zero or one – so how do we represent a register’s contents in hexadecimal? First, we need to find the binary representation, then convert that to hexadecimal. So, using the third register of the DS1307 as an example, and a time of 12:34 pm – we will read from left to right. Bit 7 is unused, so it is 0. Bit 6 determines whether the time kept is 12- or 24-hour time. So we’ll choose 0 for 12-hour time. Bit 5 (when bit 6 is 0) is the AM/PM indicator – choose 1 for PM. Bit 4 represents the left-most digit of the time, that is the 1 in 12:34 pm. So we’ll choose 1. Bits 3 to 0 represent the BCD version of 2 which is 0010.

So to store 12pm as hours we need to write 00110010 as hexadecimal into the hours register – which is 0×32.

Reading data from the DS1307 should be easy for you now, reset the register pointed, then request seven bytes of data and receive them into seven variables. The device address is 0×68.  For example:

Wire.beginTransmission(0x68);
Wire.send(0);
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
*second     = bcdToDec(Wire.receive();
*minute     = bcdToDec(Wire.receive();
*hour       = bcdToDec(Wire.receive();
*dayOfWeek  = bcdToDec(Wire.receive());
*dayOfMonth = bcdToDec(Wire.receive());
*month      = bcdToDec(Wire.receive());
*year       = bcdToDec(Wire.receive());

At which point the time data will need to be converted to decimal numbers, which we will take care of in the example sketch later. Setting the time, or controlling the square-wave output is another long operation – you need to write seven variables to set the time or eight to change the square-wave output. For example, the time:

Wire.beginTransmission(0x68);
Wire.send(0);
Wire.send(decToBcd(second));
Wire.send(decToBcd(minute));
Wire.send(decToBcd(hour));
Wire.send(decToBcd(dayOfWeek));
Wire.send(decToBcd(dayOfMonth));
Wire.send(decToBcd(month));
Wire.send(decToBcd(year));
Wire.endTransmission();

The decToBcd is a function defined in our example to convert the decimal numbers to BCD suitable for the DS1307. You could insert another Wire.send(); before the end transmission, and this would set the square wave output. There are five options for the parameter: 0×00 for off, 0×10 for 1Hz, 0×11 for 4.096 kHz, 0×12 for 8.192 kHz and 0×13 for 32.768 kHz.

For examples, I will not repeat myself and instead direct you to the list of many tronixstuff articles that make use of the DS1307.

So there you have it – hopefully an easy to understand introduction to the world of the I2C bus and how to control the devices within. We will have one or two more articles explaining the complexities of I2C and controlling more devices in the weeks ahead – so stay tuned! If you have any questions about the processes or details in this article, please ask in our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, there is the odd competition or give-away –  and we can all learn something.

Otherwise, have fun, stay safe, be good to each other – and make something!