Post navigation

Driver for Microchip 24xx Serial EEPROM

For many projects I need to store configuration or sensor data. For this I’m using either an SD card or program the internal flash memory of the microcontroller. Using the internal flash is a good thing as it does not need an external component. However, the typical number of programming cycles is limited to 10k-50k which is a limiting factor if data has to be recorded over a long time or very often. That’s why I’m using the very popular external 24xx external EEPROM devices from Microchip.

24LC512 connected to FRDM-KL25Z

24xx Serial EEPROM

The EEPROM is using an I²C interface, and devices are available from 128 bit to 1 MBit. Typically they have an erase/write cycle of 1’000’000, much more than typical flash. The devices are ‘hobby friendly’ as they are available in DIP package.

24LC512 on a bread board

Processor Expert 24AA_EEPROM Component

The 24AA_EEPROM Processor Expert component implements a driver for the 24xx series of serial I²C EEPROM devices.

Block buffer size: The driver supports cross page writes. For this an extra buffer size can be specified.

Acknowledge Polling: After writing a byte or a page, the driver supports optionally acknowledge polling (see device data sheet). To improve performance, the device specific Page Write Time can be specified.

Under Connection the I²C bus interface is specified, plus an interface to the optional Write Protection Pin.

The component features an optional command line/Shell interface.

Component Functions

The component offers a set of functions to read/write data:

24AA_EEPROM Methods

ReadByte()

With

byte EE241_ReadByte(EE241_Address addr, byte *data);

a byte is read from the device.

The memory address is device specific. E.g. the 24xx08 device encodes upper address bits in the I²C device address. And depending on the device, an 16bit address or different I²C device addresses needs to be used. This is implemented with conditional compilation and macro wrappers:

❗ Acknowledge polling is not implemented in an ideal way here. The problem is that the underlying Processor Expert I²C driver does not allow just to send the I²C address byte: it requires at least a data byte sent. As such, I’m sending a dummy 0xFF byte.

Below is how this looks on the bus with writing 0x8A to the address 0x0012:

Writing 0x8A at address 0x0012

With Acknowledge Polling enabled, the driver checks right after the write operation if the device responds with an ACK:

Summary

Using the 24xx external serial EEPROM gives storage room for many applications. And the Processor Expert driver makes usage of it very simple for me. All the sources and drivers are available on GitHub/SourceForge (see this post how to download and install the components).

Somewhat related…
On one of my reverse-engineering projects I made a wiretap for one of those serial EEPROMs in a automotive ECU. I used S08JS16 and connected to the serial memory chip. Luckily the target ECU’s micro talked to the EEPROM slowly enough I was able to monitor all the signals, decode, an spy on every read and write in real time.

Hi Erich,
excuse for the OT reply… but ever embedded component matter about it.
Have you try to enabled WatchDog on your embedded component “WAIT”?
On my small app, around an FRDM-KL25Z board and under CW Developer Studio 10.4, it cause an error at generation code of PE:
ERROR: This component is not implemented in this version!
Where i make a mistake?
TNX!

Understood. Keep in mind that I had added the watchdog to Wait to prevent watchdog timeout inside the Waitms routine. All what it does is calling the watchdog every milli-seconds there.
Are you using my sources on GitHub? I’m committing my change in a few minutes.

Sorry Erich,
my analysis is hurried… In the generated code of WAIT methods, the parameter of WDog_Clear function added at WAIT delay loop, is the pointer ‘WDog_DeviceData’ but is undeclared.
This causes an error.
If you read in the code generated of WatchDog_LDD driver, the typedef defined for this parameter (argument of function WDog_Clear) is named ‘WDog_TDeviceData’, may be the cause of error?

Ok Erich,
but the option to set yes must not only the ‘Enabled in init. code’, but also the ‘Auto initialization’ must be set to yes, otherwise the PE not define (in WDog.h file) the WDog_DeviceData token.
Well Erich, now (and previously) WDog run perfectly also on Kinesis.
TNX

Hi Eric,
Am trying to implement the eeprom Test in FreeRTOS. Have the lastest components installed. Fails and goes to PE_DEBUGHALT(); in GI2C1.c in the GI2C1_WriteBlock code. Am using I2C Channel 1 with TMOUT1 RTOS enabled, and GI2C1 RTOS enable. Any idea why this fails? Works well without FreeRTOS.

