EEPROM addressing.

fernando_g

Senior Member
#1
Let me see if I'm getting my facts straight.

-A 24LC256 EEPROM has 261568 individual bits or 32696 bytes.
-Which means it has 32696 individual addresses
-Which means that if I want to store 5 byte variables, I should partition the memory in blocks of 6540 individual adresses.
So far so good?

Therefore the code to sequentially write these variables must look something like this:

Code:
low EEProt ' write enable eeprom
	i2cslave %10100000, i2cslow, i2cword 'EEPROM
	writei2c address,(cor_temp) 'partition the memory's 32696 addresses
	pause 10
	tmpwd1 = address + 6540
	writei2c tmpwd1,(lowind)
	pause 10
	tmpwd1 = address + 13080
	writei2c tmpwd1,(hiwind)
	pause 10 
	tmpwd1 = address + 19620
	writei2c tmpwd1,(cor_RH)
	pause 10
	tmpwd1 = address + 26160
	writei2c tmpwd1,(light)
	pause 10 
	'increment address 
inc_address:
	let address = address + 1
	if  address < 6540 then not_finish
So far so good?

And to sequentially send the data to the computer, then my code would be:

Code:
index = 0
transmit_data:
i2cslave %10100000, i2cslow, i2cword 'EEPROM
	readi2c index,(cor_temp)
	
        tmpwd1 = index + 6540
	readi2c tmpwd1,(lowind)
	
        tmpwd1 = index + 13080
	readi2c tmpwd1,(hiwind)
	
        tmpwd1 = address + 19620
	readi2c tmpwd1,(cor_RH)
	
        tmpwd1 = address + 26160
	readi2c tmpwd1,(light)
	
        Sertxd (#index,COM,#cor_temp,COM,#lowind,COM,#hiwind,COM,#cor_RH,COM,#light,COM,RET,LFEED) 'the red led will show acivity 
	let index = index + 1
	if index  > 6540 then all_done 'stop transmitting when that condition is met
	goto transmit_data

all_done: 'finished so send NULL
	Sertxd(0)
 

hippy

Technical Support
Staff member
#2
24LC256 = 256kbit = 256 x 1024 bits = 262144 bits, 32768 bytes.

That's 32768 addresses ( 0 through 32767 ) and partitioned into 5 blocks that's 6553 bytes per block.

But you are okay for the rest apart from the discrepancy in size of blocks and that will still work because you under-sized the blocks.

Instead of doing ...

Code:
WriteI2c address ...
tmpwd1 = address + 6540
WriteI2c tmpwd1 ...
tmpwd1 = address + 13080
WriteI2c tmpwd1 ...
tmpwd1 = address + 19620
[i]etc[/i]
Which means having to adjust each value when the block size changes, and could be error prone, you can do ...

Code:
WriteI2c address ...
tmpwd1 = 1 * 6540 + address
WriteI2c tmpwd1 ...
tmpwd1 = 2 * 6540 + address
WriteI2c tmpwd1 ...
tmpwd1 = 3 * 6540 + address
[i]etc[/i]
Or even ...

Code:
WriteI2c address ...
tmpwd1 = [b]address[/b] + 6540
WriteI2c tmpwd1 ...
tmpwd1 = [b]tmpwd1[/b] + 6540
WriteI2c tmpwd1 ...
tmpwd1 = [b]tmpwd1[/b] + 6540
[i]etc[/i]
You can replace the 6540 with SAMPLES ( or other suitable name ) then SYMBOL define that at the start of the program ...

Code:
Symbol SAMPLES = 6540
At the end of your loop ...

Code:
let address = address + 1
if address > 6540 then all_done
That should be a >= rather than > ...

Code:
let address = address + 1
if address >= 6540 then all_done
Or, if you changed 6540 to SAMPLES ...

Code:
let address = address + 1
if address >= SAMPLES then all_done
Added : 'address' in the writing part, becomes 'index' in the outputting.
 

AllyCat

Senior Member
#4
Hi,

You may have a good reason for organising the individual data bytes in separate groups, but bear in mind that the EEPROMs are basically organised in "pages" (or perhaps better considered as blocks). When you write one byte, the EEPROM has to rewrite the whole block around it. Also, the I2C may handle strings of sequential bytes more efficiently.

EEPROMs can eventually "wear out", so you are potentially bringing that event five times closer than if writing the bytes as a sequential string !

Cheers, Alan.
 

lbenson

Senior Member
#5
And if you write more than a byte at a time, you may cross a block boundary, which on some devices may wrap around to the beginning of the block rather than crossing over to the next one.
 
#6
By chance, my current project uses 5-byte records, also stored in a (actually, multiple) 24LC256 EEPROM/s. 12x5-byte records are stored in each 64-byte page and the remaining 4 bytes are used for other storage purposes, so there is no wasted space.

For my project, each record has a record ID number which is not stored in the EEPROM. Each record is stored and recovered using a 5-byte write/read command like
hi2cOut [bChipID], wEEPROMPtr, (@bPtrInc, @bPtrInc, @bPtrInc, @bPtrInc, @bPtrInc)

I wrote the following code to determine the location of each record. (SerTxds are purely for debugging)
Rich (BB code):
' **** GetRecPtrs: Calculate Chip ID (i2c address) and record pointer in EEPROM  ****
'
' Entry: wPktNum        Packet number 0 to 65535
'  Used: bRecordPtr     Record within a page 0 - 11
'        wPage          Page Ptr (512 pages per chip, up to 8 chips)
'        bChipID        Chip selector 0-7
'  Exit: bChipID        Initialised to i2c address of required (1 of 8) chip
'        wEEPROMPtr     Absolute address, within a chip
'        bPtr           Points to RAM Data buffer
'
GetRecPtrs:
         wPage = wPktNum / 12          'Global Page ID at this point - changes below
         bChipID = wPage / 512         'Chip number 0 to 7
         SerTxd ("Pkt=,", #wPktNum, ", GlblPage=,", #wPage, ", Chip=,", #bChipID)
         wPage = wPage // 512          'Page ID within a chip - 0 to 511
         bRecordPtr = wPktNum // 12 * 5
         wEEPROMPtr = wPage * 64 + bRecordPtr
         SerTxd (", LclPage=,", #wPage, ", RecPtr=,", #bRecordPtr, ", Addr=,", #wEEPROMPtr, CR, LF)
         bChipID = bChipID * 2 Or i2cAddr24LC256   'Is now the x2 shifted i2c address #1010cid0
         bPtr = rPktBuffer
         Return
 
Last edited:

fernando_g

Senior Member
#7
And if you write more than a byte at a time, you may cross a block boundary, which on some devices may wrap around to the beginning of the block rather than crossing over to the next one.
Ok...........so this is what is meant in the Microchip datasheet:
If a Page Write command attempts to write across a physical page boundary, the result is that the data wraps around to the beginning of the current page (overwriting data previously stored there), instead of being written to the next page, as might be expected.
 

fernando_g

Senior Member
#8
Hi,

You may have a good reason for organising the individual data bytes in separate groups, but bear in mind that the EEPROMs are basically organised in "pages"......................

Cheers, Alan.
I came across that way of making memory partitions because when I had previously sent data sequentially, I would cross pages (without me realizing it) and as stated in the Microchip datasheet, it would overwrite the first records within that page, and I didn't know what was going on!
Now all makes a lot of sense.
 

fernando_g

Senior Member
#9
By chance, my current project uses 5-byte records, also stored in a (actually, multiple) 24LC256 EEPROM/s. 12x5-byte records are stored in each 64-byte page......................
So those 12X5 records mean 60 variables? How do you manipulate them within the Picaxe, if there are only 28 available variables?
 

lbenson

Senior Member
#11
And I think IP is only using one record at a time (5 bytes), so his hi2cIn command is the reverse of the hi2cOut command he showed above.
 
#12
So those 12X5 records mean 60 variables? How do you manipulate them within the Picaxe, if there are only 28 available variables?
12 records of 5 bytes. The PICAXE addresses and processes one record or 5 bytes at a time. It never actually places the data into registers b0 to b55 (this is a 28X2) but puts them in user RAM at location rPktBuffer (RAM locations 100-104 in this case).

And I think IP is only using one record at a time (5 bytes), so his hi2cIn command is the reverse of the hi2cOut command he showed above.
Correct. In this case, the 5-byte data packets are created in a computer (MS Excel) and downloaded via an AXE027 to the PICAXE 28X2's hSerial port.

Each data packet coming from the computer is actually 16-bytes long as Hex-text in the format "<aaaaDDddDDddDD>" (Eg "<000C32B0AA2709>), where aaaa is a 16-bit hex record ID or address ($000C) and DD or dd are the 5 bytes of data to be stored in the EEPROMs. The "aaaa" address ID goes into the word wPktNum and the 5 data bytes are written to user RAM at location rPktBuffer.

The location for storing the data in the EEPROM is calculated from wPktNum using the subroutine repeated below. I have now added the full related code that stores the data in the external EEPROM.

Recovering the data from the EEPROM is a similar process to the StoreRecord routine: The record number (word wPktNum) is known from other code not included here, so the record's 5 bytes can be fetched from external EEPROM into user RAM again.
Rich (BB code):
' **** GetRecPtrs: Calculate Chip ID (i2c address) and record pointer in EEPROM  ****
'
' Entry: wPktNum        Packet number 0 to 65535
'  Used: bRecordPtr     Record within a page 0 - 11
'        wPage          Page Ptr (512 pages per chip, up to 8 chips)
'        bChipID        Chip selector 0-7
'  Exit: bChipID        Initialised to i2c address of required (1 of 8) chip
'        wEEPROMPtr     Absolute address, within a chip
'        bPtr           Points to RAM Data buffer
'
GetRecPtrs:
         wPage = wPktNum / 12          'Global Page ID at this point - changes below
         bChipID = wPage / 512         'Chip number 0 to 7
         SerTxd ("Pkt=,", #wPktNum, ", GlblPage=,", #wPage, ", Chip=,", #bChipID)
         wPage = wPage // 512          'Page ID within a chip - 0 to 511
         bRecordPtr = wPktNum // 12 * 5
         wEEPROMPtr = wPage * 64 + bRecordPtr
         SerTxd (", LclPage=,", #wPage, ", RecPtr=,", #bRecordPtr, ", Addr=,", #wEEPROMPtr, CR, LF)
         bChipID = bChipID * 2 Or i2cAddr24LC256   'Is now the x2 shifted i2c address #1010cid0
         bPtr = rPktBuffer
         Return
Rich (BB code):
StoreRecord:GoSub GetRecPtrs          'bChipID, wEEPROMPtr, bPtr initialised from wPktNum
            'Now, store the data (never crosses 64-byte boundary)
            hi2cOut [bChipID], wEEPROMPtr, (@bPtrInc, @bPtrInc, @bPtrInc, @bPtrInc, @bPtrInc)
            Pause cWritei2cTime
            Return
 
Last edited:
#13
I have said it earlier, but I'm going to repeat myself. This is the best online forum.

With the prompt and accurate replies from the fellow forum dwellers, I was able -within hours- to completely solve a problem which had been nagging me for over two weeks.

Thanks, everyone
 

johndk

Senior Member
#14
Five byte records to a 256K eeprom seems to be a popular format. As it happens, I'm also playing with 5 byte records. So this discussion is very helpful. I picked up a couple of pointers that I will incorporate into my code. For one thing, I thought the blocks were 64 bytes each, which was a bit difficult to implement. Having realized the much larger blocks makes things infinitely easier.

BTW: I believe there is a problem in the simulator for the eeprom. I'm not getting the same results from the simulator as I am from the eeprom. But the discrepancies are somewhat spotty, so it could be my addled code. I'll report if the problem persists when I implement the larger block size.

One final question RE eeprom read/write. Where are the pauses needed? And at what duration?
 

hippy

Technical Support
Staff member
#15
One final question RE eeprom read/write. Where are the pauses needed? And at what duration?
For PICAXE internal EEPROM; no pauses are needed, the PICAXE firmware adds any pause required automatically.

For external EEPROM; a PAUSE should be added after every write command (HI2COUT/I2CWRITE/WRITEI2C). The pause time required depends on the actual chip, size and manufacturer, and should be in the manufacturer's datasheet. This will normally be in the region of 5ms to 10ms so a defacto "PAUSE 10" at default operating speed will often be used to cover most worst cases.
 

johndk

Senior Member
#16
For PICAXE internal EEPROM; no pauses are needed, the PICAXE firmware adds any pause required automatically.

For external EEPROM; a PAUSE should be added after every write command (HI2COUT/I2CWRITE/WRITEI2C). The pause time required depends on the actual chip, size and manufacturer, and should be in the manufacturer's datasheet. This will normally be in the region of 5ms to 10ms so a defacto "PAUSE 10" at default operating speed will often be used to cover most worst cases.
Thank you. I found that in the specs for my chip under the heading "Write cycle time" which is indeed spec'd at 5ms. I had overlooked that in previous perusals.
 
#17
One final question RE eeprom read/write. Where are the pauses needed? And at what duration?
This is related to the whole page refresh cycle. Read the explanation on page 9 of Microchip's datasheet the left hand sided note box.
The actual value is specified on the table of page 4, parameter #17.
 
#18
....For one thing, I thought the blocks were 64 bytes each, which was a bit difficult to implement. Having realized the much larger blocks makes things infinitely easier.
It's probably just a mix up of terminology: the word "Block" is used only once in the 24LC256 data sheet, referring to a "Block Diagram" on data-sheet page 1.

In the 24LC256, the page size is 64-bytes. I'm not sure what you mean by "...much larger blocks..."

If:
  1. More than 64 bytes are sent to the chip for writing or
  2. A sequence of several bytes are sent for storing such that the sequence crosses a 64-byte (6-bit) logical boundary,
then the write address pointer will wrap back to the start of the same 64-byte page.
 

johndk

Senior Member
#19
In post #2 hippy seems to imply that there are 5 blocks that can be accessed in single operations. So I guess I'm now somewhat confused by what he was talking about. I'm actually still using the 64 byte pages and it's working nicely. But I would like to understand what hippy was talking about.

If I understand what Pete is saying, anytime I cross a 64 byte boundary, I have to issue a new write with a new address pointer. Does the same apply to reads?
 
#21
In post #2 hippy seems to imply that there are 5 blocks that can be accessed in single operations. So I guess I'm now somewhat confused by what he was talking about.
Post 1 - Fernando asked/stated:
-Which means it has 32696 individual addresses
-Which means that if I want to store 5 byte variables, I should partition the memory in blocks of 6540 individual adresses..
&#8221;

Post 2 - Hippy responded:
That's 32768 addresses ( 0 through 32767 ) and partitioned into 5 blocks that's 6553 bytes per block.
That is not about the configuration of the memory within the EEPROM chip. It was just that Fernando wanted to save data received in 5 byte variables in separate regions (maybe like a datalogger?).
As others have indicated, if writing more than 1 byte at a time there are page boundaries ( page = 64 bytes) that must be respected to prevent wrap-around and accidental overwriting.
 
#22
Post 1 - Fernando asked/stated:
&#8221;

It was just that Fernando wanted to save data received in 5 byte variables in separate regions (maybe like a datalogger?).
.
Correct. I copied the command structure from the program generated by the AXE110 datalogger wizard. The wizard has a maximum of 4 variables, though.
 
#23
When using @bptr there are far more than 28 addresses available in the RAM.
What is the best way to utilise the ram so that it can be easily used as extra variables?
Assigning Symbols with a mnemonic variable name and memory address so bptr and @bptr can be readily identified seems to work
eg: Symbol extravar = 128
bptr = extravar : sertxd(@bptr)
but not with multiple variables so perhaps there is a better way?
 

techElder

Well-known member
#24
You might try looking at this code from this forum's code snippet repository. I've used it extensively in a few programs where I needed a better way to handle data several levels deep into GOSUBS.

PUSH/POP macros
 
#25
Thanks for the rapid response and helpful suggestion which I will try out.
I am using Macaxepad and have not yet tried it with macros as it seems to only support a limited set of preprocessor directives.
 

AllyCat

Senior Member
#26
Hi,

@bptr is particularly useful for "lists" of items; for individual values POKE and PEEK are sometimes just as useful and can be more readable. For example:

Code:
symbol HOURS = 100   ; ADDRESS of RAM location
symbol MINUTES = 101
symbol SECONDS = 102
...   
poke HOURS , inputvalue1
poke MINUTES , inputvalue2
poke SECONDS , inputvalue3
However, an advantage of @BPTR is that it can be included directly in mathematical expressions, so you might use something like this, later in the program:
Code:
bptr = HOURS
Number_Of_Seconds_Today = @bptrinc * 60 + @bptrinc * 60 + @bptrinc
Cheers, Alan.
 

Aries

New Member
#27
Hi,
Code:
bptr = HOURS
Number_Of_Seconds_Today = @bptrinc * 60 + @bptrinc * 60 + @bptrinc
... although this will overflow (become greater than 65535, which is the largest number that can be held in a 16-bit word) unless you keep to a 12-hour clock, in which case it represents "seconds since midnight" or "seconds since midday".
 
#28
Hi,

@bptr is particularly useful for "lists" of items; for individual values POKE and PEEK are sometimes just as useful and can be more readable. For example:

Code:
symbol HOURS = 100   ; ADDRESS of RAM location
symbol MINUTES = 101
symbol SECONDS = 102
...  
poke HOURS , inputvalue1
poke MINUTES , inputvalue2
poke SECONDS , inputvalue3
However, an advantage of @BPTR is that it can be included directly in mathematical expressions, so you might use something like this, later in the program:
Code:
bptr = HOURS
Number_Of_Seconds_Today = @bptrinc * 60 + @bptrinc * 60 + @bptrinc
Cheers, Alan.
Hi and thanks for the helpful advice. I had noticed that there are situations when peek and poke also work well depending on how the value will be used subsequently. However I have not yet tested if there is an impact on compiled program size.
 
#29
... although this will overflow (become greater than 65535, which is the largest number that can be held in a 16-bit word) unless you keep to a 12-hour clock, in which case it represents "seconds since midnight" or "seconds since midday".
Worth remembering and picaxe arithmetic has proved a challenge with overflows being one of the pitfalls.
 
Top