|
|
View previous topic :: View next topic |
Author |
Message |
weg22
Joined: 08 Jul 2005 Posts: 91
|
How to write a LONG to EEPROM?? |
Posted: Wed Jun 21, 2006 10:21 am |
|
|
Hi all,
I'm trying to write a LONG to EEPROM. I have successfully written code to write a float to EEPROM and have just modified it to write 2 bytes (long) instead of 4 (float). When reading it from the EEPROM chip and then printing it to hyperterminal, I get a value of -31650. Anyone have any ideas?
Thanks in advance,
-weg
Code: |
long data = 350;
long getData = 0;
int addressHIGH=0, addressLOW=0;
int i=0, j=0;
// initialize external eeprom
output_float(eeprom_scl);
output_float(eeprom_sda);
// write to eeprom
for(i=0; i<2; i++)
{
i2c_start(); // inintializes i2c communication
i2c_write(0xa0); // 10100000
i2c_write(addressHIGH); // initializes HIGH address byte
i2c_write(addressLOW); // initializes LOW address byte
i2c_write(*(&data + i));
delay_ms(5);
i2c_stop();
addressLOW++;
}
addressLOW=0;
// read from eeprom
for(j=0; j<2; j++)
{
i2c_start();
i2c_write(0xa0); // 10100000
i2c_write(addressHIGH); // initializes HIGH address byte
i2c_write(addressLOW); // initializes LOW address byte
i2c_start();
i2c_write(0xa1); // 10100001
(*(&getData + j)) = i2c_read(0);
i2c_stop();
addressLow++;
}
output_high(LED);
fprintf(PC, "getData: %ld\n\r", getData);
|
|
|
|
newguy
Joined: 24 Jun 2004 Posts: 1909
|
|
Posted: Wed Jun 21, 2006 10:40 am |
|
|
You're making it a bit more complicated than it needs to be. When you read something from an I2C EEPROM, you only need to set the read address for the first read. You then issue another read right after the first, which fetches the next byte in the EEPROM. Further sequential reads just keep advancing through memory.
Here are my standard I2C memory functions, straight out of one of my projects (so I know they work):
Code: | #define WRITE 0
#define READ 1
void write_one_byte(int16 address, int8 data);
int8 control_byte = 0xa0;
int8 load_data_from_address(int16 address) {
int8 high_byte, low_byte;
high_byte = (address & 0xff00) >> 8;
low_byte = address & 0x00ff;
i2c_start();
i2c_write(control_byte | WRITE); // write
i2c_write(high_byte);
i2c_write(low_byte);
i2c_start(); // restart
i2c_write(control_byte | READ); // read
low_byte = i2c_read(0); // no ack
i2c_stop();
return (low_byte);
}
int8 load_data_no_address(void) {
int8 data;
i2c_start();
i2c_write(control_byte | 1); // read
data = i2c_read(0); // no ack
i2c_stop();
return (data);
}
void write_one_byte(int16 address, int8 data) {
int8 high_byte, low_byte, ack = 1;
high_byte = address >> 8;
low_byte = address;
while (ack == 1) {
i2c_start();
ack = i2c_write(control_byte | WRITE);
}
i2c_write(high_byte);
i2c_write(low_byte);
i2c_write(data);
i2c_stop();
} |
An example of reading an int16 from memory and reconstructing the variable:
Code: |
int8 i, j;
write_one_byte(STATS_ADDRESS + 6, stats.average); // write LSB of stats.average
write_one_byte(STATS_ADDRESS + 7, stats.average >> 8); write MSB of stats.average
i = load_data_from_address(STATS_ADDRESS + 6);
j = load_data_no_address();
stats.average = ((int16)j << 8) + (int16)i; |
Obviously the data was written into EEPROM LSB first.
Last edited by newguy on Wed Jun 21, 2006 10:44 am; edited 1 time in total |
|
|
Ttelmah Guest
|
|
Posted: Wed Jun 21, 2006 10:44 am |
|
|
What you post, should not have worked for a float either!...
The key is that if you add '1' to a pointer that is a pointer to a long, the pointer will move forward by the length of the long (two bytes), rather than by one byte. On a float, the pointer should move forward by 4!...
To add a one byte offset to a pointer, you need to 'cast it, to be a pointer to an int8, or a char. So:
i2c_write(*((int8 *)(&data) + i));
and
(*((int8 *)(&getData) + j)) = i2c_read(0);
This then makes the addition move in 'int8' sized lumps.
You are actually getting the correct bottom byte, but a garbaged top byte value.
Best Wishes |
|
|
weg22
Joined: 08 Jul 2005 Posts: 91
|
|
Posted: Wed Jun 21, 2006 10:45 am |
|
|
I was kind of hoping to get an answer based on my code and not a bunch of new functions on EEPROM. Yes, I agree it's a bit more complicated, but it should still work. I'm just wondering why it doesn't?
I mean all I did was change the variables "data" & "getData" from floats to longs and just reduced the i,j loops to go from 0->2 instead of 0->4.
Thanks,
-weg |
|
|
weg22
Joined: 08 Jul 2005 Posts: 91
|
|
Posted: Wed Jun 21, 2006 10:57 am |
|
|
Ttelmah,
After changing my i2c_write and i2c_read lines to what you have posted, I still get the same result: getData = -31650. Any ideas?
Thanks,
-weg |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Wed Jun 21, 2006 12:51 pm |
|
|
My idea is, don't re-invent the wheel. I think your problem is that
you are re-writing low-level code that is already done in the CCS
eeprom driver files. So you're distracted from the actual problem,
which is how to split up a word into bytes for writing, and how to
re-join the two bytes together after reading. Notice in the code
below that by using the existing driver, all effort is put into solving
the actual problem and not into low-level i2c stuff. Also, the two
things you want to do, are made into functions. These can then
be re-used in the future, whereas your inline low-level stuff is not
easily re-usable. Making things into routines and re-using them
later is a key point in being a productive programmer.
Code: |
#include <16F877.H>
#fuses XT, NOWDT, NOPROTECT, NOBROWNOUT, PUT, NOLVP
#use delay(clock = 4000000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7, ERRORS)
// Change EEPROM pins to fit PicDem2-Plus board.
#define EEPROM_SDA PIN_C4
#define EEPROM_SCL PIN_C3
#include <24256.c> // EEPROM driver
// Macro to access MSB of a word.
#define hi(x) (*((int8 *)&x+1)) // Fixed. This is the corrected macro.
//-----------------------------------
void write_word(int16 address, int16 data)
{
write_ext_eeprom(address, data); // Write LSB
write_ext_eeprom(address+1, data >> 8); // Write MSB
}
//----------------------------------
int16 read_word(int16 address)
{
int16 retval;
retval = read_ext_eeprom(address); // Read LSB
hi(retval) = read_ext_eeprom(address +1); // Read MSB
return(retval);
}
//======================================
void main()
{
int16 data = 350;
int16 address = 0;
int16 value;
init_ext_eeprom();
write_word(address, data);
value = read_word(address);
printf("value read = %ld \n\r", value);
while(1);
} |
-------
Edited to fix the hi(x) macro so that it works with vs. 4.xxx of the
compiler (and all other versions).
Last edited by PCM programmer on Wed Jun 04, 2008 10:43 am; edited 1 time in total |
|
|
Ttelmah Guest
|
|
Posted: Wed Jun 21, 2006 2:37 pm |
|
|
As a comment, are you sure you are using the same compiler version that worked for the floats?. There was a fault some time ago, with the CCS compiler, _incorrectly_ incrementing pointers by 1, rather than the element size, and this would explain the previous code working. However with the casts, it should work OK....
Using 'make8/make16' or a union, seems much simpler to me.
Your 'Hi' macro, PCM, would have the same problem as the original code, if the compiler correctly handled pointer arithmetic...
Best Wishes |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Wed Jun 21, 2006 2:51 pm |
|
|
Not that this is the problem, but %ld is for signed numbers and long is unsigned by default.
In your original code, what happens if the address is 0x00FF and you write 2 bytes. Answer, the upper byte gets written to 0x0000. |
|
|
weg22
Joined: 08 Jul 2005 Posts: 91
|
|
Posted: Wed Jun 21, 2006 4:36 pm |
|
|
Okay, I followed "newguy's" example code and now I get the output as getData=-1. Am I doing something wrong?
Thanks again,
-weg
Code: |
#include <16F877A.H>
#include <stdlib.h>
#include <math.h>
#define LED PIN_C0
#define eeprom_sda PIN_C4
#define eeprom_scl PIN_C3
#fuses HS,NOWDT,NOPROTECT,PUT,NOLVP
#use delay(clock=10000000)
#use rs232(baud=9600, xmit=PIN_D6, rcv=PIN_D7, stream=PC)
#use i2c (master, sda=eeprom_sda, scl=eeprom_scl)
#define WRITE 0
#define READ 1
int8 control_byte = 0xa0;
int8 load_data_from_address(int16 address) {
int8 high_byte, low_byte;
high_byte = (address & 0xff00) >> 8;
low_byte = address & 0x00ff;
i2c_start();
i2c_write(control_byte | WRITE); // write
i2c_write(high_byte);
i2c_write(low_byte);
i2c_start(); // restart
i2c_write(control_byte | READ); // read
low_byte = i2c_read(0); // no ack
i2c_stop();
return (low_byte);
}
int8 load_data_no_address(void) {
int8 data;
i2c_start();
i2c_write(control_byte | 1); // read
data = i2c_read(0); // no ack
i2c_stop();
return (data);
}
void write_one_byte(int16 address, int8 data) {
int8 high_byte, low_byte, ack = 1;
high_byte = address >> 8;
low_byte = address;
while (ack == 1) {
i2c_start();
ack = i2c_write(control_byte | WRITE);
}
i2c_write(high_byte);
i2c_write(low_byte);
i2c_write(data);
i2c_stop();
}
void main()
{
long data = 350;
long getData = 0;
long address=0;
int8 i=0, j=0;
output_high(LED); delay_ms(2000);
output_low(LED); delay_ms(1000);
write_one_byte(address, data); // write LSB of data
write_one_byte(address+1, data >> 8); // write MSB of data
i = load_data_from_address(address);
j = load_data_no_address();
getData = ((int16)j << 8) + (int16)i;
output_high(LED);
fprintf(PC, "getData: %ld\n\r", getData);
} |
|
|
|
heath.g
Joined: 20 Jun 2006 Posts: 6
|
|
Posted: Wed Jun 21, 2006 5:28 pm |
|
|
Just going back to your original issue. I have had the same problems. When working with INT16 and INT32 variables and needing to send them to an external device either serial or otherwise on a 8 bit data stream in a pain.
CCS assumes pointers arn't static so whenever you typecast a variable as a pointer it always uses the FSR.
So if you write:
INT8 i;
INT32 Address;
i = *(int)&Address + 1;
It will give you the 2nd byte in Address but at the expense of excessive instructions.
To overcome this I use a union and declare any INT16 or INT32 to their own union.
union uINT16
{
int16 INT16Data;
int8 Data[2];
};
When I declare the procedure:
void FRAMWrite(union uINT16 Address, int DataSize)
When calling the procedure just pass the address as a long. When you want to access the bytes in the Address do it like this:
SPI_WRITE2(Address.Data[1]); // set address high
SPI_WRITE2(Address.Data[0]); // set address low
Its a long winded way of getting the data but it works, and as in the previous example it cuts code space by half or more as the compiler now uses the constant value to retrieve the data.
I anybody has a better way I would be more than glad to see it. |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Wed Jun 21, 2006 5:46 pm |
|
|
Which EEPROM are you using? |
|
|
weg22
Joined: 08 Jul 2005 Posts: 91
|
|
Posted: Wed Jun 21, 2006 6:19 pm |
|
|
Ttelmah: Yes, I am using an older version of PCM and that's probably why my code worked for floating point numbers.
Mark: I am using Microchip's 24FC515 EEPROM chip. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Jun 22, 2006 11:58 am |
|
|
Quote: |
Your 'Hi' macro, PCM, would have the same problem as the original code,
if the compiler correctly handled pointer arithmetic... |
Actually I got that macro from CCS. It's in a few of the eeprom driver
files, such as 24128.C.
You're right about it. I think the macro could be made more
portable by casting 'x' to a char, as you showed above:
#define hi(x) (*(&(char)x+1)) |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|