AT32UC3C - Einstieg in die Programmierung

Wechseln zu: Navigation, Suche

Motivation

Ich beschäftige mich nunmehr seit einem Jahr im Rahmen eines Praktikums bei der Bosch Engineering GmbH mit dem AT32UC3C0512C. Ich habe in dieser Zeit einige Erkenntnisse gewonnen, die ich Euch hier gerne zur Verfügung stellen möchte. Während meiner Recherche habe ich die Erfahrung gemacht, dass man einige Quellen zur A- und B-Serie des AT32UC3 findet, jedoch wenig zur C-Serie. Das Atmel Software Framework war für mich in den meisten Fällen ebenfalls nicht brauchbar, da es viel zu komplex aufgebaut ist. Daher hoffe ich, dass ich mit diesem Beitrag einigen von Euch den Einstieg in die Programmierung mit dem AT32UC3C erleichtern kann. Viel Erfolg!

Architektur

Architektur AT32UC3C.png

Der AT32UC3C0512C besitzt einen 32-bit-RISC-Prozessorkern mit 512KB Flash und 64KB SRAM. Der Prozessor kann mit einer Taktfrequenz von bis zu 66MHz betrieben werden. Der Prozessorkern ist umgeben von Peripheriemodulen, die für die verschiedenen Funktionen des Controllers zuständig sind. Die einzelnen Module erhalten ihren Arbeitstakt über die drei Peripheriebusse PBA, PBB und PBC (graue Pfeile im Bild), deren Takte jeweils einzeln konfiguriert werden können. Diese Peripheriebusse sind neben der HighSpeedBusMatrix (HSB, orangener Kasten im Bild) für den Informationsaustausch zwischen den verschiedenen Modulen zuständig. Die lila dargestellten Module sind für interne Funktionen wie Taktfrequenzen, Speicherorganisation, Interruptfreigaben usw. zuständig. Die grün dargestellten Module sind dagegen für die Kommunikation mit dem Controllerumfeld verantwortlich. In meinen weiteren Beiträgen werde ich vor allem auf die Konfiguration der wichtigen internen Funktionen eingehen, die notwendig sind, um den Mikrocontroller zu betreiben.

Headerfiles

Folgende Headerfiles müssen in einem Programm für den AT32UC3 eingebunden sein:

- inttypes.h

- avr32/io.h

Der avr32/io-Header fragt ab, welcher Mikrocontroller als Device ausgewählt wurde und bindet einen dazu passenden Header ein. Dieser wiederum bindet eine große Anzahl von Header-Files ein, die Defines für die einzelnen Funktionsmodule des Controllers enthalten. In den Headerfiles sind sowohl den Registern, als auch den im Datenblatt erwähnten wichtigen Bits und Werten jeweils Namen zugewiesen, um den Programmcode lesbarer zu gestalten. Diese Namen sind nach einer Systematik benannt, die hier im nächsten Beitrag „Beschreiben von Registern“ erläutert wird.

Systematische Bezeichnung von Registern, Bitfeldern, Masken

Hier soll die Systematik der Namen erklärt werden, die für die Bezeichnung von Registern, Bitfeldern und Masken in den Systemheader-Files hinterlegt sind:

Register

AVR32_< MODUL-NAME >.< REGISTER-NAME >

Beispiel: AVR32_USART0.cr

Mit dieser Bezeichnung wird die absolute Adresse eines Registers angegeben. Im Beispiel handelt es sich um das USART-Control Register, das die absolute Adresse 0xFFFF2800 hat. AVR32_USART0.cr heißt also dasselbe wie 0xFFFF2800.

Wenn es mehrere Register gibt, die gleich heißen und zur Identifizierung mit einer laufenden Nummer versehen sind, sieht die Bezeichnung so aus:

AVR32_< MODUL-NAME >.< REGISTER-NAME >[n]

Beispiel: AVR32_SCIF.oscctrl[0]

Register-Offset

AVR32_< MODUL-NAME >_< REGISTER-NAME >

Beispiel: AVR32_USART_CR

Diese Bezeichnung gibt den Registeroffset, also die Speicherposition im jeweiligen Modulspeicher, an. Im Beispiel ist das CR (Control Register) das erste Register im Modul USART und hat damit den Offset 0x0000. Es gibt fünf USART-Module (USART0-USART4), wobei alle Module gleich aufgebaut sind. Damit ist der Registeroffset überall gleich.

Bit-Felder

AVR32_< MODUL-NAME >_< REGISTER-NAME >_< BITFELD-NAME >

Beispiel: AVR32_USART_CR_TXEN

Bitfelder sind ein- oder mehrere Bit große Registerbereiche, die auf eine bestimmte Weise beschrieben werden müssen, damit eine entsprechende Funktion ausgeführt wird. Jedes Bitfeld, dem eine Funktion zugewiesen ist, hat einen zur Funktion passenden Namen, der im Datenblatt in der Registerübersicht zusammen mit der zugehörigen Funktion vermerkt ist. Die Bitfeldzeichnung gibt die Position im Register an.

Im Beispiel heißt das Bitfeld TXEN (Transmitter Enable). Da es sich beim TXEN um das Bit 6 im CR-Register handelt, ist die Bezeichnung AVR32_USART_CR_TXEN gleichbedeutend mit der Zahl 6.

Absolute Adressierung von Bitfeldern

AVR32_<MODUL-NAME>.< REGISTER-NAME >.< BITFELD-NAME >

Beispiel: AVR32_USART.CR.txen

Wenn man ein Bitfeld in einem Register direkt beschreiben will, kann man die absolute Adresse des Registerbereichs auf diese Weise angeben.

Offset

AVR32_< MODUL-NAME >_< REGISTER-NAME >_< BITFELD-NAME >_OFFSET

Beispiel: AVR32_USART_CR_TXEN_OFFSET

Diese Bezeichnung ist gleichbedeutend mit der Bezeichnung des Bitfeldes selbst. Sie enthält lediglich das Zusatzwort _OFFSET, um besser zu verdeutlichen, dass es sich um die entsprechende Dezimalzahl für das Bit im Register handelt.

Masken

AVR32_< MODUL-NAME >_< REGISTER-NAME >_< BITFELD-NAME >_MASK

Beispiel: AVR32_USART_CR_TXEN_MASK

Die Bitfeldbezeichnung mit dem Zusatz _MASK steht für eine Hexadezimalzahl, die als Binärzahl umgerechnet an der Stelle des bezeichneten Bits eine 1 enthält. Setzt man sie mit dem Register gleich, setzt man an der entsprechenden Stelle das Bit. Im Beispiel ist AVR32_USART_CR_TXEN_MASK gleichbedeutend mit 0x00000040.

Beschreiben von Registern mit LOCK-Funktion

Die Systemregister des AT32UC3C, die für die grundlegenden Initialisierungsschritte zuständig sind (z.B. im SCIF-Register und im PM-Register), sind oft mit einer Lock-Funktion versehen. Das bedeutet, sie sind nicht ohne weiteres zum Beschreiben freigegeben. Um die Register trotzdem beschreiben zu können, müssen sie zunächst durch Beschreiben eines UNLOCK-Registers entsperrt werden.

Anlegen von Macros zum Entsperren von LOCK-Registern:

#define SCIF_UNLOCK(reg)  (AVR32_SCIF.unlock = (unsigned long)(AVR32_SCIF_UNLOCK_KEY_VALUE<<AVR32_SCIF_UNLOCK_KEY_OFFSET)|(reg))

#define PM_UNLOCK(reg)	  (AVR32_PM.unlock = (unsigned long)(AVR32_PM_UNLOCK_KEY_VALUE<<AVR32_PM_UNLOCK_KEY_OFFSET)|(reg))

Das Makro zum Entsperren der SCIF-Register heißt hier SCIF_UNLOCK. In das AVR32_SCIF.unlock-Register wird zunächst im entsprechenden Bitfeld ein Unlock-Key (0xAA) eingegeben. Gleichzeitig wird im Adressteil des Unlock-Registers die Adresse des zu entsperrenden Registers eingetragen. Da jedes gesperrte Modul-Register ein eigenes Unlock-Register besitzt, muss als Adresse nur der Registeroffset eingetragen werden.

Der Entsperrbefehl muss unmittelbar vor dem Beschreiben des Registers vom Prozessor bearbeitet werden, da das Register nur für den nächsten Taktzyklus freigegeben ist.

Verwendung der Makros:

Möchte man das „Oscillator0 control register“ (OSCCTRL0, Registeroffset 0x0024 im SCIF) beschreiben, so geht man wie folgt vor. In diesem Fall wird der Oscillator 0 aktiviert.

//Registerbereich OSCCTRL0 wird entsperrt, Konstante AVR32_SCIF_OSCCTRL entspricht 0x0024
SCIF_UNLOCK(AVR32_SCIF_OSCCTRL); 

//OSC Enable Bit auf 1 setzen
AVR32_SCIF.oscctrl[0] = (1<<AVR32_SCIF_OSCEN_OFFSET);

System Control Interface (SCIF) und Power Manager (PM)

Das System Control Interface ist für folgende Funktionen zuständig:

  • Konfiguration der internen Oszillatoren (OSCCTRL0, OSCCTRL1, OSCCTRL32)
  • Konfiguration der beiden Phasenregelschleifen (PLL0 und PLL1), die zur Modufikation und Vervielfachung (bis maximal 66MHz) der Oszillatortakte verwendet werden können.
  • Konfiguration und Aktivierung von Generic Clocks. Es gibt einige Module (z.B. USB, CAN …), die spezifische Taktfrequenzen benötigen. Diese werden als Generic Clock bereitgestellt und müssen vor der Nutzung aktiviert und durch Setzen von Teilern oder Faktoren eingestellt werden.

Der Power Manager ist für folgende Funktionen zuständig:

  • Festlegung der Taktquelle für die Systemtakte. Hier wird festgelegt, mit welchem Oszillator (intern oder extern) oder PLL der Arbeitstakt für die CPU, sowie die drei Peripheriebusse (PBA, PBB, PBC) und die High-Speed-Bus-Matrix (HSB) erzeugt werden soll. Außerdem können Teiler und Faktoren zur Anpassung des CPU-/PBx-Takte gesetzt werden. Der HSB-Takt muss immer gleich dem CPU-Takt sein.
  • Taktmaskierung: In den ClockMASK-Registern können Modultakte durch löschen des zum Modul gehörenden Bits angeschaltet werden, um Energie zu sparen. Im Default-Zustand sind alle Modultakte aktiviert.
  • Verwaltung der Energiesparfunktionen (Sleepmodes) und Hard-/Softwareresets

Beispielprogramm zur Initialiserung der CPU

Headerfile cpu.h

/*
 * cpu.h – Definition von Makros, Deklaration von Funktionen
*/ 

#ifndef CPU_H_		//Vermeidung von Mehrfacheinbindung des Headers
#define CPU_H_

//Makros
//-------
//Auslesen und Beschreiben von Statusregistern(*1)
#define CPU_sysreg_read(reg)		__builtin_mfsr(reg)
#define CPU_sysreg_write(reg, val)  	__builtin_mtsr(reg, val)

//Speicherbarriere(*2)
#define CPU_barrier()			asm volatile("" ::: "memory")

//UNLOCK-Makros
#define CPU_scif_unlock(reg)		(AVR32_SCIF.unlock =	\ 	
  	(unsigned long)(AVR32_SCIF_UNLOCK_KEY_VALUE<<AVR32_SCIF_UNLOCK_KEY_OFFSET)|(reg))
#define CPU_pm_unlock(reg)		(AVR32_PM.unlock = 	\
  	(unsigned long)(AVR32_PM_UNLOCK_KEY_VALUE<<AVR32_PM_UNLOCK_KEY_OFFSET)|(reg))

//Aktivierung und Deaktivierung globaler Interrupts(*3)
#define IRQ_enable()		do {				\
		 barrier();				        \
		 __builtin_csrf(AVR32_SR_GM_OFFSET);	        \
	   } while (0)

#define IRQ_disable()		do {				\
		 __builtin_ssrf(AVR32_SR_GM_OFFSET);	        \
		barrier();				        \
	   } while (0)

//Funktionsprototypen
void CPU_set_clk(void);

#endif /* CPU_H_ */

Erklärungen zum Code (*1 - *3):

1. Auslesen und Beschreiben von Systemregistern:

Die Systemregister befinden sich außerhalb des virtuellen Addressraums und müssen deshalb durch die besonderen Befehle „mfsr=read status reg“ und „mtsr=write status reg“ modifiziert werden. Es gibt hier Statusregister/Flagregister, die den aktuellen Stand einer Rechenoperation dokumentieren. Beim Beschreiben von normalen Registern im vituellen Speicher kann es passieren, dass Statusregister (Bezeichnung AVR32_SR) verändert werden. Daher sollten diese zunächst mithilfe der vorliegenden Makros gesichert werden und nach dem Verändern der virtuellen Register wieder auf ihre alten Werte gesetzt werden.

2. Speicherbarriere:

Hier wird ein Macro für eine Speicherbarriere (memory barrier) definiert. Die Speicherbarriere sorgt dafür, dass alle vorangehenden Speicheroperationen abgeschlossen werden bevor die nachfolgenden Speicheroperationen gestartet werden. Damit werden Optimierungsversuche des Compilers vermieden.

3. Aktivierung und Deaktivierung globaler Interrupts:

Da die globale Interruptfreigabe ebenfalls über die Systemregister erfolgt, werden hier Makros zur Aktivierung und Deaktivierung globaler Interrupts angelegt. Dazu werden die Befehle „csrf=clear sys reg“ und „ssrf=set sys reg“ verwendet. Beschrieben wird hier das Global-Interrupt-Mask-Statusregister.

C-File cpu.c

/*
 * cpu.c
*/ 

#include <avr32/io.h>
#include <stdint.h>
#include "cpu.h"

//Quarzfrequenz (Hz)
#define CRYSTAL_FREQUENCY		12000000
//CPU-Clock, PBA-Clock
#define CPU_CLOCK			66000000
#define PBA_CLOCK			33000000

//Funktionsprototypen
static void CPU_set_osc0_clk(void);
static void CPU_set_pll0_clk(void);
static void CPU_set_cpu_clk(void);
static void CPU_set_pba_clk(void);

void CPU_set_clk(void)
{
        // Systemregisterinhalt sichern
	uint32_t flags;
	flags = CPU_sysreg_read(AVR32_SR);
	//Globale Interrupts deaktivieren
	IRQ_disable();
	
	//Aktivierung und Konfiguration des Oszillator0 und des PLL0 - Funktionsaufruf
	CPU_set_osc0_clk();
	CPU_set_pll0_clk();
		
	//Falsh Wait State auf 1 setzen(*4)
	AVR32_FLASHC.fcr |= (1 << AVR32_FLASHC_FWS_OFFSET);

	//PLL0 als Taktquelle des Main-Clocks setzen (*5)
	CPU_pm_unlock(AVR32_PM_MCCTRL);
	AVR32_PM.mcctrl = (AVR32_PM_MCCTRL_MCSEL_PLL0<<AVR32_PM_MCCTRL_MCSEL_OFFSET);
	
	//Warten bis synchrone Takte bereit sind
	while ((AVR32_PM.sr & AVR32_PM_SR_CKRDY_MASK)==0);
	
	//CPU- und PBA-Takt einstellen
	//Die Einstellung von HSB-, PBB-, PBC-Takt erfolgt genauso
        CPU_set_cpu_clk();
	CPU_set_pba_clk();

        //Systemregisterinhalte zurückschreiben
	CPU_barrier();
	CPU_sysreg_write(AVR32_SR, flags);
	CPU_barrier();		
	//Globale Interrupts aktivieren
	IRQ_enable();
	
} 
//…

Erklärungen zum Code (*4 - *5):

4. Flash-Wait-State:

Bei Arbeitsfrequenzen größer 33MHz muss der Falsh-Wait-State auf 1 gesetzt werden, da der Flash ab dieser Frequenz langsamer arbeitet als die CPU. Die CPU muss also zwischen ihren Speicherzugriffen eine Pause einlegen, damit die Zugriffe noch fehlerfrei funktionieren.

5. Taktquelle des Main-Clocks auswählen:

Der Main-Clock ist der Arbeitstakt des µControllers. Aus ihm werden die Takte für die CPU, HSB (High-Speed-Bus-Matrix) und die Peripheriebusse (PBA, PBB, PBC) generiert. Per Default sind alle Takte aktiv und laufen auf derselben Frequenz wie der Main-Clock.

//…

void CPU_set_osc0_clk(void)
{
        //Aktivierung und Konfiguration des Oscillator 0 auf 12MHz (*6)
        //Einstellungen: Externer 12MHz-Quarz als Quelle (GAIN, MODE), 
        //Startup-Time 2048 Oscillator-Takte (STARTUP), Oscillator0 aktivieren (OSCEN)
        CPU_scif_unlock(AVR32_SCIF_OSCCTRL);

        AVR32_SCIF.oscctrl[0] = 							\
        (AVR32_SCIF_OSCCTRL0_STARTUP_2048_RCOSC << AVR32_SCIF_OSCCTRL_STARTUP_OFFSET)| 	\
	(AVR32_SCIF_OSCCTRL_MODE_CRYSTAL<<AVR32_SCIF_OSCCTRL_MODE_OFFSET)|		\
        (AVR32_SCIF_OSCCTRL1_GAIN_G2<<AVR32_SCIF_OSCCTRL_GAIN_OFFSET)|			\
	(1<<AVR32_SCIF_OSCEN_OFFSET);

	 //Warten bis Oscillator0 bereit ist
	 while((AVR32_SCIF.pclksr & AVR32_SCIF_OSC0RDY_MASK)== 0);
}

void CPU_set_pll0_clk(void)
{
        //Aktivierung und Konfiguration des PLL0 auf 66MHz (*7)
        //Einstellungen: Oscillator0 als Quelle (PLLOSC), Teiler (PLLDIV), Faktor (PLLMUL), 
        //weitere Optionen (PLLOPT), PLL0 aktivieren (PLLEN)
        CPU_scif_unlock(AVR32_SCIF_PLL);

        AVR32_SCIF.pll[0] = 								\
        (AVR32_SCIF_PLLOSC_OSC0<<AVR32_SCIF_PLLOSC_OFFSET)|				\
        	(2<<AVR32_SCIF_PLL_PLLDIV_OFFSET)| 					\
        	(10<<AVR32_SCIF_PLL_PLLMUL_OFFSET)|					\
        	(5<<AVR32_SCIF_PLL_PLLOPT_OFFSET)| 					\
        	(1<<AVR32_SCIF_PLL_PLLEN_OFFSET);
	
	//Warten bis PLL0 bereit ist
	while ((AVR32_SCIF.pclksr & AVR32_SCIF_PLL0_LOCK_MASK)==0);	
}

Erklärungen zum Code (*6 - *7):

6. Aktivierung und Konfiguration des Oscillators0 auf 12MHz:

Durch das Beschreiben der Bitfelder GAIN und MODE im OSCCTRL0-Register wird der Oscillator0 so konfiguriert, dass er einen externen Quarz mit einer Taktfrequenz zwischen 10 und 16MHz als Quelle nutzt. Im Beispiel handelt es sich um einen Quarz mit 12MHz. Dieser muss an folgende Pins des AT32UC3C angeschlossen werden: XOUT0 = PB31, XIN0 = PB30

7. Aktivierung und Konfiguration des PLL0 auf 66MHz:

Der Takt von Oscillator0 (12MHz), der hier als Quelle gewählt wurde (PLLOSC) muss durch Setzen der Werte PLLMUL und PLLDIV so modifiziert werden, dass sich 66MHz ergeben. Für die Berechnung gilt folgende Formel:

Wenn PLLDIV>0: fvco=(PLLMUL+1)/(PLLDIV)*fosc

Für PLLDIV=2 und PLLMUL=10 ergibt sich eine Taktfrequenz von 66MHz.

//…
	
void CPU_set_cpu_clk(void)
{	
        //Setzen des CPU-Clocks auf den ungeteilten Main-Clock 66 MHz (*8)
        //(diese Einstellung entspricht der Default-Einstellung)
	CPU_pm_unlock(AVR32_PM_CPUSEL);
	AVR32_PM.cpusel = (0<<AVR32_PM_CPUSEL_CPUDIV_OFFSET)|(0<<AVR32_PM_CPUSEL_CPUSEL_OFFSET);
	
	//Warten bis synchrone Takte bereit sind
	while ((AVR32_PM.sr & AVR32_PM_SR_CKRDY_MASK)==0);
}

void CPU_set_pba_clk(void)
{
	//Setzen des PBA-Clocks auf den halben Main-Clock 33 MHz (*9)
        CPU_pm_unlock(AVR32_PM_PBASEL);
	AVR32_PM.pbasel = (1<<AVR32_PM_PBASEL_PBADIV_OFFSET)|(0<<AVR32_PM_PBASEL_PBSEL_OFFSET);
	
        //Warten bis synchrone Takte bereit sind
	while ((AVR32_PM.sr & AVR32_PM_SR_CKRDY_MASK)==0);
}

//…

Erklärungen zum Code (*8 - *9):

8. Setzen des CPU-Clocks auf den ungeteilten Main-Clock:

Hier wird der CPU-Clock auf den ungeteilten Main-Clock gesetzt, indem man sowohl CPUSEL als auch CPUDIV mit 0 beschreibt. Diese Einstellung entspricht jedoch der Default-Einstellung und muss nicht zwingend durchgeführt werden.

9. Setzen des PBA-Clocks auf den halben Main-Clock (33MHz):

Der PBA-Clock wird hier auf die Hälfte des Main-Clocks gesetzt. Der Main-Clock kann nur durch Zweierpotenzen geteilt werden. Hier wird durch setzen des PBADIV-Feldes auf 1 der Teiler aktiviert. Er berechnet sich dann aus dem Wert, der in PBSEL abgelegt wird nach der Formel PBA-Div=2^(PBSEL+1). Im Beispiel wird PBSEL auf 1 gesetzt, sodass sich als Teiler 2 ergibt.

Besonderheiten beim Debuggen mit dem JTAGICE und dem STK600

Bei der Programmierung des AT32UC3C empfiehlt es sich, einen Debugger zu verwenden, da die Fehlersuche sonst sehr beschwerlich ausfallen kann. Ich verwende den JTAGICE in Verbindung mit dem STK600. Der Umgang mit dem Debugger und dem Entwicklungsboard ist in der Atmel-Hilfe ausführlich beschrieben, daher möchte ich darauf nicht näher eingehen.

Ich möchte hier lediglich auf einen Bug hinweisen, der beim Debuggen auftreten kann.

Der aktuelle Status der Register, die über einen UNLOCK-Befehl entsichert werden müssen, wird nur angezeigt, wenn folgendes beachtet wird: Das Atmel Studio muss neu gestartet sein. Nur im ersten Debug-Durchgang wird der Registerstatus korrekt angezeigt. Während dem Debuggen muss das IO-View Fenster geschlossen werden. Das bedeutet, man geht mit Start Debugging and Break in den Debug Modus, schließt sofort den IO Viewer, arbeitet sich mit Step Over oder mithilfe von Breakpoints bis über die Stelle hinaus vor, die man betrachten will und öffnet dann das IO-View Fenster wieder.