Kampis Elektroecke

XMega – Lesen und Schreiben der User Signature Row

XMega Blockdiagramm

Bei der User Signature Row handelt es sich um ein seperates Datenfeld von der Größe einer einzelnen Flash-Seite. Dieses Feld ist für statische Anwendungsparameter gedacht und kann dem entsprechend von dem Mikrocontroller oder einem Programmiergerät gelesen und beschrieben werden. Im Gegensatz zum EEPROM kann die User Signature Row nicht durch einen Chip-Erase-Befehl des Programmiergerätes, sondern nur durch ein spezielles Kommando gelöscht werden.

Beschreiben der User Signature Row:

Der Zugriff auf die User Signature Row erfolgt auch hier über den internen Puffer des NVM-Controllers. Allerdings werden in diesem Fall keine einzelnen Datenbytes, sondern Datenworte (also zwei Bytes) geschrieben. Um ein Datenwort in die User Signature Row zu schreiben, muss der folgende Ablauf eingehalten werden:

  1. Flash-Puffer mit Daten füllen
    1. NVM-Kommando LOAD_FLASH_BUFFER in das CMD-Register schreiben.
    2. Z-Register mit der Wortadresse für das Datenwort beschreiben.
    3. Datenwort in die Register R1:R0 schreiben.
    4. spm Befehl ausführen.
  2. WRITE_USER_SIG_ROW-Kommando in das CMD-Register schreiben
  3. spm Befehl ausführen
    • Der Befehl wird jetzt durch das CCP-Register geschützt, weshalb zuerst die korrekte Signatur in das CCP-Register geschrieben werden muss.
  4. Warten bis die Operation abgeschlossen ist.

Da beim Schreiben (und auch beim Lesen) der User Signature die Parameter in festgelegten Registern stehen müssen und die Ausführung des Befehls zudem an den spm Befehl gekoppelt ist, soll der Code für diesen Anwendungsfall direkt in Assembler programmiert werden. Eine Besonderheit ergibt sich bei der Nutzung des spm Befehls. Dieser Befehl kann nur aus dem Bootloaderbereich heraus ausgeführt werden, was zur Folge hat, dass Funktionen, die den spm Befehl nutzen, im Bootloaderbereich platziert werden müssen.

Die Startadresse des Bootloaderbereichs ist abhängig vom verwendeten Mikrocontroller und kann dem Datenblatt des Mikrocontrollers entnommen werden. Bei einem XMega384C3, der für dieses Beispiel verwendet wurde, beginnt der Bootloaderbereich bei der Wortadresse 0x30000.

Bevor es an die Umsetzung für den Code zum Beschreiben der User Signature geht, soll die Funktion zum Ausführen des spm Befehls programmiert werden. Diese Funktion muss im Bootloaderbereich des Mikrocontrollers platziert werden. Daher habe ich für Code, der im Bootloaderbereich platziert werden soll, eine eigene Sektion namens boot angelegt.

.section .boot, "ax"
NVM_ExecuteSPM:

	ret

Über die Flags a und x wird der Speicherbereich als allocatable (a) und executable (x) markiert, sprich er wird im Programmcode eingefügt und kann ausgeführt werden.

In den Projekteinstellungen muss nun dem Linker noch mitgeteilt werden, dass es eine neue Sektion namens boot gibt und das diese Sektion in die Bootloadersektion des Mikrocontrollers kopiert werden soll. Dazu wird in den Memory Settings des Linkers in den Projekteinstellungen die Startadresse des Bootloaderbereichs und ein Verweis auf die Sektion eingefügt.


Hinweis:

Wird die Kommandozeile genutzt, so muss der Linkeraufruf folgendermaßen ergänzt werden.

-Wl,--section-start=.boot=0x060000

Hierbei ist zu beachten, dass beim Linker eine Byteadresse und keine Wortadresse angegeben wird!


Nun geht es an den Programmcode um den SPM-Befehl auszuführen. Der Befehl ist folgendermaßen definiert:

Mnemonics Operands Description Operation Flags #Clocks
SPM   Store Program Memory (RAMPZ:Z) ← R1:R0 None

Der Befehl verwendet die Register R1, R0, RAMPZ, R31 und R30. Bei der Ausführung wird das Datenwort, welches in R1 und R0 gespeichert ist, unter der Adresse, die über RAMPZ und dem Z-Pointer, also die Register R31 und R30, definiert ist, gespeichert.

Für den Aufruf des spm Befehls sollen folgende Übergabeparameter gelten:

