Tutorial - Arduino and The I2C Bus - Part Two
Tutorial - Arduino and The I2C Bus - Part Two
Home
Tronixlabs
Arduino Tutorials
Kit Reviews
Projects
Reviews
About
Contact Us
Categorized | 24LC256, arduino, I2C, learning electronics, lesson, microcontrollers, PCF8574, tutorial
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, lets 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, lets consult the pinout:
1 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
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 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:
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 lets 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:
2 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
3 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// Example 21/.1
#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.write(0);
Wire.endTransmission();
delay(dd+50);
Wire.beginTransmission(redchip);
Wire.write(255);
Wire.endTransmission();
delay(dd+50);
Wire.beginTransmission(yellowchip);
Wire.write(0);
Wire.endTransmission();
delay(dd+50);
Wire.beginTransmission(yellowchip);
Wire.write(255);
Wire.endTransmission();
delay(dd+50);
Wire.beginTransmission(greenchip);
Wire.write(0);
Wire.endTransmission();
delay(dd+50);
Wire.beginTransmission(greenchip);
Wire.write(255);
Wire.endTransmission();
delay(dd+50);
}
void testfunc2()
{
for (int y=1; y<256; y*=2)
{
Wire.beginTransmission(redchip);
Wire.write(255-y); // we need the inverse, that is high = off
Wire.endTransmission();
delay(dd);
Wire.beginTransmission(redchip);
Wire.write(255);
Wire.endTransmission();
delay(dd);
4 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
}
for (int y=1; y<256; y*=2)
{
Wire.beginTransmission(yellowchip);
Wire.write(255-y);
Wire.endTransmission();
delay(dd);
Wire.beginTransmission(yellowchip);
Wire.write(255);
Wire.endTransmission();
delay(dd);
}
for (int y=1; y<256; y*=2)
{
Wire.beginTransmission(greenchip);
Wire.write(255-y);
Wire.endTransmission();
delay(dd);
Wire.beginTransmission(greenchip);
Wire.write(255);
Wire.endTransmission();
delay(dd);
}
}
void testfunc3()
{
Wire.beginTransmission(redchip);
Wire.write(0);
Wire.endTransmission();
Wire.beginTransmission(yellowchip);
Wire.write(0);
Wire.endTransmission();
Wire.beginTransmission(greenchip);
Wire.write(0);
Wire.endTransmission();
delay(dd+50);
allOff();
delay(dd+50);
}
void allOff()
{
Wire.beginTransmission(redchip);
Wire.write(255);
Wire.endTransmission();
Wire.beginTransmission(yellowchip);
Wire.write(255);
Wire.endTransmission();
Wire.beginTransmission(greenchip);
Wire.write(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();
}
}
5 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
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. Dont 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 doesnt 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 (thats 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:
6 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
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:
1 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.
1 Wire.write(25000 >> 8); // send the left-hand side of the address down
2 Wire.write(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:
1 Wire.write(12);
2 Wire.endTransmission();
Then, ask for the byte(s) of data starting at the current address:
1 Wire.beginTransmission(0x50); // if pins A0~A2 are set to GND
2 Wire.requestFrom(0x50,1);
3 Wire.read(incomingbyte);
In this example, incomingbyte is a byte variable used to store the data we retrieved from the IC. Now we have the theory, lets 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
7 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Example 21.2
#include "Wire.h" // 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.write((int)(add >> 8)); // left-part of pointer address
Wire.write((int)(add & 0xFF)); // and the right
Wire.write(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
8 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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.
Have fun and keep checking into tronixstuff.com. Why not follow things on twitter, Google+, 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.
Bio
John Boxall
Founder, owner and managing editor of tronixstuff.com.
9 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
21
Like this:
1.
Marcel says:
October 29, 2010 at 12:42 am
Very useful. Thanks.
Im confused on the Sink / Source issue and dont see why a resistor is not necessary if its Sinking. :\
[m]
Reply
John Boxall says:
October 29, 2010 at 12:49 am
Hi Marcel
Thats a good question. It is peculiar to the TI PCF8574, it takes care of the current for us, whereas the 74HC595 current
source needs a resistor.
Thanks for pointing out that to me, Ill go back and change it a little.
Cheers
john
Reply
2.
3.
Sonny says:
10 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
rusty says:
January 12, 2011 at 7:00 am
Ive been reading & re-reading the datasheet for the PCF8574. What is the wording (or diagram) that tells the user the output is a
current sink. I am trying to use a PCA9555 and if not for the diagram (fig.19) that shows the hook-up of a LED I wouldnt have
known it was a current sink also. Help!
Rusty
Reply
John Boxall says:
January 12, 2011 at 10:36 am
On page one: At power on, the I/Os are high. In this mode, only a current source to VCC is active. Which means that there
is 5V at the pin, so current will not flow from 5V through the LED to the 8574s pin. In other words, a current sink.
Sometimes I think data sheet authors dont really write their documentation in an easily accessible form.
When we set the
pin to low, it becomes 0V, and current will flow from 5V through the LED to the pin. It seems a bit arse-about (backwards),
and to let current flow through all pins (turn all 8 LEDs on) we set all the pins low. Took me an hour or so to work it out as
well.
cheers
john
Reply
5.
6.
ram says:
May 5, 2013 at 3:32 pm
in PCF8574s coding .
i am confused how it is writing each led one by one
plzz guide me ..
i am novice !!
Reply
11 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
IK says:
June 13, 2013 at 10:23 pm
Im trying to use 24LC16B memory and found some issues with it. First, pins A0 A2 are not connected ..? (physically
available). This 16k ram uses different (not 2 byte) addressing than larger types. I would be happy if someone can explain me how
to use it with arduino wire
Reply
8.
sergio says:
July 21, 2013 at 4:09 am
Great work.
It is a good idea to end the code you show for the EEPROM with something that slows it down or stops it. If not you can put
unnecessary write to the eeprom as the loop keeps going.
Reply
9.
Patrick says:
August 13, 2013 at 10:41 am
Im using an Arduino Uno with the MCP23017 port expander to run a 4-digit 7-segment display. I want to count up to 9999 from 0.
Ive tried various coding options to get the 23017 to do this but havent found the correct coding. The Serial Monitor output shows
the counter code to be working correctly. I have read on an Arduino forum that the MCP23017 really isnt used for this purpose. Is
that true? What is happening is that digit 4 will count correctly 0 9, digit 3 then shows 1 and digit 4 shows 1 and they sequence
together 2 2, 3 3 until 99, then digit 2 shows 1, digit 3 shows 1 and digit 4 shows 1 and they sequence together 1-1-1, 2-2-2,
Using transistors wired to the output pins on the Arduino to drive the digits. Circuit is good. First time using an I2C device; have
read your articles and others but havent come across using the MCP23017 with a 4-digit display for counting purposes. Any
direction and help would be appreciated. Thanks
Reply
John Boxall says:
August 13, 2013 at 10:47 am
Youre right, the MCP23017 isnt really the right part.
Short answer if you have a common-anode display, get an NXP SAA1064 perfect for the job: http://tronixstuff.com
/2011/07/21/tutorial-arduino-and-the-nxp-saa1064-4-digit-led-display-driver/
Long answer The MCP23017 will happily set the segments for one digit and then just sit there until you tell it what to do.
Therefore it needs to be constantly refreshed.
That is you tell it to display the first digit, then the second digit, then the third digit, then the fourth digit at full speed.
And repeat. Its a mess as it ties up the Arduino, whereas you can send a number to an SAA1064 and then your sketch can
do something else.
Reply
Patrick says:
12 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
Trackbacks/Pingbacks
Leave a Reply
Name (required)
Mail (will not be published) (required)
Website
Visit tronixlabs.com
Helping you make it with Australia's best value for supported hobbyist electronics from adafruit, DFRobot, Freetronics, Seeed Studio and
more!
13 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
tronixstuff forum
Why not join our moderated discussion forum?
Bluetooth
Starter Kit
Jump start your bluetooth
data app with a flexible
PIC32 starter kit
Arduino Tutorials
Click for Detailed Chapter Index
Chapters 0 1 2 3 4
Chapters 5 6 6a 7 8
Chapters 9 10 11 12 13
Ch. 14 - XBee
Ch. 15 - RFID - RDM-630
Ch. 15a - RFID - ID-20
Ch. 16 - Ethernet
Ch. 17 - GPS - EM406A
Ch. 18 - RGB matrix - awaiting update
Ch. 19 - GPS - MediaTek 3329
Ch. 20 - I2C bus part I
Ch. 21 - I2C bus part II
Ch. 22 - AREF pin
Ch. 23 - Touch screen
Ch. 24 - Monochrome LCD
Ch. 25 - Analog buttons
Ch. 26 - GSM - SM5100 Uno
Ch. 27 - GSM - SM5100 Mega
Ch. 28 - Colour LCD
Ch. 29 - TFT LCD - coming soon...
Ch. 30 - Arduino + twitter
Ch. 31 - Inbuilt EEPROM
Ch. 32 - Infra-red control
Ch. 33 - Control AC via SMS
Ch. 34 - SPI bus part I
Ch. 35 - Video-out
Ch. 36 - SPI bus part II
Ch. 37 - Timing with millis()
Ch. 38 - Thermal Printer
Ch. 39 - NXP SAA1064
Ch. 40 - Push wheel switches
Ch. 40a - Wheel switches II
Ch. 41 - More digital I/O
Ch. 42 - Numeric keypads
Ch. 43 - Port Manipulation - Uno
Ch. 44 - ATtiny+Arduino
Ch. 45 - Ultrasonic Sensor
Ch. 46 - Analog + buttons II
Ch. 47 - Internet-controlled relays
Ch. 48 - MSGEQ7 Spectrum Analyzer
First look - Arduino Due
Ch. 49 - KTM-S1201 LCD modules
Ch. 50 - ILI9325 colour TFT LCD modules
14 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
15 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...
Australian Electronics!
Buy and support Silicon Chip - Australia's only Electronics Magazine.
Interesting Sites
Amazing Arduino Shield Directory
David L. Jones' eev blog
Silicon Chip magazine Always a great read!
Talking Electronics
Dangerous Prototypes
The Amp Hour podcast
Superhouse.tv High-tech home renovation
tronixstuff.com by John Boxall is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
16 http://tronixstuff.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part...