Hi John,
I believe you run into a hard fault in GI2C1_RequestBus() because GI2C1_busSem semaphore is NULL.
Is GI2C1_Init() called? There the semaphore will be initialized, and usually it gets called before main().

Eric,
I did add TMOUT1_AddTick() to the tick handler in Events.c, but it still failed. I must work on something else for a while, then dig into the hard fault interrupt. When the EEPROM test is by- passed, everything else works.

Hi John,
not sure what is causing this. For now, I suggest you do not use that Test() function ;-). I do not have the same EEPROM type available (I have now ordered two pieces). But I think it is not related to the EEPROM type. If you want (and can), you can send me your project to my email address mentioned on the About page of this blog, and I’ll try to have a look to see what the problem is. But from your description it could be a stack overflow or memory corruption problem. You might increase the stack size to see if the problem goes away?

John,
I have finally received my 24AA256 EEPROM from Farnell, and checked your application. I see that you have only 40 stack units (=40*4=160 bytes) available as minimal stack size. But the EE241_WriteBlock() is using a local buffer which easily could cause a stack overflow. I suggest to increase the FreeRTOS stack size to a higher value (at least 100 units).

I copied the files from the GitHub page, there was no compilation error but the FRDM-KL25Z not communicate with the EEPROM, could not identify the error that the function ‘EE241_Test ()’ returns. I am using 24LC08B, I changed ownership Device for ‘8 ‘and Page Write Time for ‘3’. I’ve changed the EEPROM. Can anyone help me?

Hi Bruno,
I suggest that need a logic analyzer. These kind of problems are hard or impossible to solve without it. It would be like if you do not have a debugger ;-).
If you have a spare FRDM board, then you can consider using it as a low cost logic analyzer (https://mcuoneclipse.com/2013/02/03/turning-the-freedom-board-into-a-logic-analyzer/). As for myself, spending Euro 100 for a Salae logic analyzer was probably the best engineering investment I ever made: it saved me problably hunderds of hours of bug finding time.
As for your problem: It seems that the write and read reports success, but the value read back is wrong. It should have written zero to the address 0x0000, but it seems that it reads back something different. Again, without a logic analyzer this is nearly impossible to debug.
As for the ‘no source available’: it seems that the debugger settings are missing a path to the source file where __eabi_idiv() is defined. But this is not a real problem. What you would need is a logic analyzer (or an oscilloscope).

I run test() OK.
when I enable “Shell” property, CW would add 3 Component(Shell/Utiliy/AS1), When config “Shell” component itself, there is a Utility property, but the dropdown menu is empty, and the details show “Unassigned interface” in red.
I just install CW10.5, is’t the new version’s problem?
Can anyone help me?

You mean it works in the ‘Processor Expert Driver Suite’? Note that the Driver Suite and CodeWarrior are using different folders/locations for the components. So it could be that you have loaded the Utility in the Driver suite, but not in CodeWarrior?

I copied example project using 24LC256,I have a question about Shell interface, i use termite can’t show status and read/write to the device ,but i have config Shell component and complier is ok ,Can anyone help me?

Hi Peter,
could you describe your problem a bit more? I suggest that you check if you are using the correct virtual COM port used. The other thing is: do not have the virtual COM port open if you unplug/replug the board: Close the COM port first, then unplug the board, then plug the board in, then open the COM port. Otherwise the COM port will be locked/open and you cannot communicate. Additionally the OpenSDA USB CDC will reset the USB traffic during Power-On Reset of the FRDM board.

Thank you for all your I2C-related tutorials. They have been very helpful!

1) Do you know if the KL46Z plus GI2C/I2C_LDD component combination is limited to only receiving eleven 8-bit words at a time?

2) Why is the GI2C component’s implementation limited to sending only”GI2Cx_WRITE_BUFFER_SIZE – 1″ words? Why does the GI2C component have problems when configuring the “Write Buffer Size” property to more than 16 byte-sized words?

I’ll explain myself:

I have a KL46Z project that uses the GI2C component with the underlying I2C_LDD component. So far I can send and receive short data blocks between the KL46Z and the I2C-enabled sensor I’m working with. However, if I try to receive more than 11 byte words I get a hard fault (I’m using your hard fault component for debugging). Could this have something to do with the ASIC design? I didn’t find anything explaining this limitations in the KL46Z’s reference manual.

Furthermore, GI2C’s implementation is limited to sending only “GI2Cx_WRITE_BUFFER_SIZE -1” bytes by the following code (line 271 in the GI2Cx.c source file):

Hi Carlos,
first, the component only supports 8bit units on the bus (no 11bit units, etc). But you should be able to send more data (as a block). So I assume a ‘word’ are 8bits for you?
And if you want to send larger blocks, then you need to increase the WRITE_BUFFER_SIZE.

I’ve been using your hard fault component for debugging the issue I posted above and I have a couple of questions.

The following values are loaded to the stacked registers when your hard fault handler is called:

stacked_pc is 0x400.
stacked_r3 is 0x0.
stacked_lr is 0x5f2d.

Using the disassembly view with address 0x400 gives me several instructions of the type “.short 0xffffffff”.
I believe that having 0x0 for stacked_r3 means that a NULL pointer was accessed.
The stacked linked register leads me to the following block from GI2C1.c:

if (GI2C1_UnselectSlave()!=ERR_OK) {
return ERR_FAILED;
}

So perhaps the processor was trying to execute the UnselectSlave() function?

Is this the proper interpretation for these stacked register values? If this is the case then I’m not too sure how trying to receive more than 11 bytes is a problem. Except maybe if my I2C-enabled sensor wasn’t able to send more than 11 bytes? I tried looking at the sensor with an oscilloscope but the presence of the oscilloscope probes messes up with the execution of the whole firmware.

Hi Carlos,
it looks the problem is at 0x5f2d. That r3 is zero does not mean that this is a problem. It would be only if e.g. the assembly code around 0x5f2d uses that register e.g. for a pointer access. The hardfault could be as well that at this place the function UnselectSlave() returns, but the stack is corrupted. Can you try to increase the stack size if this has an impact?

Hi Carlos,
if the presence of an oscilloscope is messing up your firmware, then this concerns me: either something is wrong with your probe, or your hardware really is that fragile that it will not stand a real environment? Maybe this is the source of your problem? Why is there a problem with your probe?

Thanks for all the replies. I did try increasing the stack size but that didn’t solve the problem. However, I think I may have found the actual cause of the problem. I will write back as soon as I confirm this. I think it makes sense for the hard fault to occur. This is why:

The I2C-enabled sensor I’m using came with some generic drivers. These drivers are abstracted from the target microcontroller (the KL46Z in my case). The abstraction required me to implement a macro so these drivers can use the Generic I2C component. I already spotted some pointer issues in my I2C wrappers that bridge the drivers and the Generic I2C component. Fixing these problems I spotted will let me know whether this is the sole cause of the hard fault.

As for the scope probe, I’m not sure. I haven’t seen impedance matching/circuit loading problems like these before. Not even when I have probed 125 MHz Ethernet controller chips. I’ll also post about this as soon as I figure this out.

Are you sure that interrupts are enabled when SendBlock() gets called? I suspect that interrupts are disabled, e.g. if you call any I2C functions before the RTOS is started?
So are you sure interrupts are enabled (and you call the I2C e.g. from a task context)?

I’m trying to use a 24AA1026 EEPROM in one of my project, and I’m working with a TWR K60 board. When using the EE241 component, I encounter these following three problems:

1. “EE241_WriteByte” function always get blocked here :
do { /* Wait until data is sent */
} while (!GI2C1_deviceData.dataTransmittedFlg);
which is in the lower component GI2C. To solve this problem temporarily, I enabled the Timeout of GI2C. So I can jump out of this routine, but I still have no idea where the problem is. (I noticed that it is the same problem with Moise?)

2. “GI2C1_ProbeACK” function always returns ERR_FAILED. That is to say my EEPROM never sends back or my micro never receives a AKG signal, right? Moreover, I find that this time, even if I enabled Timeout for EE241, it won’t break the ACK polling loop.

3. Both “EE241_WriteByte” and “EE241_WriteBlock” get stuck because of the loop of waiting for the interrupt flag. But, if I use “EE241_ReadByte” or “EE241_ReadBlock” to check the address that I’ve just written to, I find the data is there. So does that mean only the interrupt flag isn’t set correctly?

