Log in

View Full Version : EEPROM Code


Kevin Watson
11-10-2005, 01:44
I've just posted software that can be used to read and write the Electrically Erasable Programmable Read-Only Memory (EEPROM) found in IFI's robot controllers. The code can be found here: http://kevin.org/frc. As always, if you find a bug in the code or a problem with the documentation, please let me know.

-Kevin


Here's the readme file:


The source code in eeprom.c/.h contains software to read from
and write to the Electrically Erasable Programmable Read-only
Memory (EEPROM) contained within your robot controller's
processor. Storing information in EEPROM has the advantage of
being permanent, unlike Random Access Memory (RAM), which
gets reinitialized each time you reset or restart your robot
controller.

See the code in Process_Data_From_Master_uP() for an example
application of this software.

This source code will work with the Robovation (A/K/A EDU-RC)
robot controller and the FIRST Robotics robot controller.

The included project files were built with MPLAB version 7.20.
If your version of MPLAB complains about the project version,
the best thing to do is just create a new project with MPLAB's
project wizard. Include every file except: FRC_alltimers.lib
and ifi_alltimers.lib and you should be able to build the code.
************************************************** **************
Three things must be done before this software will work
correctly with your robot controller:

1) You need to add eeprom.c and eeprom.h to your project.
Do this by copying the two files to your project directory
and then right clicking on "Source Files" in the project
tree, selecting "Add Files...", if necessary, navigate to
the project directory and then double click on eeprom.c.
Repeat the above procedure for eeprom.h under "Header Files".

2) A #include statement for the eeprom.h header file must be
included at the beginning of each source file that calls the
functions in eeprom.c. The statement should look like this:
#include "eeprom.h".

3) The function EEPROM_Write_Handler() function must be called
from Process_Data_From_Master_uP() each time it executes.
************************************************** **************
Here's a description of the functions in eeprom.c:

EEPROM_Read()
Reads data from EEPROM.

EEPROM_Write()
Places new write data on the EEPROM write queue. This function
returns a value of one if there was an available slot on the
queue, zero if there wasn't. EEPROM_Queue_Free_Space() should
be called to determine if enough free space is available on
the queue before calling EEPROM_Write(). By default, there
are sixteen slots available. The number of usable slots is
defined in eeprom.h.

EEPROM_Write_Handler()
If buffered EEPROM write data is present (i.e., EEPROM_Write()
has been called), this function will write one byte to EEPROM
each time it is called. A call to this function should be placed
in and called each time Process_Data_From_Master_uP() is called.
If, on average, you need to write more than one byte of data to
EEPROM each time Process_Data_From_Master_uP() is called, you can
call it multiple times each loop. If you start experiencing wacky
behavior when your 'bot runs or get the red-light-of-death, you
might be attempting to write too much data too quickly. Because
it takes 2 milliseconds to write each byte of data to EEPROM and
all interrupts are disabled during this period, don't write data
to EEPROM when you need to service interrupts.

EEPROM_Queue_Free_Space()
Returns the number of free slots available on the EEPROM write
queue. This function should be called to determine if enough
free space is available on the write queue before calling
EEPROM_Write().

CJO
11-10-2005, 19:46
THANK YOU!!!!!!!!!

