Tutorial: Arduino and the I2C bus – Part Two

Part two of our Arduino and I2C bus tutorial.

[Updated 28/11/2014]

Today we are going to continue learning about the I2C bus and how it can work for us. If you have not already, please read and understand 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:

i2cbufferedss

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…

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 the 74HC595; 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:

pcf8574intdef

So if we set pins A0 to A2 to GND, our device address in binary will be 0100000, or 0x20 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:

sinksource1

In the example above, please note that the PCF8574N can take care of current limitation with LEDs, whereas the 74HC595 needs a current-limiting resistor to protect the LED.

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:

  • Arduino Uno or compatible board
  • A large solderless breadboard
  • Three PCF8574 I/O extenders
  • Eight each of red, green and yellow (or your choice) LEDs, each with a current draw of no more than 20mA
  • Two 4.7 kilo ohm resistors
  • Hook-up wires
  • Three 0.1 uF ceramic capacitors

Here is the schematic:

exam21p1schemss

… and the example board layout:

example21p1boardss

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:

 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.

LEDborder

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. 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:

24lc256bb

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, A1 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 0x50 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 as bitshift right. This 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:

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.

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

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:

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

In this example, incomingbyte is a byte variable used to store the data we retrieved from the IC. 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:

  • Arduino Uno or compatible board
  • A large solderless breadboard
  • Two Microchip 24LC256 EEPROMs (you can use 24LC512s as well)
  • Two 4.7 kilo ohm resistors
  • Hook-up wires
  • Two 0.1 uF ceramic capacitors

Here is the schematic:

examp21p2schemss

… the board layout:

exam21p2boardss

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:

And the output from the example sketch:

example21p2result

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.

If you enjoyed this article, or want to introduce someone else to the interesting world of Arduino – check out my book (now in a fourth printing!) “Arduino Workshop”.

visit tronixlabs.com

Have fun and keep checking into tronixstuff.com. Why not follow things on twitterGoogle+, subscribe  for email updates or RSS using the links on the right-hand column, or join our forum – dedicated to the projects and related items on this website.

The following two tabs change content below.

John Boxall

Founder, owner and managing editor of tronixstuff.com.

