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:
- Flash-Puffer mit Daten füllen
- NVM-Kommando LOAD_FLASH_BUFFER in das CMD-Register schreiben.
- Z-Register mit der Wortadresse für das Datenwort beschreiben.
- Datenwort in die Register R1:R0 schreiben.
spm
Befehl ausführen.
- WRITE_USER_SIG_ROW-Kommando in das CMD-Register schreiben
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.
- 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:
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:
- Das NVM-Kommando ERASE_USER_SIG_ROW in das CMD-Register schreiben.
spm
Befehl ausführen- 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.
- Z-Register mit der Leseadresse laden
- READ_USER_SIG_ROW-Kommando in das CMD-Register schreiben
lpm
Befehl ausführen
Für den lpm
Befehl gilt die folgende Definition:
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