EEPROM Control and System Information Storage

In this post, we will explore how to read from and write to EEPROM in an STM32F4 project. For embedded systems, EEPROM is more than just a data container — it plays a crucial role in maintaining persistent system configurations. When power is turned off, the stored data remains intact, and during reboot, it allows automatic restoration of essential system settings.


Why Use EEPROM?

EEPROM is a type of non-volatile memory, which serves the following purposes:

  • Store configuration data that must persist even after power-off
  • Reload stored data during reboot for automatic initialization
  • Retain essential information such as version numbers, MAC addresses, or firmware metadata in networking devices

In short, EEPROM ensures system consistency and reliability by preserving critical data across power cycles.


Methods of EEPROM Access

EEPROM devices are typically accessed via I2C or SPI communication.

  • Small-capacity EEPROM → I2C
  • Larger Serial Flash → SPI

In this tutorial, we use the TW100PC module, which includes a 4KB AT24C32 EEPROM connected over I2C. Therefore, we will demonstrate EEPROM handling using the BaseI2C class provided by the twlabcpp library.


EEPROM Data Structure (Field Definition)

As the TW100PC module is an Ethernet-based device, its EEPROM contains standard fields such as:

  • Version information
  • Network configuration (e.g., IP, MAC address)
  • Firmware update metadata

These definitions can be extended or modified to fit application-specific needs.


Code Components

The EEPROM control in this project is organized into three main classes:

  1. EEPDefinition(.cpp, .h)
    • EEPROM field name and size definition
  2. AT24CTask(.cpp, .h)
    • I2C driver class for AT24Cxx EEPROM devices
  3. SystemInfo(.cpp, .h)
    • Stores and manages data read from EEPROM

This tutorial does not cover the full internal implementation of each class but focuses on object creation and usage examples.


Code Implementation

1. Include header files and declare objects

#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <ConsoleTask.h>
#include <BasicFunctions.h>
#include <RUNLEDTask.h>

#include <EEPDefinition.h>
#include <SystemInfo.h>
#include <AT24CTask.h>

#include <ProductCode.h>
...
ConsoleTask CTask;
__attribute__ ((section(".ccmram"))) volatile uint8_t consoleBuf[2048];

BasicFunctions basic;
RUNLEDTask runledTask;

EEPDefinition EEP;
SystemInfo SysInfo;
AT24CTask At24cTask;

2. Define initialization function

...
void InitEepromLocationStruct(void);
void InitSystemInfo(void);
void InitAt24cTask(void);
...
/*
 *
 */
void InitEepromLocationStruct(void)
{
	EEP = EEPDefinition(&CTask);

	EEP.PrintPositionAndLength();
}

/*
 *
 */
void InitSystemInfo(void)
{
	CTask.PRINTF((char *)"InitSystemInfo starts\r\n");
	CTask.flushTxBuf();
	SysInfo = SystemInfo(&CTask);
	SysInfo.setEEPPtr(&EEP);
	SysInfo.setInitialization();
	CTask.PRINTF((char *)"InitSystemInfo finished\r\n");
	CTask.flushTxBuf();
}

/*
 *
 */
void InitAt24cTask(void)
{
	At24cTask = AT24CTask(&hi2c3, &CTask, &SysInfo);
	At24cTask.setEEPDefinitionPtr(&EEP);

	if(At24cTask.checkEEPROM())
	{
		At24cTask.LoadSystemInfoFromEEPROM();

		if(memcmp(SysInfo.getMacAddr(), TWARELABMAC, 3) != 0)
		{
			CTask.PRINTF((char *)"First Run. Doing Factory Reset\r\n");
			SysInfo.setMacAddr(DeviceMac);
			At24cTask.saveMacAddrToEEPROM();
			SysInfo.setFactoryReset();
			bConfigChanged = True;
		}

		if((memcmp(SysInfo.getProductCode(), ProductCode, 2) != 0) || (memcmp(SysInfo.getVersion(), FWVer, 3) != 0))
		{
			CTask.PRINTF((char *)"Product Code is invalid\r\n");
			SysInfo.setProductCode(ProductCode);
			SysInfo.setVersion(FWVer);
			SysInfo.setNTPDomain(NullString);
			bConfigChanged = True;
		}

		if(SysInfo.General.NTPDomain[0] == 0xFF)
		{
			SysInfo.setNTPDomain(NullString);
			bConfigChanged = True;
		}

		if(SysInfo.getChannelNum() != 1)
		{
			SysInfo.setChannelNum(1);
			bConfigChanged = True;
		}

		CTask.flushTxBuf();

		if(bConfigChanged)
		{
			At24cTask.SaveSystemInfoToEEPROM();
			CTask.flushTxBuf();
			At24cTask.LoadSystemInfoFromEEPROM();
		}

	}
}

3. Call initialization in main.c

...
  InitEepromLocationStruct();
  InitSystemInfo();
  InitAt24cTask();
...

Troubleshooting

When working with EEPROM, developers often encounter a few common issues:

  1. Data not written
    • Cause: Accessing I2C before the write cycle is complete
    • Solution: Wait for tWR (Write Cycle Time) after each write
  2. Unexpected read values
    • Cause: EEPROM address miscalculation
    • Solution: Verify multi-byte addressing (e.g., AT24C32 uses 2-byte addressing)
  3. Initialization failure at boot
    • Cause: EEPROM accessed before it is ready
    • Solution: Ensure sufficient stabilization time after power-on

Extended Use Cases

Beyond storing network settings, EEPROM can be applied in various scenarios:

  • User preferences (brightness, volume, operation mode)
  • Event logging
    (basic history of system events)
  • Firmware management (storing active image flags for OTA updates)

Summary

In this step, we introduced the basic structure for storing and retrieving system information using EEPROM.
Key takeaways:

  • EEPROM provides persistent data storage across power cycles
  • The TW100PC module uses an AT24C32 EEPROM over I2C
  • A structured field definition makes system data easier to manage
  • Using layered classes (AT24CTask, SystemInfo) improves code maintainability
  • Troubleshooting tips and extended use cases make the design more practical

In the next post, we will dive deeper into practical read/write operations with EEPROM, including W5500 setting for Ethernet and Network function examples.


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *

en_USEnglish