Kampis Elektroecke

XMega – Zugriff auf das EEPROM

XMega Blockdiagramm

In diesem Beispiel möchte ich zeigen, wie mit Hilfe des NVM-Controllers lesend, bzw. schreibend auf das interne EEPROM zugegriffen wird. Das EEPROM wird immer dann gerne genutzt, wenn kleine Datenmengen (z. B. Programmparameter) nicht flüchtig im Mikrocontroller gespeichert werden sollen.

Sämtliche Schreibvorgänge des NVM-Controllers werden über einen internen Puffer durchgeführt, da der NVM-Controller sowohl für die Schreibvorgänge auf das EEPROM (Zugriff erfolgt byteweise), als auch für die Schreibvorgänge auf den Flash-Speicher (Zugriff erfolgt seitenweise) verantwortlich ist. Daher müssen die entsprechenden Datenbytes zuerst in den Puffer geschrieben werden. Anschließend wird die komplette Seite des Puffers in den Flash, bzw. das EEPROM kopiert.

Die Seitengröße des Puffers ist von Mikrocontroller zu Mikrocontroller unterschiedlich und muss im Datenblatt nachgeschaut werden. Bei einem XMega384C3 beträgt die Seitengröße für das EEPROM 32 Byte. Da der Zugriff auf das EEPROM seitenweise erfolgt, ist die Anzahl der vorhandenen Seiten wichtig. Diese ergibt sich aus der Seitengröße (32 Byte) und der Größe des EEPROMs (4k Byte), wodurch sich eine Anzahl von 128 Seiten ergibt.

Schreibzugriff auf das EEPROM:

Für das Beschreiben des EEPROMs sind folgende Schritte notwendig:

  1. EEPROM-Puffer mit Daten füllen
    1. NVM-Kommando LOAD_EEPROM_BUFFER in das CMD-Register schreiben.
    2. Schreibadresse in die ADDR-Register schreiben.
      • Die Adresse besteht aus bis zu Bit. Die Aufteilung ergibt sich aus dem Datenblatt des Mikrocontrollers.
      • Für das aktuelle Beispiel addressieren die Bits 4:0 den Byteoffset (E2BYTE) und die Bits 11:5 die Seite (E2PAGE) des EEPROMs.
    3. Das Datenbyte in das DATA0-Register schreiben. Dies startet den Schreibvorgang, wodurch das Byte in den internen Puffer kopiert wird.
  2. EEPROM Seite löschen
  3. EEPROM Seite schreiben
    1. Die Schritte 2 und 3 werden mit dem ERASE_WRITE_EEPROM_PAGE-Kommando ausgeführt.
    2. Das Kommando wird in das CMD-Register kopiert.
    3. Das ADDR-Register wird mit der Adresse gefüllt.
    4. Das CMDEX-Bit im CTRLA-Register wird gesetzt. Dieses Bit wird durch das CCP-Register geschützt, weshalb zuerst die korrekte Signatur in das CCP-Register geschrieben werden muss.

Der komplette Programmcode zum beschreiben des EEPROMs sieht folgendermaßen aus:

void NVM_EEPROMWriteByte(const uint8_t Page, const uint8_t Offset, const uint8_t Data)
{
	if((Page > (EEPROM_SIZE / EEPROM_PAGE_SIZE)) || (Offset > (EEPROM_PAGE_SIZE - 0x01)))
	{
		return;
	} 

	NVM_WaitBusy();

	NVM.CMD = NVM_CMD_LOAD_EEPROM_BUFFER_gc;

	uint16_t Address = (uint16_t)(Page * EEPROM_PAGE_SIZE) | (Offset & (EEPROM_PAGE_SIZE - 0x01));
	
	NVM.ADDR0 = Address & 0xFF;
	NVM.ADDR1 = (Address >> 0x08) & 0x1F;
	NVM.ADDR2 = 0x00;

	NVM.DATA0 = Data;

	NVM_ExecuteCommand(NVM_CMD_ERASE_WRITE_EEPROM_PAGE_gc);
}

Zu Anfang wird geprüft ob die übergebene Seite und der übergebene Offset innerhalb des erlaubten Bereichs liegen. Die Konstanten EEPROM_SIZE und EEPROM_PAGE_SIZE werden vom AVRGCC mitgeliefert und beinhalten die entsprechenden Informationen des ausgewählten Mikrocontrollers.

Eine komplette Seite kann geschrieben werden, indem zuerst die Seitenadresse in das ADDR-Register geschrieben und anschließend der Inhalt der Seite in den Puffer geschrieben wird.