;--
;	Input:
;		r1:r0		Data for SPM command
;		r31:r30		Lowest 2 bytes of the z pointer
;		r26		NVM command
;		RAMPZ		High byte of the z pointer
;
;	Return:
;		-
;--

Damit der spm Befehl genutzt werden kann, muss eine bestimmte Signatur in das CCP-Register geschrieben werden. Sobald diese Signatur in das Register kopiert worden ist, kann die Anwendung vier Taktzyklen lang den spm und lpm Befehl ausführen.

Es ergibt sich damit die folgende Funktion:

.section .boot, "ax"
NVM_ExecuteSPM:

	sts NVM_CMD, r26
	ldi r19, CCP_SPM_gc
	sts CCP, r19
	spm
	clr r1
	sts NVM_CMD, r1

	ret

Die Funktion kopiert zuerst das übergebene NVM-Kommando in das CMD-Register und schaltet anschließend den spm Befehl frei, der direkt nach dem Freischalten ausgeführt wird.

Weil der Assemblercode zusammen mit C-Code genutzt werden soll, muss anschließend das Register R1 auf 0 gesetzt werden, da dieses Register für AVRGCC reserviert ist und beim Verlassen einer Funktion immer den Wert 0 führen muss (das sogenannte __zero_reg__). Abschließend wird noch das CMD-Register des NVM-Controllers gelöscht (also der Befehl NO_OPERATION eingefügt) und das Register R19 vom Stack geholt.

Jetzt können wir uns dem Beschreiben des Flash-Puffers widmen. Da diese Funktionen aus der Applikation heraus verwenden werden soll, müssen die Register entsprechend des AVRGCC-ABI genutzt werden. Bevor die Daten allerdings in die Signatur Row geschrieben werden können, müssen diese erst in den Puffer des Flash-Speichers geschrieben werden (siehe den weiter oben vorgestellten Ablauf). Dies soll eine Funktion namens NVM_FlashWriteWord, die folgendermaßen definiert ist, übernehmen.

/** @brief		    Write one word to the flash buffer.
 *  @param Address	Word address
 *  @param Data		Data word
 */
void NVM_FlashWriteWord(const uint16_t Address, const uint16_t Data);

Die Funktion erwartet zwei 16-Bit Übergabeparameter, die, wie im AVRGCC-ABI beschrieben, in den Registern R25 und R24 für den Parameter Address und in den Registern R23 und R22 für den Parameter Data übergeben werden.

Das LSB wird dabei immer in der geraden Registeradresse gespeichert. Gemäß des Ablaufs müssen die Daten aus den Registern R25 und R24 im Z-Register gespeichert werden und die Daten aus den Registern R23 und R22 in den Registern R1 und R0.

.section .text
.global NVM_FlashWriteWord
NVM_FlashWriteWord:

	in r18, RAMPZ
	sts RAMPZ, r1
	movw ZL, r24
	movw r0, r22

Hinweis:

Da die User Signature Row nur eine Flash-Seite lang ist (also 512 Byte), reichen zwei Register für die Adressierung. Andernfalls muss das RAMPZ-Register mit genutzt und der Übergabeparameter für die Funktion auf einen uint32_t geändert werden.


Die Funktion sichert zudem das RAMPZ-Register und löscht es anschließend. Dadurch wird verhindert, dass ein noch vorhandener Wert im RAMPZ-Register fälschlicherweise für die Zieladresse genutzt wird.

Da es sich bei dieser Funktion um eine globale Funktion handelt und diese von außerhalb aufgerufen werden soll, muss der Funktionsname nach außen sichtbar gemacht werden. Dies geschieht durch den Zusatz .global Zudem soll diese Funktion im Programmspeicher abgelegt werden. Daher wird für diese Funktion die Speichersektion .text, also die Sektion für den Programmcode, genutzt.

Im nächsten Schritt wird das LOAD_FLASH_BUFFER-Kommando in das Register R26 kopiert und der spm Befehl ausgeführt.

ldi r26, NVM_CMD_LOAD_FLASH_BUFFER_gc
call NVM_ExecuteSPM

Abschließend wird das RAMPZ-Register wiederhergestellt, der Inhalt des verwendete Registers R18 vom Stack geholt und die Funktion verlassen.

out RAMPZ, r18
ret

Für große Datenmengen ist es natürlich praktischer, wenn direkt eine komplette Seite in den Buffer kopiert werden kann. Daher soll eine weitere Funktion namens NVM_FlashWritePage angelegt werden.

/** @brief			Write one complete page to the flash buffer.
 *  @param Data			Pointer to buffer
 */
void NVM_FlashWritePage(const uint16_t* Data);

Die Funktion zum Beschreiben einer kompletten Seite sieht folgendermaßen aus:

.section .boot, "ax"
.global NVM_FlashWritePage
NVM_FlashWritePage:

	in r18, RAMPZ
	sts RAMPZ, r1
	clr ZL
	clr ZH

	ldi r26, NVM_CMD_LOAD_FLASH_BUFFER_gc
	sts NVM_CMD, r26

	movw XL, r24

	ldi r21, ((APP_SECTION_PAGE_SIZE / 2) & 0xFF)
	ldi r19, CCP_SPM_gc

	NVM_FlashUserSignatureWritePage_Loop:
		ld r0, X+
		ld r1, X+ 
		sts CCP, r19
		spm
		adiw ZL, 2 
		dec r21
		brne NVM_FlashUserSignatureWritePage_Loop

	sts NVM_CMD, r1
	clr r1
	out RAMPZ, r18

	ret

Da die Funktion den spm Befehl benutzt, muss auf die richtige Codesektion geachtet werden. Auch diese Funktion muss in den Bootloaderbereich kopiert werden.

Zu Anfang wird das Z– und das RAMPZ-Register gelöscht, damit die Startadresse auf den Anfang der Seite gelegt wird. Dann wird die Adresse des Buffers in das Y-Register und die Größe einer Flash-Seite kopiert und anschließend wird so lange über den Puffer iteriert, bis alle Datenbytes ausgelesen und mit Hilfe des spm Kommandos in den Flash-Puffer kopiert worden sind.

Jetzt stehen die Daten im Puffer für den Flash-Speicher und können mit dem WRITE_USER_SIG_ROW-Kommando in die User Signature Row kopiert werden. Dazu muss das Kommando in das CMD-Register kopiert und erneut ein spm Befehl ausgeführt werden. Auch hierfür wird eine Funktion angelegt:

 /** @brief Flush the flash buffer and write the data to the user signature flash page.
  */
 void NVM_FlushUserSignature(void);

Der Code für diese Funktion ist recht übersichtlich:

.section .text
.global NVM_FlushUserSignature
NVM_FlushUserSignature:

	ldi r26, NVM_CMD_WRITE_USER_SIG_ROW_gc
	call NVM_ExecuteSPM

	ret

Der Zustand des NVM-Controllers kann über das BUSY-Bit im STATUS-Register abgefragt werden. So lange wie das Bit gesetzt ist, ist der NVM-Controller beschäftigt und kann somit keine neue Operation ausführen.

.section .text
.global NVM_WaitBusy
NVM_WaitBusy:

	NVM_Busy_Loop:
		lds	r20, NVM_STATUS
		sbrc	r20, NVM_NVMBUSY_bp
		rjmp	NVM_Busy_Loop

	ret

Damit sind alle Komponenten zusammen und müssen nur noch nacheinander aufgerufen werden:

NVM_FlashWriteWord(0x00, 0x1001);
NVM_FlashWriteWord(0x0A, 0x0110);
NVM_FlushUserSignature();
NVM_WaitBusy();

uint16_t UserSignature_Read[256];
uint16_t UserSignature_Write[256];

for(uint16_t i = 0x00; i < 256; i++)
{
	UserSignature_Write[i] = i;
}

NVM_EraseUserSignature();
NVM_FlashWritePage(UserSignature_Write);
NVM_FlushUserSignature();
NVM_WaitBusy();

Die User Signature Row kann nach dem Ausführen des Programms mittels Debugger ausgelesen werden um die Funktion des Programms zu überprüfen.

:100000001001FFFFFFFFFFFFFFFF0110FFFFFFFF5C
:10001000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0
:10002000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0

Da das Schreiben somit funktioniert, wollen wir uns im nächsten Schritt anschauen, wie die Signatur wieder gelöscht werden kann.

Löschen der User Signature Row:

Für eine Löschung der User Signature Row sind folgende Schritte notwendig:

  1. Das NVM-Kommando ERASE_USER_SIG_ROW in das CMD-Register schreiben.
  2. spm Befehl ausführen
  3. Warten bis die Operation abgeschlossen ist

Die Funktion dafür sieht recht übersichtlich aus, da nur das Kommando geladen und in die spm Funktion gesprungen werden muss.

/** @brief Clear the user signature page.
 */
void NVM_EraseUserSignature(void);
.section .text
.global NVM_EraseUserSignature
NVM_EraseUserSignature:

	ldi r26, NVM_CMD_ERASE_USER_SIG_ROW_gc
	call NVM_ExecuteSPM

	ret

Hinweis:

Um fehlerhafte Daten zu vermeiden ist es sinnvoll, wenn die User Signature Seite vor dem Schreiben komplett gelöscht wird. Andernfalls kann es zu fehlerhaften Daten kommen, wie dieses Beispiel zeigt:

:100000000000FFFFFFFFFFFFFFFF1000FFFFFFFFEC
:10001000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0

In diesem Fall habe ich versucht die geschriebenen Werte 0x1001 und 0x0110 mit den Werten 0x2020 und 0x3030 zu überschreiben, ohne vorher die User Signature Seite gelöscht zu haben.


Zu guter Letzt wollen wir uns noch anschauen, wie Daten von der User Signature Row gelesen werden können.

Auslesen der User Signature Row:

Damit die Datenwörter wieder ausgelesen werden können, muss folgendermaßen vorgegangen werden:

NVM-Kommando LOAD_FLASH_BUFFER in das CMD-Register schreiben.

  1. Z-Register mit der Leseadresse laden
  2. READ_USER_SIG_ROW-Kommando in das CMD-Register schreiben
  3. lpm Befehl ausführen

Für den lpm Befehl gilt die folgende Definition:

Mnemonics Operands Description Operation Flags #Clocks
LPM Rd, Z+ Load Program Memory and Post-Increment Rd ← (Z)
Z ← Z + 1
None 3

Der Befehl lädt das Datenbyte, welches durch das Z-Register addressiert wird, in das Register Rd. Daher muss vor der Ausführung des Befehls das Z-Register mit der Leseadresse beschrieben werden. Als Zielregister soll das Register R24 genutzt werden. Zudem wird empfohlen Interrupts global zu deaktivieren, bevor der lpm Befehl ausgeführt wird, weswegen vor der Ausführung das SREG gesichtert, das GIE-Bit gelöscht und das Register anschließend wiederhergestellt wird.

;--
;	Input:
;		r31:r30		Lowest 2 bytes of the z pointer
;		r26			NVM command
;
;	Return:
;		r24:r25		Result from LPM operation
;--
.section .text
NVM_ExecuteLPM:

	in r18, SREG
	cli
	sts NVM_CMD, r26
	lpm r24, Z+
	lpm r25, Z
	out SREG, r18

	ret

Fehlt nur noch die Funktion um ein Datenwort auszulesen. Die Funktion soll eine 16-Bit Adresse als Übergabeparameter annehmen und das Datenwort als Rückgabewert zurückgeben.

/** @brief		    Read one word to the flash buffer.
 *  @param Address	Word address
 *  @return		    Data word
 */
uint16_t NVM_UserSignatureReadWord(const uint16_t Address);

Gemäß des Ablaufplans ergibt sich damit der folgende Funktionscode:

.section .text
.global NVM_UserSignatureReadWord
NVM_UserSignatureReadWord:

	movw ZL, r24
	ldi r26, NVM_CMD_READ_USER_SIG_ROW_gc
	rcall NVM_ExecuteLPM

	ret

Natürlich kann auch hier wieder eine komplette Seite auf einmal ausgelesen werden. Dazu wird das Kommando geladen und, ähnlich wie bei dem seitenweisen Schreiben, der lpm Befehl in jedem Schleifendurchlauf ausgeführt.

.section .text
.global NVM_UserSignatureReadPage
NVM_UserSignatureReadPage:

	in r18, RAMPZ
	sts RAMPZ, r1
	clr ZL
	clr ZH
	ldi r26, NVM_CMD_READ_USER_SIG_ROW_gc
	sts NVM_CMD, r26
	movw XL, r24
	ldi r21, ((APP_SECTION_PAGE_SIZE / 2) & 0xFF)
	in r18, SREG
	cli

	NVM_FlashUserSignatureReadPage_Loop:
		lpm r24, Z+
		lpm r25, Z+
		st X+, r24
		st X+, r25
		dec r21
		brne NVM_FlashUserSignatureReadPage_Loop

	out SREG, r18
	sts NVM_CMD, r1
	clr r1
	out RAMPZ, r18

	ret

Und schon kann die programmierte User Signature Row über die Applikation ausgelesen werden.

uint16_t Data0 = NVM_UserSignatureReadWord(0x00);
uint16_t Data1 = NVM_UserSignatureReadWord(0x0A);

NVM_UserSignatureReadPage(UserSignature_Read);

Zurück

Schreibe einen Kommentar

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