Note that both “EE241_ReadByte” and “EE241_ReadBlock” works well and I should have enabled the interrupt. I did two tests, one with FreeRTOS, another without.

Hi Kenan,
it is really hard to tell what is going on. Have you checked the bus activites with a logic analyzer. It sounds to me that either your I2C bus is not configured properly, or your have cable/connection problems (proper dimension of the I2C pull-ups? length of cables?). And you need to check with the debugger if your interrupts are enabled.
I hope this helps.

If the I2C driver does not work, the most likely reason is that interrupts are disabled. Are you 100% sure that you have interrupts enabled properly? I suggest to check this in the debugger (PRIMASK core register) and verify that the least significant bit is NOT set.

Hi Erich,
I am trying to use a different EEPROM for the same project part# ISSI24C16A, Should there be any modifications in the same as the Pin configrations all seems to be the same.
But when i try this on the EEPROM nothing is getting displayed on the Serial Terminal.
Please help

I have noted that there is a component called AT25HP512, but when I try to include it in a project it flags an error because it cannot find the EEPROM_SPI driver. I have found said driver on your GitHub, but I can’t seem to get it loaded into Processor Expert. Please advise.

Hi Marcela,
I think you meant 20 BYTES (not bits). WriteBlock() is using an internal buffer size. See the ‘block buffer size’ in . It seems that yo have that setting set to 24?
I hope this helps,
Erich

yes it seems to work but now i have troubles with the write block , i use for example: res = EE241_ReadBlock(0x5, data, sizeof(data)); // lee valor velocidad limite
latitud=data; but i need to read another adresses , then when i try to read two or more the latitud variable and the others variables have the same value , i know that it is for use the same temporal variable “data” but if i use another name with the same type the task ReadBlock do not works ok.

I have a problem with a S9KEAZN16AMLC automotive part and a 24LC32A EEPROM from microchip. I am starting out with the EEPROM driver from the PE components. The program gets stuck on a never ending loop which leads me to suspect that the I2C.datatransmittedflag is never set. I tried scoping the I2C lines out and found that the Controlbyte (0xA0) and the dummy byte (0xFF+ACK) seem to be working but it is the writing and reading that seems to be in error. The primask registers is read to be 0x00. I looked at the registers tab in the debug perspective. I was wondering how this issue was overcome. I am running the I2C peripheral at 100kHz clock and with 2ms page write time on the EEPROM.

It seems to me that you are using the I2C driver in interrupt mode, but your interrupts are disabled? Can you check with the debugger when you are inside the never ending loop checking for I2C.datatransmittedflag if your interrupts are turned on?

This is the status of the I2C registers upon stepping through the code until the part where the loop gets stuck waiting for the data transmitted flag (read MSB first)
I2C status register = 10100010
IICIF bit in the I2C status register is set already upon entering this part of the loop in the writeblock routine.
do
{
}while(!GI2C1_deviceData.dataTransmittedFlg);

Also, I looked at the primask register upon entering and exiting critical section. The interuupts are disabled and enabled upon enter and exit respectively. Is ther any kind of debug that I can do?
Thanks for the help. Much appreciated.

I have a scope connected and I see the 0xA0 along with the start and atop and the dummy 0xFF+ACK that you send. But the address and data don’t seem to be written and read. Would you like to take a look at the scope info and my project? Thanks. I can email it to you.

Hi Erich,
I was just checking why the device was not sending an ACK inspite of me sending the control byte and the like. Is there a way to test an EEPROM?
PIN7 SCL (from uC) connected to pin 6 of the Chip through a 1k pullup to 3.3Vdc.
PIN8 SDA (from uC) connected to pin 5 through 1k pullup to 3.3Vdc
A0,A1,A2 WP and Vss of the chip is grounded.
Vdd is fixed at 3.3Vdc and decoupled to ground using a 100nF.
Do I have to play with the pullups? And 3.3Vdc is within the max of 5.5Vdc from the datasheet.
Kind of running out of options!
Is there a way to erase the whole EEPROM and then reprogram it?
Also in order to check if the EEPROm s responding through hyperterminal/Termite I would like to know what connections need be made. Thanks!

