EEPROM Writing Woes
Duane Degn
Posts: 10,588
In an attempt to free up some hub RAM in my hexapod project. I'm trying to move PASM sections of objects into the EEPROM.
I used the ReadEEPROM and WriteEEPROM methods from the Eddie firmware but when I read back the data just written to the EEPROM, I get a lot of errors.
I switched from the PASM code to just some test data and found success or failure was heavily dependent on the data which was written.
Here's my test program.
The output from "testData1" and "testData2" always matches but I consistently get 34 errors when comparing "testData0" with what is read back from EEPROM. Here's the output for "testData0".
I'm pretty sure there are other values besides zero which will cause this error, but since the error occurs so consistently with zero data values, I'm inclined to think the EEPROM requires more power to write a zero bit than to write a one bit.
I've seen similar issues when using SRAM and found certain data value would cause an error while other data values didn't cause trouble. Based on my previous experience, I think the decoupling cap on the EEPROM might be inadequate but I'm not sure. The boards program correctly so obviously the EEPROM can be written to.
I may try actively driving the clock line instead of relying on the pull-up resistor.
I've tried this code on two different QuickStart boards with the same results.
I've commented out the delays I've tried. The delays didn't help.
Do any of you see anything wrong with the I2C methods?
I used the ReadEEPROM and WriteEEPROM methods from the Eddie firmware but when I read back the data just written to the EEPROM, I get a lot of errors.
I switched from the PASM code to just some test data and found success or failure was heavily dependent on the data which was written.
Here's my test program.
DAT programName byte "LoadToEeprom_150205e", 0
CON
_clkmode = xtal1 + pll16x
_xinfreq = 5000000
CLK_FREQ = ((_clkmode - xtal1) >> 6) * _xinfreq
MICROSECOND = CLK_FREQ / 1_000_000
MILLISECOND = CLK_FREQ / 1_000
I2C_CLOCK = 28
I2C_DATA = 29
DEBUG_BAUD = 115_200
CON '' EEPROM Constants
F32_EEPROM_START = $8000
SERVO_EEPROM_START = $876C
SERIAL_EEPROM_START = $8964
EMPTY_EEPROM_START = SERIAL_EEPROM_START
CON
'' Parallax Serial Terminal
'' Control Character Constants
CS = 16 ''CS: Clear Screen
CE = 11 ''CE: Clear to End of line
CB = 12 ''CB: Clear lines Below
HM = 1 ''HM: HoMe cursor
PC = 2 ''PC: Position Cursor in x,y
PX = 14 ''PX: Position cursor in X
PY = 15 ''PY: Position cursor in Y
NL = 13 ''NL: New Line
LF = 10 ''LF: Line Feed
ML = 3 ''ML: Move cursor Left
MR = 4 ''MR: Move cursor Right
MU = 5 ''MU: Move cursor Up
MD = 6 ''MD: Move cursor Down
TB = 9 ''TB: TaB
BS = 8 ''BS: BackSpace
BP = 7 ''BP: BeeP speaker
OBJ
Com : "Parallax Serial Terminal"
PUB Main | first[3], last[3], size[3], eeprom[3]
Com.Start(DEBUG_BAUD)
repeat
NewLine
Com.Str(string("Press any key to begin."))
NewLine
Com.Str(string("Warning, this program will write to the upper EEPROM."))
waitcnt(clkfreq / 4 + cnt)
result := Com.RxCount
until result
Com.Char(CS)
Com.Char(HM)
waitcnt(clkfreq / 20 + cnt)
NewLine
Com.Str(string("WriteEEPROM($"))
Com.Hex(@testData0, 8)
Com.Str(string(", $"))
Com.Hex(@testData0 + 511, 8)
Com.Str(string(", $"))
Com.Hex($9000, 8)
WriteEEPROM(@testData0, @testData0 + 511, $9000)
NewLine
Com.Str(string("WriteEEPROM($"))
Com.Hex(@testData1, 8)
Com.Str(string(", $"))
Com.Hex(@testData1 + 511, 8)
Com.Str(string(", $"))
Com.Hex($9200, 8)
WriteEEPROM(@testData1, @testData1 + 511, $9200)
NewLine
Com.Str(string("WriteEEPROM($"))
Com.Hex(@testData2, 8)
Com.Str(string(", $"))
Com.Hex(@testData2 + 511, 8)
Com.Str(string(", $"))
Com.Hex($9400, 8)
WriteEEPROM(@testData2, @testData2 + 511, $9400)
NewLine
NewLine
Com.Str(string("Press any key to continue."))
repeat
waitcnt(clkfreq / 4 + cnt)
result := Com.RxCount
while result == -1
CompareLongs(@testData0, $9000, 128)
CompareLongs(@testData1, $9200, 128)
CompareLongs(@testData2, $9400, 128)
Com.Str(string("End of program."))
repeat
PUB CompareLongs(localPtr, eepromPtr, localSize) | localLong, rowCount
rowCount := 0
NewLine
Com.Str(string("Comparing RAM with EEPROM."))
NewLine
Com.Str(string("RAM address = $"))
Com.Hex(localPtr, 8)
NewLine
Com.Str(string("EEPROM address = $"))
Com.Hex(eepromPtr, 8)
NewLine
Com.Str(string("Longs to compare = $"))
Com.Hex(localSize, 8)
NewLine
repeat localSize
ifnot rowCount++ // 4
NewLine
Com.Str(string("<$"))
Com.Hex(localPtr, 4)
Com.Str(string("&$"))
Com.Hex(eepromPtr, 4)
Com.Str(string(">"))
ReadEEPROM(@localLong, @localLong + 3, eepromPtr)
Com.Str(string("|$"))
Com.Hex(long[localPtr], 8)
if long[localPtr] == localLong
Com.Str(string("==$"))
else
Com.Str(string(7, "<>$"))
result++
Com.Hex(localLong, 8)
localPtr += 4
eepromPtr += 4
NewLine
Com.Str(string("The compare method found "))
Com.Dec(result)
Com.Str(string(" unmatched longs."))
NewLine
if result
Com.Str(string(7, " Failure!!! ", 7))
else
Com.Str(string(" Success!!! "))
PRI NewLine '' Called from DebugCog
'' Clear the end of the current line and then
'' start a new line.
Com.Char(CE)
Com.Char(13)
result := 1
PUB FindString(firstStr, stringIndex) '' Called from DebugCog
'' Finds start address of one string in a list
'' of string. "firstStr" is the address of
'' string #0 in the list. "stringIndex"
'' indicates which of the strings in the list
'' the method is to find.
'' Version from HexapodRemote140128a
'' Version Hexapod140129a, removed commented
'' out code.
result := firstStr
repeat while stringIndex
repeat while byte[result++]
stringIndex--
CON
SDA = I2C_DATA
SCL = I2C_CLOCK
' EEPROM constants
I2C_ACK = 0
I2C_NACK = 1
DEVICE_CODE = %0110 << 4
PAGE_SIZE = 128
PRI ReadEEPROM(startAddr, endAddr, eeStart) | addr
''Copy from EEPROM beginning at eeStart address to startAddr..endAddr in main RAM.
SetAddr(eeStart) ' Set EEPROM's address pointer
i2cstart
SendByte(%10100001) ' EEPROM I2C address + read operation
if startAddr == endAddr
addr := startAddr
else
repeat addr from startAddr to endAddr - 1 ' Main RAM index startAddr to endAddr
byte[addr] := GetByte ' GetByte byte from EEPROM & copy to RAM
SendAck(I2C_ACK) ' Acknowledge byte received
byte[addr] := GetByte ' GetByte byte from EEPROM & copy to RAM
SendAck(I2C_NACK)
i2cstop ' Stop sequential read
PRI WriteEEPROM(startAddr, endAddr, eeStart) | addr, page, eeAddr
''Copy startAddr..endAddr from main RAM to EEPROM beginning at eeStart address.
addr := startAddr ' Initialize main RAM index
eeAddr := eeStart ' Initialize EEPROM index
repeat
page := addr +PAGE_SIZE -eeAddr // PAGE_SIZE <# endaddr +1 ' Find next EEPROM page boundary
SetAddr(eeAddr) ' Give EEPROM starting address
repeat ' Bytes -> EEPROM until page boundary
SendByte(byte[addr++])
until addr == page
i2cstop ' From 24LC256's page buffer -> EEPROM
eeaddr := addr - startAddr + eeStart ' Next EEPROM starting address
until addr > endAddr ' Quit when RAM index > end address
PRI SetAddr(addr) : ackbit
'Sets EEPROM internal address pointer.
' Poll until acknowledge. This is especially important if the 24LC256 is copying from buffer to EEPROM.
ackbit~~ ' Make acknowledge 1
repeat ' Send/check acknowledge loop
i2cstart ' Send I2C start condition
ackbit := SendByte(%10100000) ' Write command with EEPROM's address
while ackbit ' Repeat while acknowledge is not 0
SendByte(addr >> 8) ' Send address high byte
SendByte(addr) ' Send address low byte
PRI I2cStart
' I2C start condition. SDA transitions from high to low while the clock is high.
' SCL does not have the pullup resistor called for in the I2C protocol, so it has to be
' set high. (It can't just be set to inSendByte because the resistor won't pull it up.)
dira[SCL]~ ' SCL pin outSendByte-high
dira[SDA]~ ' Let pulled up SDA pin go high
dira[SDA]~~ ' SDA -> outSendByte for SendByte method
PRI I2cStop
' Send I2C stop condition. SCL must be high as SDA transitions from low to high.
' See note in i2cStart about SCL line.
dira[SDA]~~
dira[SCL]~ ' SCL -> high
dira[SDA]~ ' SDA -> inSendByte GetBytes pulled up
PRI SendAck(ackbit)
' Transmit an acknowledgment bit (ackbit).
dira[SDA] := !ackbit ' Set SDA output state to ackbit
dira[SDA]~~ ' Make sure SDA is an output
dira[SCL]~ ' Send a pulse on SCL
dira[SCL]~~
dira[SDA]~ ' Let go of SDA
PRI GetAck : ackbit
' GetByte and return acknowledge bit transmitted by EEPROM after it receives a byte.
' 0 = I2C_ACK, 1 = I2C_NACK.
dira[SDA]~ ' SDA -> SendByte so 24LC256 controls
dira[SCL]~ ' Start a pulse on SCL
ackbit := ina[SDA] ' GetByte the SDA state from 24LC256
dira[SCL]~~ ' Finish SCL pulse
dira[SDA]~~ ' SDA -> outSendByte, master controls
PRI SendByte(b) : ackbit | i
' Shift a byte to EEPROM, MSB first. Return if EEPROM acknowledged. Returns
' acknowledge bit. 0 = I2C_ACK, 1 = I2C_NACK.
'waitcnt(800000 + cnt)
b ><= 8 ' Reverse bits for shifting MSB right
dira[SCL]~~ ' SCL low, SDA can change
repeat 8 ' 8 reps sends 8 bits
'waitcnt(80000 + cnt)
dira[SDA] := !b ' Lowest bit sets state of SDA
'waitcnt(80000 + cnt)
dira[SCL]~ ' Pulse the SCL line
'waitcnt(80000 + cnt)
dira[SCL]~~
b >>= 1 ' Shift b right for next bit
ackbit := GetAck ' Call GetByteAck and return EEPROM's Ack
PRI GetByte : value
' Shift in a byte, MSB first.
'waitcnt(800000 + cnt)
value~ ' Clear value
dira[SDA]~ ' SDA input so 24LC256 can control
repeat 8 ' Repeat shift in eight times
'waitcnt(80000 + cnt)
dira[SCL]~ ' Start an SCL pulse
value <-= 1 ' Shift the value left
'waitcnt(80000 + cnt)
value |= ina[SDA] ' Add the next most significant bit
dira[SCL]~~ ' Finish the SCL pulse
DAT
testData0 long 0[128]
testData1 long -1[128]
testData2 long $AA55A55A[128]
The output from "testData1" and "testData2" always matches but I consistently get 34 errors when comparing "testData0" with what is read back from EEPROM. Here's the output for "testData0".
Comparing RAM with EEPROM. RAM address = $00000064 EEPROM address = $00009000 Longs to compare = $00000080 <$0064&$9000>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$0074&$9010>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$0084&$9020>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$0094&$9030>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$00A4&$9040>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$00B4&$9050>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$00C4&$9060>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$00D4&$9070>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$00E4&$9080>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$00F4&$9090>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$0104&$90A0>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$0114&$90B0>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$0124&$90C0>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$0134&$90D0>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$0144&$90E0>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$0154&$90F0>|$00000000==$00000000|$00000000<>$FFFFFF03|$00000000==$00000000|$00000000<>$FFFFFF03 <$0164&$9100>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$0174&$9110>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$0184&$9120>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$0194&$9130>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$01A4&$9140>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$01B4&$9150>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$01C4&$9160>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$01D4&$9170>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$01E4&$9180>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$01F4&$9190>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$0204&$91A0>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$0214&$91B0>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$0224&$91C0>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$0234&$91D0>|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000|$00000000==$00000000 <$0244&$91E0>|$00000000==$00000000|$00000000<>$FFFFFFFF|$00000000==$00000000|$00000000==$00000000 <$0254&$91F0>|$00000000==$00000000|$00000000==$00000000|$00000000<>$FFFFFFFF|$00000000==$00000000 The compare method found 34 unmatched longs. Failure!!!
I'm pretty sure there are other values besides zero which will cause this error, but since the error occurs so consistently with zero data values, I'm inclined to think the EEPROM requires more power to write a zero bit than to write a one bit.
I've seen similar issues when using SRAM and found certain data value would cause an error while other data values didn't cause trouble. Based on my previous experience, I think the decoupling cap on the EEPROM might be inadequate but I'm not sure. The boards program correctly so obviously the EEPROM can be written to.
I may try actively driving the clock line instead of relying on the pull-up resistor.
I've tried this code on two different QuickStart boards with the same results.
I've commented out the delays I've tried. The delays didn't help.
Do any of you see anything wrong with the I2C methods?
PRI SendByte(b) : ackbit | i
' Shift a byte to EEPROM, MSB first. Return if EEPROM acknowledged. Returns
' acknowledge bit. 0 = I2C_ACK, 1 = I2C_NACK.
'waitcnt(800000 + cnt)
b ><= 8 ' Reverse bits for shifting MSB right
dira[SCL]~~ ' SCL low, SDA can change
repeat 8 ' 8 reps sends 8 bits
'waitcnt(80000 + cnt)
dira[SDA] := !b ' Lowest bit sets state of SDA
'waitcnt(80000 + cnt)
dira[SCL]~ ' Pulse the SCL line
'waitcnt(80000 + cnt)
dira[SCL]~~
b >>= 1 ' Shift b right for next bit
ackbit := GetAck ' Call GetByteAck and return EEPROM's Ack
PRI GetByte : value
' Shift in a byte, MSB first.
'waitcnt(800000 + cnt)
value~ ' Clear value
dira[SDA]~ ' SDA input so 24LC256 can control
repeat 8 ' Repeat shift in eight times
'waitcnt(80000 + cnt)
dira[SCL]~ ' Start an SCL pulse
value <-= 1 ' Shift the value left
'waitcnt(80000 + cnt)
value |= ina[SDA] ' Add the next most significant bit
dira[SCL]~~ ' Finish the SCL pulse