(Bows and kisses Kevin's feet)

The possibilities are so good.

P.S. Do you know if this will work on the VEXtroller?

P.P.S: Did I say thak you?


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(Rather than Double Post)

How does data need to be formatted when you pass it to the EEPROM_Write() function? Can you send variables by name?

Could you call this function using your printf/serial driver. For instance, type "Field Side: Left," and then have this set a variable (such as "rss" - robot start side") equal to 1 (for instance). And then have the handler write to the EEPROM so that when the robot is turned on again, it knows what side it is on without a switch.

Also, I am assuming that the read time is negligable compared to the write time (I.E. one does not need to woory about avoiding interrupts when reading?)

Greg Marra
11-10-2005, 20:06
Sweet.

We were considering using the EEPROM when we ran out of code space last year, and this ought to make it a lot easier to do.

Thanks!

Kevin Watson
11-10-2005, 20:21
...Do you know if this will work on the VEXtroller?I haven't tried it. I'm not really supporting the Vex controller because it's a commercial product.

How does data need to be formatted when you pass it to the EEPROM_Write() function? EEPROM_Write() is expecting a 10-bit address in an unsigned int and 8-bit data byte in an unsigned char.

Could you call this function using your printf/serial driver. For instance, type "Field Side: Left," and then have this set a variable (such as "rss" - robot start side") equal to 1 (for instance). And then have the handler write to the EEPROM so that when the robot is turned on again, it knows what side it is on without a switch.If I understand your question correctly, yes, data will be retained even after you power cycle the robot controller.

Also, I am assuming that the read time is negligable compared to the write time (I.E. one does not need to woory about avoiding interrupts when reading?)Yes, reads are much faster than writes and you don't (as far as I know) have to worry about interrupts when reading.

-Kevin

Kevin Watson
11-10-2005, 20:51
Sweet.

We were considering using the EEPROM when we ran out of code space last year, and this ought to make it a lot easier to do.

Thanks!Yes, EEPROM is a great place to locate, for example, trigonometric lookup table(s). A little piece of of code like this:

#include <math.h>
#include "eeprom.h"

unsigned int i;
unsigned char data;

//create a zero to ninety degree lookup table
for(i=0; i<=90; i++)
{
// wait, if necessary, for a free slot on the circular queue
while(EEPROM_Queue_Free_Space() == 0);

// normalize the output to the maximum value a byte variable can hold
data = (unsigned char)(255.0 * sin((float)i * 3.14159 / 180.0));

EEPROM_Write(i, data);
}
will generate one quadrant of a sine lookup table in EEPROM. Execute the code and you'll have a semi-permanent table in EEPROM that will allow you to very quickly solve sin(x) and cos(x).

-Kevin

CJO
12-10-2005, 01:18
Very cool, thanks.

kc8nod
12-10-2005, 11:53
Hi Kevin,

Thanks for all the effort, it looks great and we'll definately be using it. I'd like to make one suggestion though.
With so many novice progammers using your code, it's likely that someone will use the EEPROM_Write() function too much, even when the data doesn't need to be updated. A small change to EEPROM_Write() might avoid problems for these folks.


unsigned char EEPROM_Write(unsigned int address, unsigned char data)
{
unsigned char return_value;

// determine if this is really new data
if(data == EEPROM_Read(address))
{
return_value = 1;
}
else if(eeprom_queue_full == FALSE) // return error flag if the queue is full
{
// put the byte and its address on their respective circular queues
eeprom_queue_data[eeprom_queue_write_index] = data;
eeprom_queue_address[eeprom_queue_write_index] = address;


If the new data being written, is the same as the old data that's already there, then we never add it to the queue.

Thanks again for all the hard work.

Kevin Watson
12-10-2005, 13:35
Hi Kevin,

Thanks for all the effort, it looks great and we'll definately be using it. I'd like to make one suggestion though.
With so many novice progammers using your code, it's likely that someone will use the EEPROM_Write() function too much, even when the data doesn't need to be updated. A small change to EEPROM_Write() might avoid problems for these folks.


unsigned char EEPROM_Write(unsigned int address, unsigned char data)
{
unsigned char return_value;

// determine if this is really new data
if(data == EEPROM_Read(address))
{
return_value = 1;
}
else if(eeprom_queue_full == FALSE) // return error flag if the queue is full
{
// put the byte and its address on their respective circular queues
eeprom_queue_data[eeprom_queue_write_index] = data;
eeprom_queue_address[eeprom_queue_write_index] = address;


If the new data being written, is the same as the old data that's already there, then we never add it to the queue.

Thanks again for all the hard work.

Thanks, it's a good idea and I considered it, but I'd also have to search the queue for writes to the same address as well, which would make the code a bit more complex. If I had to consider all of the wacky things that people do with my code and try to save them from themself, the code would be even more complex and bloated <grin>.

-Kevin

CJO
12-10-2005, 19:31
So, to write "127" to the first data block would be

EEPROM_Write(1, 127)

and to get that value would be:

EEPROM_Read(1)

which would return 127?

Kevin Watson
12-10-2005, 19:59
Yes, that will work.


-Kevin

Rickertsen2
12-10-2005, 20:19
hrmm. I love the simplicity of this. Last year, i wrote overly complex EEPROM routines to allow data of arbitrary length to be stored with dynamic allocation and FAT table. It turns out, not a whole lot of that functionality was ever needed. A scheme like yours would have workded just fine. It makes me want to smack myself in the face and say "duh".

Tom Bottiglieri
12-10-2005, 20:24
How much variable is space is available in the onboard EEPROM?

Rickertsen2
12-10-2005, 20:40
How much variable is space is available in the onboard EEPROM?
1024 bytes

Kevin Watson
12-10-2005, 22:53
Yes, EEPROM is a great place to locate, for example, trigonometric lookup table(s). A little piece of of code like this:

#include <math.h>
#include "eeprom.h"

unsigned int i;
unsigned char data;

//create a zero to ninety degree lookup table
for(i=0; i<=90; i++)
{
// wait, if necessary, for a free slot on the circular queue
while(EEPROM_Queue_Free_Space() == 0);

// normalize the output to the maximum value a byte variable can hold
data = (unsigned char)(255.0 * sin((float)i * 3.14159 / 180.0));

EEPROM_Write(i, data);
}
will generate one quadrant of a sine lookup table in EEPROM. Execute the code and you'll have a semi-permanent table in EEPROM that will allow you to very quickly solve sin(x) and cos(x).

-Kevin

Ugh, I just realized this code will cause the red-light-of-death. Anyone care to venture a guess as to why it won't work? I'll post corrected code tomorrow-ish.

-Kevin

CJO
12-10-2005, 23:22
It will write only when there is no free space in the write que?

Matt Krass
12-10-2005, 23:28
It risks getting caught in an infinite loop waiting for the queue and breaks the comm sync on the controller?

sciguy125
12-10-2005, 23:29
Ugh, I just realized this code will cause the red-light-of-death. Anyone care to venture a guess as to why it won't work? I'll post corrected code tomorrow-ish.

-Kevin

I'm not sure, but I don't like that while

// wait, if necessary, for a free slot on the circular queue
while(EEPROM_Queue_Free_Space() == 0);

Won't that keep looping until the queue is full (which it never will be if it never leaves that loop)?

CJO
12-10-2005, 23:31
It risks getting caught in an infinite loop waiting for the queue and breaks the comm sync on the controller?
Which is the ultimate result of what I mentioned (put more nicely).

Mike Bortfeldt
13-10-2005, 09:52
Kevin,

I haven't tried this, but from the PIC documentation, it looks like you only have to disable the interrupts during the period that you call "pre-write sequence" and "execute the write" in the EEPROM_Write_Handler routine. You should be able to re-enable the interrupts after these 3 lines (from the 39609b Microchip document, section 7.4) rather than waiting until the 2ms write is complete. This may also eliminate the code error by allowing the high priority interrupts to mostly go on schedule.

Mike

Kevin Watson
13-10-2005, 13:38
I'm not sure, but I don't like that while

// wait, if necessary, for a free slot on the circular queue
while(EEPROM_Queue_Free_Space() == 0);

Won't that keep looping until the queue is full (which it never will be if it never leaves that loop)?Yep, it's the while loop. I'm trying to write ninety-one bytes, but the queue only has sixteen slots. The code sits in the for loop and eventually EEPROM_Queue_Free_Space() returns zero forever because EEPROM_Write_Handler() isn't getting called to do the actual EEPROM write. The only way the code would work is if he queue size were changed to ninety-one slots. The trig table code seems like something that teams could use, so I'm writing a version that'll generate sin()/cos() and tan() tables in EEPROM. I'll also write the code to do the table lookup.

-Kevin

Kevin Watson
13-10-2005, 14:25
Kevin,

I haven't tried this, but from the PIC documentation, it looks like you only have to disable the interrupts during the period that you call "pre-write sequence" and "execute the write" in the EEPROM_Write_Handler routine. You should be able to re-enable the interrupts after these 3 lines (from the 39609b Microchip document, section 7.4) rather than waiting until the 2ms write is complete. Thanks for the suggestion. I'd heard from another source that interrupts had to be disabled for the entire period to ensure a reliable write. As I wasn't sure what the answer was, I decided to err on the conservative side and disable interrupts globally for the entire period (at least on the first revision). When I get some time to work on it, I'll talk with the folks at Microchip to see what they say.

This may also eliminate the code error by allowing the high priority interrupts to mostly go on schedule.That isn't a problem because the high-priority interrupt never gets disabled (i.e., EEPROM_Write_Handler() never gets called). The problem is that getdata() and putdata() won't be called once the code gets stuck in the while() loop, which makes the master processor cranky. BTW, if EEPROM_Write Handler() gets called from Process_Data_From_Master_uP(), disabling the high-priority interrupt isn't a problem because it won't fire-off again until the next SPI packet arrives from the master processor in a period of time much greater than two milliseconds.

-Kevin

CJO
13-10-2005, 16:46
On a slightly different topic, does the addressing go from 0 to 1023 or 1 to 1024?

i.e. can I write to 0?

Kevin Watson
13-10-2005, 16:51
On a slightly different topic, does the addressing go from 0 to 1023 or 1 to 1024?

i.e. can I write to 0?The range is 0 to 1023. I guess I should add that bit of information to the read me file.

-Kevin

CJO
13-10-2005, 16:59
Thanks, this makes it perfect for recording tables

If you want (say) a table with the five attributes in each row
0, 5, 10, . . . = attribute a
1, 6, 11, . . . = attribute b
2, 7, 12, . . . = attribute c
3, 8, 13, . . . = attribute d
4, 9, 14, . . . = attribute e

Then to find a specific row number, you would take the row number, multiply by 5 and subtract five, and then start reading values?

Dave Flowerday
13-10-2005, 19:29
On a slightly different topic, does the addressing go from 0 to 1023 or 1 to 1024?

i.e. can I write to 0?
As a rule of thumb, software people always count starting with 0. ;)

CJO
13-10-2005, 21:46
I have met one or two systems which did not, and not having a controller with me at the moment to test it out, I figured I might as well ask.

Kevin Watson
14-10-2005, 02:18
I just wrote and tested some quick-n-dirty trig table code. Grab a copy of the EEPROM code here (http://kevin.org/frc) and add the code below. I only create a table covering zero to ninety degrees because sin(x) in the other three quadrants can be derived using sin(x) data in quadrant one (exercise left to the student). I'll release a more polished version at a later time.

This code creates the table:


#include <math.h>
#include "printf_lib.h"
#include "eeprom.h"
/************************************************** *****************************
*
* FUNCTION: Sine_Table()
*
* PURPOSE: Creates a sine table in EEPROM
*
* CALLED FROM:
*
* PARAMETERS: Unsigned int containing the address.
*
* RETURNS: 1 when finished creating table, 0 otherwise
*
* COMMENTS:
*
************************************************** *****************************/
unsigned char Sine_Table(unsigned int address)
{
static unsigned char angle = 0;
static unsigned char done_flag = 0;
static unsigned char sine;
if(EEPROM_Queue_Free_Space() > 0 && done_flag == 0)
{
// calculate normalized sine value
sine = (unsigned char)255.0 * sin((float)angle * 3.14159265 / 180.0);
// write the angle and sine value to EEPROM
EEPROM_Write(address + angle, sine);
// send diagnostic information to the terminal
printf("writing x=%u sin(x)=%u\r", (unsigned int)angle, (unsigned int)sine);
// are we done?
if(angle == 90)
{
done_flag = 1;
printf("Finished!\r\n");
}
else
{
angle++;
}
}
return(done_flag);
}


This code verifies that EEPROM was written correctly:


/************************************************** *****************************
*
* FUNCTION: Verify_Sine_Table()
*
* PURPOSE: Creates a sine table in EEPROM
*
* CALLED FROM:
*
* PARAMETERS: Unsigned int containing the address.
*
* RETURNS: 1 when finished creating table, 0 otherwise
*
* COMMENTS:
*
************************************************** *****************************/
unsigned char Verify_Sine_Table(unsigned int address)
{
static unsigned char angle = 0;
static unsigned char done_flag = 0;
if(done_flag == 0)
{
if(EEPROM_Read(address+angle) == (unsigned char)(255.0 * sin((float)angle * 3.14159265 / 180.0)))
{
printf("angle=%u verified\r", (unsigned int)angle);
}
else
{
printf("angle=%u failed\r\n", (unsigned int)angle);
}
if(angle == 90)
{
done_flag = 1;
printf("Finished!\r");
}
else
{
angle++;
}
}
return(done_flag);
}



This code goes into Process_Data_From_Master_uP():


if(Sine_Table(0) == 1)
{
Verify_Sine_Table(0);
}
EEPROM_Write_Handler();


-Kevin

kc8nod
09-11-2005, 14:11
The range is 0 to 1023. I guess I should add that bit of information to the read me file.

-Kevin
Hi Kevin,

I've got a question about EEPROM_Write().

unsigned char EEPROM_Write(unsigned int address, unsigned char data)
{
unsigned char return_value;

// return error flag if the queue is full
if(eeprom_queue_full == FALSE)
{
// put the byte and its address on their respective circular queues
eeprom_queue_data[eeprom_queue_write_index] = data;
eeprom_queue_address[eeprom_queue_write_index] = address;

// increment the queue byte count
eeprom_queue_count++;
The parameter address is an unsigned integer, but eeprom_queue_address is an array of unsigned chars. Aren't you chopping off two bits from the address? Or did you mean to define eeprom_queue_address as an unsigned int also?

Kevin Watson
09-11-2005, 15:09
Hi Kevin,

I've got a question about EEPROM_Write().

unsigned char EEPROM_Write(unsigned int address, unsigned char data)
{
unsigned char return_value;

// return error flag if the queue is full
if(eeprom_queue_full == FALSE)
{
// put the byte and its address on their respective circular queues
eeprom_queue_data[eeprom_queue_write_index] = data;
eeprom_queue_address[eeprom_queue_write_index] = address;

// increment the queue byte count
eeprom_queue_count++;
The parameter address is an unsigned integer, but eeprom_queue_address is an array of unsigned chars. Aren't you chopping off two bits from the address? Or did you mean to define eeprom_queue_address as an unsigned int also?Ugh, It may be a bug, but I won't have time to look at it until this evening.

-Kevin

Kevin Watson
09-11-2005, 18:36
Hi Kevin,

I've got a question about EEPROM_Write().

unsigned char EEPROM_Write(unsigned int address, unsigned char data)
{
unsigned char return_value;

// return error flag if the queue is full
if(eeprom_queue_full == FALSE)
{
// put the byte and its address on their respective circular queues
eeprom_queue_data[eeprom_queue_write_index] = data;
eeprom_queue_address[eeprom_queue_write_index] = address;

// increment the queue byte count
eeprom_queue_count++;
The parameter address is an unsigned integer, but eeprom_queue_address is an array of unsigned chars. Aren't you chopping off two bits from the address? Or did you mean to define eeprom_queue_address as an unsigned int also?Yep, it certainly looks like a bug, an old bug. I'm not sure what happened, but the latest code isn't on the website. I'd added a EEPROM_Wipe( ) function and incorporated the change Mike Bortfeldt suggested (above), but for some reason the code never got uploaded. I guess I'll have to up the dosage on my brain fart medication <grin>. Thanks for catching this.

-Kevin

Joel J
12-01-2006, 03:54
In the eeprom.c released on the 10th of January, I see the following opening lines of code:

//#include "ifi_picdefs.h"
#include <p18f8722.h>
#include "eeprom.h"

Is it safe to uncomment the first line, and remove the specific request for the p18f8722.h info header file, or is that required for the functionality of the code? Basically, as it is now, I won't be able to use it on the '05 controller (unless I modify line 2 every time I want to make the switch, which I could forget eventually). Just checking to see if that's a life or death thing.. I could just test the code, but I am not yet near a FIRST controller.

:)

Kevin Watson
12-01-2006, 11:37
In the eeprom.c released on the 10th of January, I see the following opening lines of code:

//#include "ifi_picdefs.h"
#include <p18f8722.h>
#include "eeprom.h"

Is it safe to uncomment the first line, and remove the specific request for the p18f8722.h info header file, or is that required for the functionality of the code? Basically, as it is now, I won't be able to use it on the '05 controller (unless I modify line 2 every time I want to make the switch, which I could forget eventually). Just checking to see if that's a life or death thing.. I could just test the code, but I am not yet near a FIRST controller.

:)Yes, use the first line for the 2004/2005 controllers. You can also just grab the code for the 2005 controller here: http://kevin.org/frc/2005. Other than the header file change, there is no difference in the EEPROM code itself.

-Kevin