Can you check if you are using the correct EEPROM/device type on your board, as different devices can have different I2C addresses.
And you don’t need to erase it first: it has to respond with the ACK signal if it responds to the I2C address you put on the bus.

Oh I just realised that I had acknowledgement polling enabled when I tried read/write and so I disabled acknowledgement poll on the PE component and the read/write happens as expected.
Thank you for your help.
I am using a custom board and I am programming using a Jlink. I would like to know if there is a way for the shell interface to communicate with my board. Or is it absolutely essential to have Open SDA in order for the shell interface and termite communication to take place?

Ah, that makes sense now. I did not realize that you were using polling mode.
As for the shell interface: all what you need is either a USB CDC, UART or Segger RTT connection. So you can do that with any board and you do not need OpenSDA.

Sure. Thanks.
I have a follow-up question though. I am trying to build a temperature logger and write temperature values on the EEPROM. I am using the CPU’s internal temperature sensor /ADC and reading the values that I need to write on FreeMaster. I was thinking if there is a way to write float values on the EEPROM and if so how to log it for extended periods of times. I have a variable avgCpuTemp (it varies from 29 to 32 degree C) and I am trying to write it inside the ADC ISR. And the ADC is hardware triggered. I don’t want the ADC to miss triggers or write incorrect data on my EEPROM. Is there a better way to do this?

Hi Erich, I have a question with regard to reading the data from the EEPROM on a terminal program. I would like to read it from the EEPROM without the use of a debugger like SEGGER/OpenSDA. I have a TTL to RS232 cable connected to my PC. I am able to send strings and read the data independent of the EEPROM i.e. UART communication between uC and PC is established. What I would like to know of is a way for accessing EEPROM data and read EEPROM through this connection. Thanks.

Hi Erich,
I am trying to write a structure that represents a log record of errors that looks like this.
struct
{
uint16–;
uint16–;
uint8;
uint32;
} logrecord;
byte* logpointer = (byte*)&logrecord;
i have a byte pointer to point to the whole record and copy to block.
What I have a problem with is writing multiple records on the EEEPROM 24LC32A. Is there a way I can write the whole record in one shot, keeping in mind the offsets in addresses because of writing multiple bytes of data. Any pointers would be greatly helpful- approach wise.

Yes, WriteBlock would work but when I scoped the data on the SDA and SCL lines – I see that the page size of 8 was an issue.
My routine goes something like this-
for(startaddress = 5; startaddress <= 0x0FFF; startaddress+sizeof(errLogEE_record))
{
res = EE241_WriteBlock(startaddress, errLogEE_ptr, sizeof(errLogEE_record) );
if(res == ERR_OK) //res never was ERR_OK
errLogEE_ptr+=sizeof(errLogEE_record);
}

Sorry about the bad indentation-

I tried increasing the page size in EE241.h to 10 from 8 and it seemed to capture all the bytes in the record but the next subsequent write proved to not be functional- which makes me believe that res != ERR_OK
I have 8 such records to be written at any point of time. Logic wise- Is there an error in my approach?
Thanks.

It seems like the code is getting stuck waiting for the i2c.transmitted flag. Does this have to do with fact that I changed the Page size in the header? I seem to have no issues writing a byte/ using your test code.

Hi Erich,
I did check the primask register and followed if the enable and disable interrupts are functional as the code enters and exits the critical section. They seem to be operational.
Further more I have condensed my structure to be 8 bytes such that it fits snugly into the page size boundary as defined in the header. But I have a question isn’t the page write 32 bytes before the data is saved and why are we restricting the hardware to do only 8 byte writes?? Any other debug tips? Thanks

On further analysis, i see that the first block of 8 bytes gets written but the second block of 8 bytes that I want written on the address gets written, the second block of 8 bytes that I want written in the same address results in the transmitted flag getting stuck in the do..while loop. Is there a reason why this is the case? Thanks.

These are pretty much the two blocks that I am trying to write but block 2 gets stuck on the transmitted flag error. Is my first argument – address wrong? Also, my block buffer size is 8 now. if that helps. Thanks.

Individual byte writes and reads work for me. It is the block writes that I am having an issue with. I am not sure if I am using the latest version of the project that you have. Let me check and get back you on that.