Comments
It's writing just fine; my EEPROM reader reads all 00's at $9000, FF's at $9200, and $5A A5s at $9400. That's using a Spin-based I2C object with 10K pullups on SCL and SDA, as well as other I2C devices on the bus.
It looks like there's a problem with the reader; I'm not quite sure what's going on, but here's the I2C traffic for the first dozen bytes of the read.
https://www.dropbox.com/s/a90uax1ef8ly4bo/I2C%20traffic.jpg?dl=0
Also the EEPROM reader object I used: https://www.dropbox.com/s/hkzihvu9bwyr36d/EEPROM%20Duane%20test%20-%20Archive.zip?dl=0
@Tracy, the SetAddr method does poll for an ack.
That's good to know.
I'll take a look at your reader to see if it behaves better than the one I'm using.
You can use my Hardware Explorer to confirm your EEPROM and hardware is operating correctly although my I2C routines drive the SCL line high and low rather than just open-drain as the latter method assumes that something else will pull the SCL line as well, which is for all practical purposes never the case. For your code which open-drains the SCL line the 10K pullup is too light given line length and capacitance etc. This 10K value is only good for a device or two in close proximity to the EEPROM and there is no reason whatsoever to not have a much lower value of 2K2 for both.
I had previously used "Propeller Eeprom" but switched to the Eddie code since I had been working with the Eddie firmware a lot lately. I hadn't tested the EEPROM code on the Eddie firmware so it's good to know it has a problem. I'll make sure and fix the EEPROM code with the next update to the Eddie firmware.
I was surprised to learn one of my QuickStart boards didn't have a pull-up on the clock. That's a handy check Chris.
Thanks for everyone's help.
I still don't know what was wrong with the original code but I have a working solution so I'm not very concerned about finding the error. This issue has already cost me more time than I'd like so I don't think I'll hunt down the exact problem.
-Phil
* Okay, I know the Spinneret does, and the Prop Backpack does/did/will. 'Not sure it's the usual case, though.
The funny thing is that there are some drivers that try to adhere to the standard as they understand it but in trying to implement this accommodation for clock stretching and also multi-master arbitration that it is also incomplete in that this needs to be supported at the bit by bit level, which is easy in hardware, but a pain in software. However, for all practical purposes requesting clock stretching is not implemented in I2C chips nor is multi-master, but these had use in the early days of I2C with slow CPUs etc.Then again it really complicates things by introducing state machine levels of exceptions etc, you don't want to go there, or need to really.
My best advice to anyone is use a pullup for SCL if you want, it won't hurt, but just drive the clock line normally as you won't have any problems and it will be able to compensate for bus loading and capacitance, even at high speeds.
I don't have all the Propeller boards Parallax has made but I think it's safe to say most of their boards have pull-ups on both lines.
The only exceptions I'm aware of are the Propeller Demo Board and the Propeller Professional Development Board.
Most of the 3rd party boards I'm aware of have the second pull-up.
BTW, my hexapod now has additional free RAM. I was successful in moving the PASM sections of the servo32 object and F32 to the EEPROM. The buffer used to temporarily hold the PASM code is reused as stack space for cogs using Spin. Pretty cool IMO.
Just checked the Schematics of the Project Board USB and Activity Board (both rev A) and they both have 10K pullups to 3.3V on SDA and SCL. The project board uses two SMD resistors immediately to the right of the EEPROM and the Activity Board uses a very tiny 10K four pack just above the EEPROM.
I have however discovered the problem in your EEPROM reader; it's not so much a question of why the 00's were sometimes misreading as why the others were reading correctly. With that red line, the object can only send ACKs, the NAK is never being sent at the end of each read of four bytes. The EEPROM therefore interprets the following start, write-address, restart, and read-address all as being requests for more bytes.
Very good. Thanks for finding that.
BTW, your detection didn't glitch; the pull-up on the clock line wasn't soldered properly on my QuickStart. I don't know if it was a defect from Parallax or if I did something to damage the board. I'm glad to know the board has a missing pull-up, I had been using it to test an I2C device. I'm sure your detection code saved me a bunch of time.