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.