AT24C256 Serial EEPROM (Memory)

advertisement
AT24C256 Serial EEPROM
2015 Adrian Krag
Objective
In this session we will learn how to us the AT24C256 Serial EEPROM. The Arduino has very
little non-volatile (doesn't forget when you turn
the power off) memory. Only enough for 512
integers, or 1000 characters. That's not very
much. The AT24C256 is a low cost device that
can remember up to 128,000 integers or 256,000
characters. When our projects need to gather data
and store it, this device can be very useful.
To use this device we will be looking at several of the "wire" library commands and how to use
the I2C interface. This information is very useful when using many different types of devices.
Wire.h
The wire library comes with the basic Arduino installation. It interfaces the Arduino with the
Wire class and allows you to communicate using I2C. I2C requires 4 wires - Power (5Volts)
Ground (GND), SCL (Serial Clock - A5) and SDA (Serial Data - A4) On the red Robo Arduinos,
these 4 pins are together in the lower left hand corner of the board - 4 male pins in a blue base.
Here we see the 4 pins connected to the Red
(GND), Blue (VCC), Yellow (SDA) and Green
(SCL) wires.
Connect these pins directly to the wires on the
device - AT24C256 or other I2C device.
Add the line
#include <wire.h>
to your Arduino program
We are ready to begin using the I2C interface to talk to the device. We will be working with the
EEPROM device, but the information is useful for most any I2C device.
These are the functions that are available in the TwoWire class. In this case the constructor does
not require any parameters and is inserted automatically as part of the include file and is names
"Wire". The last line in the include file and acts as the constructor for the class
extern TwoWire Wire;
It constructs a copy of the TwoWire Class and calls it Wire. Throughout our program we will
precede all functions with "Wire." This will tell the program that we are accessing one of the
functions listed below and that the code can be found in the Wire.cpp program from the library.
Wire Functions
These are the functions that the TwoWire class brings to our program.
begin()
requestFrom()
beginTransmission()
endTransmission()
write()
available()
read()
The first function to consider is Wire.begin(); The EEPROM uses the internal I2C block in the
processor. To use this in
communication, we must initialize
it, set registers and dedicate the A4
and A5 pins to the communication
functions. Wire.begin(); does this.
If you have multiple I2C devices
connected the Arduino, such as an
LCD, real time clock, etc, each will
have a begin() function. Some will
require parameters. I have not
evaluated all possible combinations,
but in many cases, only one begin()
is required.
In this program, I have connected the AT24C256 and an LCD on a single Arduino. As I am
using the lcd.begin(chars, lines); function, the Wire.begin() is optional since the lcd.begin()
initializes the I2C port.
Writing to the EEPROM
To write to the EEPROM, we use the beginTransmission(Address); function. Here we must
specify the address of AT24C256 device. All I2C devices have a 7 bit address. The first 4 bits
are set by the type of device - the LCD first 4 bits are 0100. For the EEPROM, the first 4 are
1010. The last 3 bits of the address is a number from 0 to 7 which is defined by the connections
to pins A0, A1, and A2. These can be changed by changing the shorting jumpers. The default,
like the picture shows is 000. We add a leading 0 to these 7 bits to create a byte. For the LCD
this address becomes 00100111 or in hex 0x27 since A0, A1, and A2 on the chip are all soldered
to the VCC net. For the EEPROM 01010000 or in hex the address is 0x50. Moving the jumpers
will change the last 3 digits of the address.
Suppose the jumpers for A0 and A2 were switched from 0 Volts to 5 Volts. What would the
address of the AT24C256 be?
_____________________
The EEPROM is a memory device. To be useful we write to the memory where it is stored, then
read it back at a later time when we need it. There are 2 kinds of memory used in computers and
microprocessors - Volatile (memory that only saves information as long as there is power on the
circuit) and non-volatile (memory that remembers even after the power is turned off). EEPROM
is non-volatile memory. When we write something to the EEPROM and then unplug the
Arduino, it stays in memory and will be there when we plug it back in.
To write a series of bytes to the memory, we first must send a 16 bit address for the start location.
We can start writing to any one of 32,768 locations. Each location can hold 8 bits, one byte of
information. We need 2 bytes (16 bits) to specify the location where we want to start writing to
or reading from. I2C only sends and receives 8 bits at a time, so it will require 2 writes to send
the address which is a 16 bit (2 byte) number.
For example if you wish to start writing at location 0x0827 We would first send a 0x08,
followed with a second write of 0x27.
What is the address of the last byte in the memory?
______________________________
What is the address of the first byte of memory?
______________________________
Wire.write (0x08); // Send the upper 8 bits.
Wire.write (0x27); // Send the lower 8 bits.
After we write the above to the EEPROM, the device knows we are going to send bytes. After
we set the start address, we can write one byte at a time to the device. Each time we write a byte
to the memory, the address is incremented. The first byte will be stored in memory location
0x0827. The next byte
will be stored in 0x0828
and so on. Let's look at an
example:
In this program we
#include the TwoWire
class of functions with the
first line of the program.
#include <Wire.h>
Next the program creates a
global array of characters
called msg[] and it is initialized with a few letters.
Wire.begin(); tells the processor we will be using the I2C port and to set aside A4 and A5 for
serial communications. Next we are going to send a message We start with
Wire.beginTransmission(0x50); This tells the processor what address to use to get the attention
of the EEPROM.
Next we must set the address of the first memory we want to talk to. This can be any number
between 0, and 32767 or in hex 0 to 0x7FFF. In the above example I picked a memory location
at random. Finally we are ready to send the characters in the msg[] array. We send the bytes 1
at a time starting with msg[0] 'H' stored in 0x148, then msg[1] 'e' stored in 0x149, and msg[2] 'l'
stored in 0x014A and so on.
Note the sizeof(msg) function. This returns the number of bytes in the "msg" array. It will cause
the loop to stop when the end of the message is reached. This only returns the number of bytes
in the array. Each character takes one byte. If this had been an array of integers, this would have
had to be 2*sizeof(array), since each integer has 2 bytes.
How may bytes are in the msg[] array - Hint don't forget to count the spaces
__________________________
When we start writing, and give the start address, the EEPROM keeps storing each byte that
comes in the next location, until we specify that we are finished. Wire.endTransmission(); tells
that we are finished. The EEPROM now stops expecting bytes to store, and is now expecting
another start command Wire.beginTransmission(0x50), followed by another 2 byte address.
Reading from the EEPROM
To read data that has been stored in the EEPROM, we begin by sending the address where we
want to read the first byte of information. We have to send the start address (2 bytes) just as we
did with the write operation, followed by Wire.endTransmission(). We need to remember where
we put the string if we are going to get it back.
Now that I have set the start
address, the program requests
bytes from the memory locations
using the
Wire.requestFrom(address, how
many); instruction requires 2
parameters. The I2C address
and the number of bytes the
EEPROM is supposed to send.
Again, the program uses the
sizeof(msg) to tell the program
how many bytes to read. This instruction causes the EEPROM to send all the bytes (characters)
in the string to the Arduino. The Arduino has a buffer that collects and stores the data. This is
similar to the USART buffer used by the serial monitor. Notice in the for loop, the program uses
Wire.available(). This function returns the number of bytes that have been received and are
waiting in the buffer. Just as with Serial.available(), every time the program reads a byte from
the buffer, there is one less character available and Wire.available() eventually becomes 0 ending
the loop and read operations.
Saving and Retrieving Numbers
In the above, we worked with characters, each are one byte long. However, sometimes we want
to store and retrieve numbers that are more than one byte - integers are 2 bytes, long and float
variables are 4. We need to be able to break these numbers into bytes and send them one byte at
a time. When retrieving them we need to get all the bytes and reassemble them.
Integers and long variables are relatively easy. We can use shift function and a mask to separate
the pieces into bytes for storage, then we would recombine them using the OR functions. Let's
look at an example.
In this program we start with
a random long number
1254962917 - a big number,
too big for an integer and way
bigger than a byte. If we
want to store this in the
AT24C256 chip we will need
to break it up and store it as 4
separate bytes.
To do this the program begins
by creating a byte array btst[4]. The program will break up the long integer into 4 bytes and
store them in this array so they can be written to the memory in sequence. To break the long up
into bytes we use a device called "mask" and the "&" AND function to eliminate all but part of
the number.
This is a bit by bit operation. Remember, the AND function carried out by "&" part of the
instruction follows these rules for each bit. 1 & 1 = 1, 1 & 0 = 0, 0 & 0 = 0, 0 & 1 = 0.
So, where ever the mask is 0, the "&" will make the result 0. Wherever the mask is 1, the result
will be the same as the original.
Try to work some problems:
1101 & 0011 = __ __ __ __
1111 & 0101 = __ __ __ __
How did you come out? Did you get 0001 and 0101 for answers? The important thing to
understand is that since FF is 11111111, then where ever there is an FF in the mask, that part of
the number will not be changed. Wherever the mask is 00 the number will be set to 0s.
Consider this problem using hex numbers and a hex mask:
Number
0xA5463E2B
&
mask
0x000000FF = 0x_____________
0xA5463E2B
&
0x0000FF00 = 0x_____________
0xA5463E2B
&
0x00FF0000 = 0x_____________
Do you see how the mask will separate out the parts of the number? We also need to discuss the
">>" and the "<<" operators. These things shift the bits to the right and the left respectively.
Suppose I have a hex number 0x00340000 and I shift it 8 bits to the right - Remember each hex
digit is 4 bits and the computer will fill in blank 0s on the left. This number becomes
0x00003400.
Can you figure out what each of these operations will do?
0x00003600 >> 8 = _______________
0x00003600 << 16 = _______________
0x00003600 << 8 = _______________
0x00460000 >> 16 = _______________
0x54000000 >> 24 = _______________
This section of the code will separate the long into 4 byte size pieces that we can written to the
memory. Remember tst = 0xA5463E2B. These things are best done with numbers expressed in
HEX.
btst[0] = byte(tst & 0x000000FF);
btst[1] = byte((tst & 0x0000FF00) >> 8);
btst[2] = byte((tst & 0x00FF0000) >> 16);
btst[3] = byte((tst & 0xFF000000) >> 24);
Each of these bytes could be
written to the memory with
simple write commands.
Here we are going to look at
a program for storing long
integers.
It starts out just as we did above with the #include statement. The program defines a couple 4
byte arrays and the long integer that we have been working with. It is written in hex, but we
could convert it to the decimal equivalent of 1254962917.
First the program breaks the long integer into bytes as we did above. Then
Wire.beginTransmission(I2C_Address); a the program sends the memory address we want to use
0x0148 - This is selected at random. Normally we would start at 0x0000 and go from there, but
here we are using a different place in memory.
Now the AT24C256 is waiting for us to send bytes and it will receive them and store them
sequentially until the Wire.endTransmission() command.
After we have stored the code in the EEPROM, we will eventually want to retrieve it an put it
back together. Just as we use the mask, the "&" and ">>" to separate the number into bytes, the
program uses "<<" Shift and the "|" OR function to put it back together.
This code separates the big long
4 byte number in 4 - one byte
pieces.
Then we again send the start
address and read the 4 bytes
back into the Rbld[] (rebuild)
array.
Load the code an run it. You
should see the result on the
serial monitor that shows the
program recovering the number
correctly.
Floating Point Numbers.
This method works great for integers and long variables, but we cannot manipulate float numbers
with masks and shifts. To break up float numbers, it is appropriate to use a "union" function.
Consider the following code:
First we define a float variable "tst" and initialize
it to the number 345.687. Float variables are
stored in a complicated scientific notation that we
will not discuss here.
The program also creates another float variable
"ReBld" (Rebuild) which we will use to verify that
the reconstruction worked.
The next line is probably new. It creates a "union"
and names it FtoB. The union function says that
the byte array b[4] and the float f start at the same location. It specifies that they are the two
different ways to access the same processor memory. Finally the "union" is given a name "Un"
In setup() the program initializes the serial monitor and writes the floating point variable "tst" to
the screen so we can see it. Next, the program specifies that "tst" is copied into the float part of
the union with the instruction
Un.f = tst; // This not only puts the float variable test into the union variable Un.f, but also puts
it into the union variable Un.b[]. Remember "union" makes both Un.f and Un.b[] start at the
same location in processor memory.
Now the program writes the 4 bytes Un.b[] to the serial monitor with the for
statement. The serial monitor displays the initial float number, and the 4
bytes that make it up. Don't be overly concerned if these 4 bytes seem to
have little in common with the original number. float numbers are stored in
a complex scientific notation.
What's important is that we can put these number back together using the
same union that took them apart.
In the next line we can set ReBld = Un.f; remember Un.b[] is the same memory location as the
floating point Un.f so the bytes fall into place.
Let's write them to the EEPROM
and read them back - Look at this
program. Again, we include the
Wire.h include file and it's library
functions.
The program defines a "tst" float
variable, and assigns a random
number to it. Also, another float
variable that we will read from
memory.
In this case the program uses
memory locations 0x0000 - the first
address in the device as the start of
the location.
The program uses the "union" to allow us to access the individual bytes of the float number.
Un.f = tst; copies the tst variable into the memory defined byte the float variable Un.f and since
Un.b[] has the same address. we can access it as bytes.
The for statement in the program writes all 4 of these bytes into EEPROM address 0, 1, 2, and 3.
After all 4 are written, we Wire.endTransmission(); To read it back we must begin by sending
the address. We can only do this after a Wire.beginTransmission(); this means we must end
transmission before reading.
An interesting thing about EEPROM memory. When we write to it, it takes a few milliseconds
to actually get it written. If you try to read it to fast after writing it, you will find that it's not
written correctly. The delay(100); is there to insure that the write is complete before we again
try to read it back.
Now we're ready to read the bytes back. The program reads them directly into the Un.b[] array.
This has the effect of reading them into the Un.f float variable as well since they are in the same
memory locations.
Conclusions:
This has been a very informative discussion. The programs have sent data via the I2C interface
to the serial EEPROM memory available on the AT24C256 device. We have stored and
retrieved data.
The examples and questions, cover the use of ">>" and "<<" the shift functions. The "&" and "|"
AND and OR functions and how they are used to break ups and reassemble integers and long
variables. Also the lesson introduce the "union" functions and we saw a first look at how to do
direct memory manipulations.
I hope you found this informative and useful. As always, if you have any questions, please feel
free to contact me
Adrian Krag
707 321 6985
ak@thecije.org
Download