17 Responses to “Tutorial: Arduino and the I2C bus – Part Two”

  1. Marcel says:

    Very useful. Thanks.

    I’m confused on the Snk / Source ssue and don’t see why a resstor s not necessary f t’s Snkng. :\

    [m]

    • John Boxall says:

      H Marcel
      That’s a good queston. It s pecular to the TI PCF8574, t takes care of the current for us, whereas the 74HC595 current source needs a resstor.
      Thanks for pontng out that to me, I’ll go back and change t a lttle.
      Cheers
      john

  2. Lander Vanrobaeys says:

    H,

    I am new at I²C.
    I am usng 2 Compmonents workng wth I²C,
    MMA7660 & HMC5843. Accelerometer & Compass.
    But they use same regsters to read out.
    How can make ths work on a arduno uno?
    ’cause that devce only has 1 SDA and 1 SCL.

    Grtz

  3. Sonny says:

    In part one you mentoned you would look at the thermostat functon of the CN75 n part two. However part 2 addressed some other types of devces. Could you make a part 3 I2C and go over more of the CN75 functons? Also usng the PCF8574 would t be possble to control an H-Brdge wth ths chp usng I2C?

  4. rusty says:

    I’ve been readng & re-readng the datasheet for the PCF8574. What s the wordng (or dagram) that tells the user the output s a “current snk”. I am tryng to use a PCA9555 and f not for the dagram (fg.19) that shows the hook-up of a LED I wouldn’t have known t was a current snk also. Help!
    Rusty

    • John Boxall says:

      On page one: “At power on, the I/Os are hgh. In ths mode, only a current source to VCC s actve.” Whch means that there s 5V at the pn, so current wll not flow from 5V through the LED to the 8574’s pn. In other words, a current snk. Sometmes I thnk data sheet authors don’t really wrte ther documentaton n an easly accessble form. :) When we set the pn to low, t becomes 0V, and current wll flow from 5V through the LED to the pn. It seems a bt arse-about (backwards), and to let current flow through all pns (turn all 8 LEDs on) we set all the pns low. Took me an hour or so to work t out as well.
      cheers
      john

  5. Eugene Sherbin says:

    Accordng to your wrteup the PCF8574 s an I/O chp whch means t can read or wrte and accordng to the data sheet t can read f you set the R/W bt to hgh.

    So I have 2 questons based on that statement.

    Frst, how do I set the R/W bt n my code? It seems by default t’s low so n your code you ddn’t even have to menton t.

    And second, how do I then read the data? Do I just do Wre.requestFrom(address, quantty)?

    Thank you

  6. ram says:

    n PCF8574s codng ….
    am confused how t s wrtng each led one by one …
    plzz gude me ..
    am novce !!

    • John Boxall says:

      Look at the sketch Example 21.1, especally the followng:
      for (nt y=1; y<256; y*=2)
      {
      Wre.begnTransmsson(redchp);
      Wre.wrte(255-y); // we need the nverse, that s hgh = off
      Wre.endTransmsson();
      delay(dd);
      Wre.begnTransmsson(redchp);
      Wre.wrte(255);
      Wre.endTransmsson();
      delay(dd);
      }

  7. IK says:

    Im tryng to use 24LC16B memory and found some ssues wth t. Frst, pns A0 – A2 are not connected …..? (physcally avalable). Ths 16k ram uses dfferent (not 2 byte) addressng than larger types. I would be happy f someone can explan me how to use t wth arduno wre

  8. sergio says:

    Great work.
    It s a good dea to end the code you show for the EEPROM wth somethng that slows t down or stops t. If not you can put unnecessary wrte to the eeprom as the loop keeps gong.

  9. Patrick says:

    I’m usng an Arduno Uno wth the MCP23017 port expander to run a 4-dgt 7-segment dsplay. I want to count up to 9999 from 0. I’ve tred varous codng optons to get the 23017 to do ths but haven’t found the correct codng. The Seral Montor output shows the counter code to be workng correctly. I have read on an Arduno forum that the MCP23017 really sn’t used for ths purpose. Is that true? What s happenng s that dgt 4 wll count correctly 0 – 9, dgt 3 then shows 1 and dgt 4 shows 1 and they sequence together 2 – 2, 3 – 3 untl 99, then dgt 2 shows 1, dgt 3 shows 1 and dgt 4 shows 1 and they sequence together 1-1-1, 2-2-2, … Usng transstors wred to the output pns on the Arduno to drve the dgts. Crcut s good. Frst tme usng an I2C devce; have read your artcles and others but haven’t come across usng the MCP23017 wth a 4-dgt dsplay for countng purposes. Any drecton and help would be apprecated. Thanks

    • John Boxall says:

      You’re rght, the MCP23017 sn’t really the rght part.
      Short answer – f you have a common-anode dsplay, get an NXP SAA1064 – perfect for the job: http://tronxstuff.com/2011/07/21/tutoral-arduno-and-the-nxp-saa1064-4-dgt-led-dsplay-drver/
      Long answer – The MCP23017 wll happly set the segments for one dgt and then just st there untl you tell t what to do. Therefore t needs to be constantly refreshed.
      That s – you tell t to dsplay the frst dgt, then the second dgt, then the thrd dgt, then the fourth dgt – at full speed. And repeat. It’s a mess as t tes up the Arduno, whereas you can send a number to an SAA1064 and then your sketch can do somethng else.

Trackbacks/Pingbacks


Leave a Reply

Subscribe via email

Receive notifications of new posts by email.

The Arduino Book

Arduino Workshop

Für unsere deutschen Freunde

Dla naszych polskich przyjaciół ...

Australian Electronics!

Buy and support Silicon Chip - Australia's only Electronics Magazine.

Use of our content…

%d bloggers like this: