Kampis Elektroecke

XMega – CRC

XMega Blockdiagramm

Einige XMega-Mikrocontroller verfügen über einen eingebauten CRC-Generator, mit dem eine 16-Bit CRC nach dem CCITT-Standard oder eine 32-Bit CRC nach dem IEEE 802.3-Standard in Hardware berechnet werden kann. In diesem Teil des Tutorials zeige ich, wie der interne CRC-Generator der XMega Mikrocontroller genutzt werden kann, um eine Checksumme von beliebigen Daten oder vom Programmcode im Flashspeicher zu berechnen.

CRC eines Speicherbereichs bestimmen:

Im ersten Beispiel soll eine 16-, bzw. 32-Bit CRC von einem beliebigen Datenblock, hier ein 8 Byte großes Array, bestimmt werden. Zu Beginn werden erst einmal ein paar Daten erzeugt.

uint8_t Data_In[8];

for(uint8_t i = 0x00; i < 0x08; i++)
{
	Data_In[i] = i;
}

Vor jedem Einsatz muss das CRC-Modul zurückgesetzt werden, damit die Schieberegister des Moduls einen fest definierten Wert beinhalten. Sowohl der CCITT-, als auch der IEEE 802.3 Standard verwenden den Startwert 0xFFFFFFFF für die Berechnung der Checksumme. Daher müssen bei dem Reset alle CHECKSUM-Register mit Einsen gefüllt werden:

CRC.CTRL |= CRC_RESET_RESET1_gc;

Hinweis:

Der Defaultwert der CHECKSUM-Register richtet sich immer nach dem CRC-Standard. Wenn z. B. statt dem CCITT-Standard der AUG-CCITT-Standard genutzt werden soll, müssen die CHECKSUM-Register mit dem Wert 0x1D0F vorgeladen werden.

CRC.CHECKSUM0 = 0x1D0F & 0xFF;
CRC.CHECKSUM1 = (0x1D0F >> 0x08) & 0xFF;
CRC.CHECKSUM2 = (0x1D0F >> 0x10) & 0xFF;
CRC.CHECKSUM3 = (0x1D0F >> 0x18) & 0xFF;

Über das CTRL-Register wird anschließend der CRC-Modus, also ob eine 16-Bit oder eine 32-Bit CRC berechnet werden soll, und die Datenquelle eingestellt. Im ersten Durchgang soll eine 16-Bit CRC berechnet werden, weshalb das CRC_32-Bit gelöscht wird. Als Datenquelle wird das I/O Interface eingestellt:

CRC.CTRL &= ~CRC_CRC32_bm;
CRC.CTRL &= ~CRC_SOURCE_gm;
CRC.CTRL |= CRC_SOURCE_IO_gc;

Das Modul ist nun bereit die Daten zu empfangen. Die Daten werden einzeln in das DATAIN-Register kopiert. Sobald alle Daten kopiert wurden, wird das BUSY-Bit im STATUS-Register gesetzt um die Berechnung der CRC zu starten:

for(uint8_t i = 0x00; i < 8; i++)
{
	CRC.DATAIN = Data[i];
}

CRC.STATUS |= CRC_BUSY_bm;

Sobald das Modul die Berechnung abgeschlossen hat, löscht es das BUSY-Bit und die berechnete Checksumme kann ausgelesen werden. Abschließend wird das Modul noch deaktiviert, indem die SOURCE-Bits gelöscht werden.

while((CRC.STATUS & CRC_BUSY_bm));

Checksum = ((uint16_t)CRC.CHECKSUM0 & 0x00FF);
Checksum |= (((uint16_t)CRC.CHECKSUM1 << 0x08) & 0xFF00);

CRC.CTRL &= ~CRC_SOURCE_gm;

Mittels eines Onlinerechners kann man überprüfen ob das Modul richtig gerechnet hat. Für dieses Beispiel muss als Ergebnis der Wert 0x178D ausgegeben werden.

Wenn nun eine 32-Bit CRC berechnet werden soll, muss das CRC_32-Bit im CTRL-Register gesetzt werden. Das Ergebnis ist dann in den Registern CECKSUM03 abgelegt.

CRC.CTRL |= CRC_CRC32_bm;
CRC.CTRL &= ~CRC_SOURCE_gm;
CRC.CTRL |= CRC_SOURCE_IO_gc;
for(uint8_t i = 0x00; i < Length; i++)
{
	CRC.DATAIN = Data[i];
}

CRC.STATUS |= CRC_BUSY_bm;

while((CRC.STATUS & CRC_BUSY_bm));
Checksum = ((uint32_t)CRC.CHECKSUM0 & 0x000000FF);
Checksum |= (((uint32_t)CRC.CHECKSUM1 << 0x08) & 0x0000FF00);
Checksum |= (((uint32_t)CRC.CHECKSUM2 << 0x10) & 0x00FF0000);
Checksum |= (((uint32_t)CRC.CHECKSUM3 << 0x18) & 0xFF000000);

CRC.CTRL &= ~CRC_SOURCE_gm;

Als Ergebnis muss das Modul den Wert 0x88AA689F ausgeben.

CRC des Programmcodes bestimmen:

Weiterhin bietet das Modul die Möglichkeit eine 32-Bit CRC nach dem IEEE 802.3-Standard von dem Programmcode, dem Bootcode oder dem Anwendungscode, also Programmcode ohne Bootloadersektion, zu berechnen. Im nächsten Beispiel zeige ich, wie eine 32-Bit CRC von einem Teil des Programmcodes berechnet werden kann. Damit die Ergebniskontrolle möglichst einfach ist, sollen lediglich acht Stellen im Flash-Speicher ausgelesen und für die CRC verwendet werden.

Hier hier wird das CRC-Modul vor der Benutzung zurückgesetzt, damit es einen definierten Zustand einnimmt:

CRC.CTRL |= CRC_RESET_RESET1_gc;

Anschließend wird das Modul für eine 32-Bit eine 32-Bit CRC und der Flash-Speicher als Quelle konfiguriert.

CRC.CTRL |= CRC_CRC32_bm;
CRC.CTRL &= ~CRC_SOURCE_gm;
CRC.CTRL |= CRC_SOURCE_FLASH_gc;

Im nächsten Schritt muss der NVM-Controller, also der Controller, der die Speicher des Mikrocontrollers verwaltet, konfiguriert werden, damit dieser die Speicherzellen ausließt und den Inhalt an das CRC-Modul weiterreicht. Dazu wird zuerst das entsprechende Kommando zum Auslesen eines Bereichs des Flash-Speichers für eine CRC in das CMD-Register des NVM-Controllers geschrieben. Als nächstes wird die Start- und die Endadresse des Speicherbereichs in die ADDR– und DATA-Register des NVM-Controllers kopiert.

NVM.CMD = NVM_CMD_FLASH_RANGE_CRC_gc;
NVM.ADDR0 = Start & 0xFF;
NVM.ADDR1 = (Start >> 0x08) & 0xFF;
NVM.ADDR2 = (Start >> 0x10) & 0xFF;
NVM.DATA0 = (Start + Length - 1) & 0xFF;
NVM.DATA1 = ((Start + Length - 1) >> 0x08) & 0xFF;
NVM.DATA2 = ((Start + Length - 1) >> 0x10) & 0xFF;

Achtung:

Sowohl die Start-, als auch die Endadresse müssen gerade Werte enthalten, da der Flash-Speicher in Worte (2 Byte) aufgebaut ist. Ungerade Adressen werden aufgerundet, was zu einer falschen CRC führt!


Danach kann das Kommando ausgeführt werden. Dies erfolgt über das Setzen des CMDEX-Bits im CTRLA-Register des NVM-Controllers. Dieses Bit ist durch das CCP-Register geschützt und dem entsprechend muss erst eine spezielle Signatur in das Register geschrieben werden, damit der Schutz aufgehoben wird. Da dieser Vorgang zeitkritisch ist, wird er in einem Assemblerblock ausgeführt:

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"
);

Sobald das Modul fertig ist, kann die CRC ausgelesen und das Modul deaktiviert werden.

uint32_t Checksum = 0x00;

while((CRC.STATUS & CRC_BUSY_bm));
Checksum = ((uint32_t)CRC.CHECKSUM0 & 0x000000FF);
Checksum |= (((uint32_t)CRC.CHECKSUM1 << 0x08) & 0x0000FF00);
Checksum |= (((uint32_t)CRC.CHECKSUM2 << 0x10) & 0x00FF0000);
Checksum |= (((uint32_t)CRC.CHECKSUM3 << 0x18) & 0xFF000000);

CRC.CTRL &= ~CRC_SOURCE_gm;

Die CRC der ersten acht Speicherstellen im Beispielprogramm lautet 0xB9AD0FB1.

0x0c 0x94 0xfe 0x00 0x0c 0x94 0x12 0x01

Auch hier kann die CRC wieder online überprüft werden.


Hinweis:

Soll die CRC statt über einen bestimmten Bereich im Flash-Speicher, über den kompletten Boot- oder Applikationsektor berechnet werden, so muss das NVM-Kommando durch den Befehl NVM_CMD_BOOT_CRC_gc bzw. NVM_CMD_APP_CRC_gc ersetzt werden. Die ADDR– und DATA-Register des NVM-Controllers müssen in diesem Fall nicht beschrieben werden.


DMA Support für das CRC-Modul:

Im letzten Beispiel möchte ich noch zeigen, wie der DMA Support für das CRC-Modul funktioniert. Dazu wird zuerst der DMA-Controller und der DMA-Kanal (hier Kanal 0) konfiguriert. Der DMA soll 8 Datenbytes, die in einem Array gespeichert sind, zum CRC-Modul kopieren.

uint8_t Data_In[8];

for(uint8_t i = 0x00; i < 8; i++)
{
	Data_In[i] = i;
}

Der Transfer soll über die Software gestartet und ein Transaction complete-Interrupt soll am Ende der Übertragung ausgelöst werden.

DMA.CTRL = DMA_ENABLE_bm;
DMA.CH0.CTRLB = DMA_CH_TRNINTLVL_LO_gc;
DMA.CH0.TRFCNT = 8;
DMA.CH0.CTRLA = DMA_CH_BURSTLEN0_bm;
DMA.CH0.TRIGSRC = DMA_CH_TRIGSRC_OFF_gc;
DMA.CH0.SRCADDR0 = ((uintptr_t)&Data_In) & 0xFF;
DMA.CH0.SRCADDR1 = (((uintptr_t)&Data_In) >> 0x08) & 0xFF;
DMA.CH0.SRCADDR2 = (((uintptr_t)&Data_In) >> 0x0F) & 0xFF;
DMA.CH0.DESTADDR0 = ((uintptr_t)&CRC.DATAIN) & 0xFF;
DMA.CH0.DESTADDR1 = ((uintptr_t)&CRC.DATAIN >> 0x08) & 0xFF;
DMA.CH0.DESTADDR2 = ((uintptr_t)&CRC.DATAIN >> 0x0F) & 0xFF;
DMA.CH0.ADDRCTRL = DMA_CH_SRCRELOAD_TRANSACTION_gc | DMA_CH_DESTRELOAD_TRANSACTION_gc | DMA_CH_SRCDIR_INC_gc | DMA_CH_DESTDIR_INC_gc;

DMA.CH0.CTRLA |= DMA_CH_ENABLE_bm;

Eine detailierte Beschreibung für die Funktionsweise des DMA-Controllers ist in meinem DMA-Tutorial zu finden.

Danach wird das CRC-Modul konfiguriert. Es soll eine 16-Bit CRC mit dem DMA-Kanal 0 als Quelle berechnen.

CRC.CTRL |= CRC_RESET_RESET1_gc;
CRC.CTRL &= ~CRC_CRC32_bm;
CRC.CTRL &= ~CRC_SOURCE_gm;
CRC.CTRL |= CRC_SOURCE_DMAC0_gc;

Im DMA Modus nimmt das CRC-Modul so lange Daten vom DMA-Kanal entgegen, bis das Modul deaktiviert wird. Es können also beliebig viele Daten an das Modul geschickt werden.

Jetzt müssen noch die Interrupts aktiviert und der DMA-Transfer gestartet werden:

PMIC.CTRL = PMIC_LOLVLEN_bm;
sei();
DMA.CH0.CTRLA |= DMA_CH_TRFREQ_bm;

Sobald der DMA alle Daten übertragen hat, wird die ISR aufgerufen. In der ISR wird das Interruptflag des DMA-Kanals gelöscht und auf den Abschluss der CRC-Berechnung gewartet. Sobald das CRC-Modul fertig ist, wird die CRC ausgelesen und das CRC-Modul deaktiviert.

ISR(DMA_CH0_vect)
{
	DMA.CH0.CTRLB |= DMA_CH_TRNIF_bm;
	while((CRC.STATUS & CRC_BUSY_bm));
	Checksum = ((uint16_t)CRC.CHECKSUM0 & 0x00FF);
	Checksum |= (((uint16_t)CRC.CHECKSUM1 << 0x08) & 0xFF00);
	CRC.CTRL &= ~CRC_SOURCE_gm;
}

Das komplette Projekt mit allen Programmen steht in meinem GitHub-Repository zum Download bereit.

Schreibe einen Kommentar

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