void NVM_EEPROMWritePage(const uint8_t Page, const uint8_t* Data)
{
	if(Page > (EEPROM_SIZE / EEPROM_PAGE_SIZE))
	{
		return;
	}

	NVM_WaitBusy();

	NVM.CMD = NVM_CMD_LOAD_EEPROM_BUFFER_gc;
	
	uint16_t Address = (uint16_t)(Page * EEPROM_PAGE_SIZE);
	uint8_t Address0 = Address & 0xFF;
	NVM.ADDR1 = (Address >> 0x08) & 0x1F;
	NVM.ADDR2 = 0x00;

	for(uint8_t i = 0x00; i < EEPROM_PAGE_SIZE; i++)
	{
		NVM.ADDR0 = Address0 | i;
		NVM.DATA0 = *Data++;
	}

	NVM_ExecuteCommand(NVM_CMD_ERASE_WRITE_EEPROM_PAGE_gc);
}

Da der Offset in diesem Fall variabel ist, muss der neue Wert, zusammen mit den niederwertigsten Bits der Seitenadresse, in das ADDR0-Register geschrieben werden.

Im nächsten Schritt wird dann geprüft, ob der NVM-Controller gerade ein Kommando abarbeitet, indem das NVMBUSY-Bit im STATUS-Register abgefragt wird:

static inline void NVM_WaitBusy(void) __attribute__ ((always_inline));
static inline void NVM_WaitBusy(void)
{
	while(NVM.STATUS & NVM_NVMBUSY_bm);
}

Sobald der NVM-Controller bereit ist, wird der erste Punkt abgearbeitet und die benötigten Register mit den nötigen Informationen gefüllt. Dazu wird mit Hilfe der übergebenen Seite und des übergebenen Offsets die EEPROM-Adresse berechnet und passend formatiert in die ADDR-Register des NVM-Controllers geschrieben. Nach dem Beschreiben des DATA0-Registers wird der Kopiervorgang gestartet. Von jetzt an befindet sich das übergebene Datenbyte im Puffer des NVM-Controllers.

Zum Schluss werden die letzten beiden Punkte abgearbeitet, indem das ERASE_WRITE_EEPROM_PAGE-Kommando in das CMD-Register kopiert und durch das Setzen des CMDEX-Bits ausgeführt wird. Da sich die Seitenadresse für das EEPROM bereits in den ADDR-Registern des NVM-Controllers befindet, kann dieser Punkt übersprungen werden.

Die Ausführung eines Kommandos durch das Setzen des CMDEX-Bits wird durch die nachfolgende Funktion gestartet.

static inline void NVM_ExecuteCommand(const uint8_t Command)
{
	NVM.CMD = Command;

	asm volatile(	"movw r30,  %0"	    "\n\t"
					"ldi  r16,  %2"	    "\n\t"
					"out   %3, r16"     "\n\t"
					"st     Z,  %1"     "\n\t"
					::
					"r" (&NVM.CTRLA),
					"r" (NVM_CMDEX_bm),
					"M" (CCP_IOREG_gc),
					"i" (&CCP)
					: "r16", "r30", "r31"
	);
 
	NVM.CMD = NVM_CMD_NO_OPERATION_gc;
}

Zuerst wird das auszuführende Kommando in das CMD-Register kopiert. Damit das Kommando ausgeführt wird, muss das CMDEX-Bit im CTRLA-Register gesetzt werden. Bevor dieses Bit allerdings gesetzt werden kann, muss die Signatur 0xD8 in das CCP-Register kopiert werden. Nachdem die Signatur in das CCP-Register kopiert worden ist, hat die Software vier Taktzyklen Zeit um ein geschütztes Register, hier das CMD-Register, zu beschreiben.

Damit dieser Vorgang auch bei ausgeschalteter Optimierung funktioniert, habe ich die entsprechenden Codezeilen in Inline-Assembler programmiert. Zuerst werden die Adresse des CTRLA-Registers und die Signatur für das CCP-Register in den Registern r30 und r16 gespeichert. Anschließend wird die Signatur in das CCP-Register kopiert und direkt danach das CMDEX-Bit gesetzt. Abschließend wird das CMD-Register gelöscht, indem das Kommando NO_OPERATION in das Register geschrieben wird.

Jetzt kann die Anwendung das EEPROM beschreiben.

NVM_EEPROMWriteByte(0x00, 0x00, 0xCC);
NVM_EEPROMWriteByte(0x00, 0x1F, 0xCC);
NVM_EEPROMWriteByte(0x01, 0x02, 0xAA);

uint8_t EEPROM_PageBufferOut[EEPROM_PAGE_SIZE];
for(uint8_t i = 0x00; i < EEPROM_PAGE_SIZE; i++)
{
	EEPROM_PageBufferOut[i] = i;
}

NVM_EEPROMWritePage(1, EEPROM_PageBufferOut);

Nach der Ausführung des Programms kann das EEPROM z. B. mit einem JTAG-Programmiergerät ausgelesen werden, um zu überprüfen, ob die Werte auch tatsächlich in das EEPROM geschrieben worden sind.

:1000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE
:10001000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02ED
:10002000FFFF03FFFFFFFFFFFFFFFFFFFFFFFFFFDC

Scheint alles geklappt zu haben. Dann schauen wir uns jetzt an, wie wir das EEPROM wieder löschen können.

Löschen des EEPROMs:

Damit das EEPROM gelöscht wird, müssen die folgenden Schritte durchgeführt werden:

  1. NVM-Kommando ERASE_EEPROM in das CMD-Register schreiben
  2. Das CMDEX-Bit im CTRLA-Register setzen
  3. Warten bis der NVM-Controller fertig ist

Die fertige Funktion zum Löschen des EEPROMs ist recht einfach gehalten. Da auch hier das CMDEX-Bit gesetzt werden muss, kann die Funktion NVM_ExecuteCommand wiederverwendet werden.

void NVM_EEPROMErase(void)
{
	NVM_WaitBusy();
	NVM_ExecuteCommand(NVM_CMD_ERASE_EEPROM_gc);
	NVM_WaitBusy();
}

Direkt nach dem Aufruf der Funktion wird gewartet, bis der NVM-Controller alle offenen Aufträge abgearbeitet ist. Anschließend wird der ERASE_EEPROM-Befehl in das CMD-Register kopiert und ausgeführt. Sobald der NVM-Controller den Befehl abgearbeitet hat, wird die Funktion verlassen.

Als letztes wollen wir uns anschauen, wie die geschriebenen Daten wieder ausgelesen werden können.

Aus dem EEPROM lesen:

Für einen lesenden Zugriff auf das EEPROM müssen die folgenden Schritte abgearbeitet werden:

  1. NVM-Kommando READ_EEPROM in das CMD-Register schreiben
  2. Adresse in das ADDR-Register schreiben
  3. Das CMDEX-Bit im CTRLA-Register setzen
  4. DATA0-Register auslesen

Für eine entsprechende Lesefunktion sind bereits alle Komponenten vorhanden und müssen nur noch in der richtigen Reihenfolge aufgerufen werden.

uint8_t NVM_EEPROMReadByte(const uint8_t Page, const uint8_t Offset)
{
	if((Page > (EEPROM_SIZE / EEPROM_PAGE_SIZE)) || (Offset > (EEPROM_PAGE_SIZE - 0x01)))
	{
		return -1;
	}

	NVM_WaitBusy();

	uint16_t Address = (uint16_t)(Page * EEPROM_PAGE_SIZE) | (Offset & (EEPROM_PAGE_SIZE - 0x01));

	NVM.ADDR0 = Address & 0xFF;
	NVM.ADDR1 = (Address >> 0x08) & 0x1F;
	NVM.ADDR2 = 0x00;

	NVM_ExecuteCommand(NVM_CMD_READ_EEPROM_gc);

	return NVM.DATA0;
}

Der Ablauf ist nahezu identisch zum Schreibablauf, außer das im letzten Schritt das Datenbyte aus dem DATA0-Register ausgelesen und zurückgegeben wird. Für große Datenmengen empfiehlt es sich auch hier eine komplette Seite in einem Stück auszulesen.

void NVM_EEPROReadPage(const uint8_t Page, uint8_t* Data)
{
	if(Page > (EEPROM_SIZE / EEPROM_PAGE_SIZE))
	{
		return;
	}

	NVM_WaitBusy();

	uint16_t Address = (uint16_t)(Page * EEPROM_PAGE_SIZE);
	uint8_t Address0 = Address & 0xFF;
	NVM.ADDR1 = (Address >> 0x08) & 0x1F;
	NVM.ADDR2 = 0x00;

	for(uint8_t i = 0x00; i < EEPROM_PAGE_SIZE; i++)
	{
		NVM.ADDR0 = Address0 | i;
		NVM_ExecuteCommand(NVM_CMD_READ_EEPROM_gc);
		*Data++ = NVM.DATA0;
	}
}

Damit können die, im EEPROM gespeicherten, Datenbytes von der Software wieder ausgelesen werden.

uint8_t Data0 = NVM_EEPROMReadByte(0x00, 0x00);
uint8_t Data1 = NVM_EEPROMReadByte(0x00, 0x1F);
uint8_t Data2 = NVM_EEPROMReadByte(0x01, 0x02);

uint8_t EEPROM_PageBufferIn[32];
NVM_EEPROReadPage(1, EEPROM_PageBufferIn);

Zurück

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.