G.D. Sever's class has served me well for months, but I finally ran into a bug, so I thought I'd document it here.
The 24LCxxx EEPROMs have page write buffers (PWB). The PWB size varies from model to model and is not necessarily the same for all models with the same capacity:
24LC16B: 16 bytes
24LC32A: 32 bytes
24LC64: 32 bytes
24LC128: 64 bytes
24LC256: 64 bytes
24LC512: 128 bytes
24LC515: 64 bytes
Here's what the data sheet says about pages:
Page write operations are limited to writing bytes within a single physical page, regardless of the number of bytes actually being written. Physical page boundaries start at addresses that are integer multiples of the page buffer size (or ‘page size’) and end at addresses that are integer multiples of [page size – 1]. 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. It is, therefore, necessary for the application software to prevent page write operations that would attempt to cross a page boundary.
It seems as though there are two situations where you can run into trouble when writing to EEPROM with the _24LC01 class:
- Writing any value longer than the page size.
- Writing any value (even short ones) across page boundaries.
This is a quick and dirty fix, but I thought I better post something if I was going to point out the problem. I've tested this by writing a long string value (several pages long) to a starting address that's not a page boundary. I'm not sure how much this can be cleaned up without modifying the underlying I2CBus case so that it allows writing of specific data out of buffers (i.e. by start index and length) instead of assuming that the entire buffer should be sent. Any clean up suggestions will be welcome.
public int WriteBytes(UInt16 writeAddress, byte[] data)
{
if (writeAddress + data.Length - 1 <= MaxSize)
{
int result = 0;
int offset = 0;
int availableInPage = PageWriteBufferSize - (writeAddress % PageWriteBufferSize);
while (offset < data.Length)
{
int bytesRemaining = data.Length - offset;
int bytesToWrite = bytesRemaining > availableInPage ? availableInPage : bytesRemaining;
byte[] pageData = new byte[bytesToWrite];
for (int i = 0; i < bytesToWrite; i++) pageData[i] = data[i+offset];
// Grab the low byte.
byte addrLow = (byte)((writeAddress + offset) & 0xFF);
if (MaxSize > 0xFF)
{
// double byte address.
byte addrHigh = (byte)(((writeAddress + offset) >> 8) & 0xFF);
result += Write(new byte[] { addrHigh, addrLow }, pageData);
}
else
{
// single byte address
result += Write(new byte[] { addrLow }, pageData);
}
offset += bytesToWrite;
availableInPage = PageWriteBufferSize; // After start page, all of page is available.
}
return result;
}
else
{
throw new Exception("Address too high and/or value too long.");
}
}
It depends on the following... A cleaned up version would save the PWB size instead of switching for it every time.
public ushort PageWriteBufferSize
{
get
{
// Note: PWB size is NOT a function of capacity.
// E.g. 24LC512 and 24LC515 are both 512 kbit, but have PWB sizes of 64 and 128 bits, respectively.
switch (ic)
{
case IC._24LC16B: return 16;
case IC._24LC32A: return 32;
case IC._24LC64: return 32;
case IC._24LC128: return 64;
case IC._24LC256: return 64;
case IC._24LC512: return 128;
}
throw new Exception();
}
}