AVR32 Tutorial

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche


Dieses Tutorial soll eine Einführung in die AVR32(32-bit)-Architektur erleichtern. Die AVR32-Architektur ist im Vergleich zu den 8 Bit AVRs oder PICs schwieriger zu erlernen und deshalb nicht unbedingt für Mikrocontroller-Anfänger geeignet. Die meisten Änderungen der 32-bit- Architektur im Vergleich zur 8-bit-Architektur liegen im Interrupt-Handling und beim Register-Zugriff.

Die Besonderheit dieses Tutorials ist es, komplett auf das AVR32 Software-Framework von Atmel zu verzichten, um so besser verstehen zu können wie der UC funktioniert.

Der vorgestellte Code ist für einen AT32UC3L064 zugeschnitten und dort auch getestet.

Motivation und Zielgruppe

Dieses Tutorial wurde geschrieben, da es bisher noch kein Tutorials zu Atmels 32-Bit-Serie gibt. Deshalb richtet sich das Tutorial in erster Linie an alle, die von der 8-bit Serie (ATmega/Attiny/Xmega...) auf die 32-bit-Prozessoren umsteigen möchten. Das Tutorial wurde so gestaltet, dass es auch von allen anderen, die in die Atmel 32-bit Serie einsteigen wollen, verwendet werden kann. Sollte es Verbesserungsvorschläge oder Ideen geben, mit denen das Tutorial verbessert werden kann: Bitte sendet eine PN oder hinterlasst im Diskussionsbereich einen Kommentar. --Basti195 (Diskussion) 11:52, 22. Aug. 2014 (CEST)

Benötigte Vorkenntnisse

Da der GCC verwendet wird, ist es notwendig gute C-Kenntnisse zu besitzen. Ohne das Atmel Software Framework ist es außerdem notwendig, einige grundlegende Kenntnisse in Assembler zu haben, die man sich ggf., wenn man die entsprechenden Stellen des Tutorials erreicht, noch nachträglich aneignen kann.

Benötigte Software

Grundsätzlich kann man mit dem AVR32 Studio und der Atmel AVR32-Toolchain (mittlerweile auch mit dem Atmel Studio) alle Aufgaben übernehmen. Es geht aber auch ohne das AVR-Studio. Man benötigt dann einen Editor, die Atmel AVR32-Toolchain und unter Windows ist cygwin zu empfehlen, bzw. man kann auch einfach unter Linux entwickeln.

Die Atmel AVR32-Toolchain bekommt man über die Atmel-Homepage (Quellcode oder fertige Installer) oder alternativ hier den Quellcode [1].


Die Toolchain besteht aus mindestens 3 Komponenten:

  • binutils
  • gcc
  • eine c-library

Wenn man diese Software aus dem Quellcode installieren möchte, sind die Patches von Atmel erforderlich. Man kann sich die fertig gepatchten Quellen von avr32linux.org runterladen. Selbst zu patchen kann je nach Linuxdistribution bzw. der Version der Distribution sehr kompliziert werden, da nach dem Patchen die Makefiles neu aufgebaut werden müssen.

Benötigte Hardware


Benötigt werden

  • Prozessor: z.B. AT32UC3L064
  • Programmer: z.B. JTAG
  • Board: z.B. ein Dev-board
  • Oszilloskop -> zum Debuggen


Trouble-Shooting


Hier geht es um Logik-Fehler und nicht um Syntax-Fehler.
Die hier dargestellten Methoden sind allgemein und dienen als Checkliste.

Leider funktioniert nur selten alles wie geplant. Deshalb ist es hier umso wichtiger, die Projekte genau zu dokumentieren, da so deutlich schneller Fehler gefunden werden können.
Schleicht sich doch einmal ein Fehler in den Code und funktioniert es nicht, ist es sehr hilfreich, einen Debugger zu haben. Alternativ kann man auch auf den eingeschränkten Simulator vom Atmel Studio zurückgreifen.
Am besten geht man dabei folgendermaßen vor:

  • Fehler lokalisieren: über ein Oszilloskop kann herausgefunden werden, ob es sich um einen Prozessor-Fehler handelt oder um ein Fehler eines Fremdgerätes.

  • Zunächst einmal sollte der Code nochmals durchgearbeitet werden, um so sicher zu stellen, dass nicht wichtige Teile vergessen wurden.

  • Nun sollte das Programm mit dem Debugger Schritt für Schritt durchgegangen werden um zu überprüfen, ob wirklich alle Bits in den richtigen Registern gesetzt worden sind. Manchmal löst ein Bit ein Ereignis aus, welches andere Bits löscht!

  • Ebenfalls sollte im Power-Management überprüft werden, ob die Funktionen aktiv sind.



Register Zugriff

Der Registerzugriff bei der AT32-Serie ist etwas anders als bei der AT8-Serie. Um hier ein Register zu beschreiben muss nicht wie z.B. an einem ATmega8 der Befehl wie folgt aussehen:

  DDRD = 0xff;

Sondern bei der 32-bit Serie wird ein Register stattdessen folgendermaßen beschrieben:

  AVR32_"Funktion".[Channel].Register = 0xff;

Vor jedem RegisterBefehl steht zunächst das "AVR32_". Hieran wird dann eine Funktion (z.B. SPI) angehängt, damit der Prozessor weiß wem er das Register zuweisen muss. Die Unterfunktion kommt dann zum Einsatz, wenn es mehrere Channels gibt, wie z. B. 3 Timer oder 2 GPIO-lines. Hier muss dann die Zahl in "[ ]" geschrieben werden. Zum Schluss muss noch das entsprechende Register angesprochen werden ".Register".

Der gesamte Befehl sieht dann am Ende so aus:

  AVR32_TC0.channel[1].cr = 1<<8;


GPIO - General Purpose Input Output

Codebeispiel anhand eines AT32UC3L064

IO-Zugriff

Bei der 32-Bit Serie ist die Ansteuerung der Pins etwas anders gelöst: Hier kann ein Port nicht mehr mit DDRx und PORTx beschrieben werden, sondern dies geschieht über ein spezielles GPIO-Register, wobei man sich auch hier entsprechende #DEFINEs machen kann.

Die GPIO Befehle sind folgendermaßen aufgebaut:

    AVR32_GPIO.port[X].register = 0xff;

Der Teil AVR32_GPIO.port[1/2] bezeichnet die entsprechenden Ports (A oder B). Mit dem .Register-Name wird dann das entsprechende Register angesprochen. Nun muss nur noch eine 1 auf das entsprechende Bit gesetzt werden. Hier gilt, dass der erste Pin auf der 0ten Position steht und der zweite auf Position 1...


Um ein Signal aus dem UCxx zu bekommen, muss zunächst das GPIO-Modul aktiviert werden. Dies wird über das GPIO Enable Register(gper) gemacht.

  AVR32_GPIO.port[0].gper = 1<<13 // Port A Pin 13


Der Pin wird mit dem OVR (Output Value Register) auf high gezogen

 AVR32_GPIO.port[1].ovr = 1<<13 // Port B Pin 13 auf 3,3V''


Es gibt zu jedem Register zusätzlich die Endung t/c/s toggl/clear/set

  AVR32_GPIO.port[0].gperc = 1<<13 //löscht das entsprechende Bit da gperC (clear)


Um nun den Wert eines Pins auslesen zu können, muss man sich das .pvr(Port Value Register ) anschauen. Ist das entsprechende Bit gesetzt, ist der Port logisch high.

Modul-Konfiguration

Die AT32-Prozessoren verfügen über das Feature, dass Hardware-Funktionen nicht an einen speziellen Pin gebunden sind, sondern sich multiplexen lassen.
Dies hat den Vorteil, dass man nicht mehr an einen bestimmten Pin gebunden ist, sondern dass man den Prozessor an das Layout des Schaltplans anpassen kann. Jedoch macht dies die Konfiguration deutlich komplizierter.


Jeder Port hat die Möglichkeit, bis zu 8 (A-H) verschiedene Hardware-Funktionen zu übernehmen (Datenblatt: Package and Pinout)

Nun muss der entsprechende Port im PMRx (GPIO) der entsprechenden Funktion zugewiesen bekommen.

Ausschnitt aus der "Peripheral Multiplexing on I/O lines"-Tabelle:

PIN Pin Type A B C ...
PA00 Normal I/O USART0 TXD USART1 RTS SPI NPCS[2]
PA02 Highdrive I/O USART0 RTS ADCIFB TRIGGER USART2 TXD
PA06 Highdrive I/O, 5V tolerant SPI SCK USART2 TXD USART1 CLK



Die prozessorspezifische Tabelle befindet sich unter dem Punkt "Package and Pinout"-"Peripheral Multiplexing on I/O lines" im Datenblatt.

Wichtig:
Damit die Hardware-Funktionen auch verwendet werden können, muss im Register "Gper" der Pin deaktiviert werden, da sonst der Port ganz normale GPIO-Funktionen hat.



Um nun den Modus eines Pines zu verändern müssen die PMRx Register wie Folgend verändert werden.

MODE PMR2 PMR1 PMR0
A 0 0 0
B 0 0 1
C 0 1 0
D 0 1 1
E




Beispielcode um auf Pin A17 den Mode B und auf Pin B05 den Mode D zu bekommen:

 
AVR32_GPIO.port[0].pmr0 = 1<<17;	AVR32_GPIO.port[0].pmr1 = 0;	AVR32_GPIO.port[0].pmr2 = 0; //mode B
AVR32_GPIO.port[1].pmr0 = 1<<5;		AVR32_GPIO.port[1].pmr1 = 1<<5;	AVR32_GPIO.port[1].pmr2 = 0; //mode D




Interrupt

IRQ(Interrupt Request)

Der Interrupt-Controller der AT32-Serie hat zahlreiche Unterscheide zur AT8-Serie. Hier gibt es verschiedene Interrupt Requests, die jedem Interrupt einzeln zugewiesen werden können. Hierzu ist es wichtig die Gruppe des Interrupts zu wissen. Jede Hardware-Funktion hat ihre eigene Gruppe.
Jeder Interrupt hat eine eigene Interrupt-Line. So kann man jeden Interrupt direkt zuordnen. Aus den beiden Werten bildet sich die IRQ-Nummer (interrupt request nummber).

Dies erstellt sich folgendermaßen:

 Interrupt-Gruppe * 32 + Interrupt-Line

Dem Interrupt-Controller müssen nun nur die IRQ-Nummer und ein Pointer auf die Interrupt-Routine und die Priorität des Interrupts übergeben werden. Dies geschieht in der main{}.

 
INTC_register_interrupt( &funktion,IRW,Prio)


Interrupt im Atmel Studio

Nun muss nur noch die Funktion, die dann wärend des Interruptes durchgeführt wird, erstellt werden, jedoch mit dem Suffix:

 __attribute__((__interrupt__))
void funktion(void)
{
//code
}


So wird dem Compiler mitgeteilt, dass es sich hierbei um eine Interrupt-Routine handelt und er diese mit "Vorsicht" genießen soll, d.h. aktuelles Programm anhalten, Daten speichern, Interrupt ausführen, und wieder an die letzte Position zurück gehen.

Beispiel

Ein solches Programm kann dann so ausschauen:


 __attribute__((__interrupt__)) void timer(void) //interrupt handler 
         {  
		//Code	  
	 }	


int main (void)
  {

   Enable_global_interrupt(); //wichtig!!
   INTC_register_interrupt( &timer,800,1); //interupt group int_level 	
   // IRQ%32 = line number  
   // IRQ/32 = Gruppe
   // IRQ = 32 *25 (Gruppe)+0(line)= 800.

   // im Register den Interrupt erlauben
   AVR32_TC0.channel[0].ier = 1<<AVR32_TC_IER0_CPCS; //interrupt enable 
   }



Sonderfälle

In einigen Registern ist der AVR32UC3 nicht in der Lage den Interrupt-Aufruf von selbst zu löschen. d.h der Interrupt wird immer wieder nach Beendigung der Interrupt Routine wieder ausgerufen.
um dies zu umgehen muss man laut Datenblatt den Interrupt Disablen, und daraufhin ein Dummy-read auf das Status- bzw. dem Interrupt-Status -Registers ausführen, damit intern der Interrupt Aufruf gelöscht wird.
möchte man nun die Interrupt Routine wieder starten macht man das über IER

Bsp:

	
		AVR32_TC0.channel[Timer].idr =	1<< AVR32_TC_IER0_CPCS; //disable  Interuppt bei RC comparae
		AVR32_TC0.channel[0].sr;// dummy read
		AVR32_TC0.channel[0].ier = 1<< AVR32_TC_IER0_CPCS; //enable interrupt

Datenblatt:

Clearing of the interrupt request is done by writing to registers in the corresponding peripheral module, which then clears the corresponding NMIREQ/IREQ signal. The recommended way of clearing an interrupt request is a store operation to the controlling peripheral register, followed by a dummy load operation from the same register. This causes a pipeline stall, which prevents the interrupt from accidentally re-triggering in case the handler is exited and the interrupt mask is cleared before the interrupt request is cleared.

Timer/Counter


So wie viele andere Prozessoren verfügt auch der AVR32xxx über eine Timer Funktion.
Der AVR32UCL verfügt über insgesamt 6x 32-Bit-Timer. Diese werden über insgesammt 2 Register angesprochen:

     - AVR32_TC0
- AVR32_TC1

Jedes dieser Register verfügt über je 3 Timer, die sich einzeln konfigurieren lassen. Dies geschieht über die Channel-Funktion. z.B.

      - AVR32_TC0.channel[0].ccr 
      - AVR32_TC0.channel[1].ccr 
      - AVR32_TC0.channel[2].ccr


Der Timer ist in der Lage in 2 Funktions-Typen zu arbeiten, dem Capture Mode und dem Waveform Mode. Der unterschied liegt ist, dass im Capture Mode TIOA und TIOB (Timer Input/Output A/B)als Input für den Timer verwendet werde, und im Waveform Mode als Output.

Capture Mode

Beispiel Konfiguration, in der der Timer auf einen Bestimmten wert hoch zähl und in der zeit in eine Routine abläuft.

AVR32_TC0.channel[0].ccr = 1<<AVR32_TC_CCR0_CLKEN | 1<<AVR32_TC_CCR0_SWTRG ;//start timer + enable timer
AVR32_TC0.channel[0].cmr = 2<<AVR32_TC_CMR0_WAVSEL |1<<AVR32_TC_CMR0_WAVE | 1<<AVR32_TC_CMR0_TCCLKS;//mode config

while(AVR32_TC0.channel[2].cv <= zeit_daten_senden) 
			{	
				//mach was 
			}


Über die Register LDRB und LDRA kann der Aktuelle Zählerstand in einen der beiden Register (RA / RB) abgelegt werden.

Waveform Mode

im Waveform Mode kann über ein RA/RB Compare und einem RC Compare ein eine Steigende oder fallende Flanke am TIOA/B erzeugt werden. So ist es einfach ein PWM Signal zu erzeugen, was ein genaues PWM ermöglicht als der 8-Bit PWM der pro Pin verbaut ist.
Um den Waveform Mode zu aktivieren muss im CMR Register WAVE auf 1 gesetzt werden. Insgesamt gibt es 4 verschieden Funktionen, die über WAVESEL ausgewählt werden können:

     -UP mode 
-UPDOWN
-UP mode mit RC Compare Trigger
-UPDOWN mit RC Compare Trigger

Der Counter wird entweder bis 0xFFFF hochgezählt und beginnt wieder bei 0x0000, oder er wird nach 0xffff von oben nach unten zurückgezahlt.
auch ist es möglich bei einem RC Compare einen reset auszulösen.
TIOA und TIOB sind in der Lage ein Signal bei einem Compare mit RA/RB und RC auszulösen. Hierbei ist es möglich das Signal entweder zu löschen, Toggeln oder zu setzen.

	AVR32_TC0.channel[Timer].ier =		1<< AVR32_TC_IER0_CPCS; //enable Interuppt bei RC comparae
	AVR32_TC0.channel[Timer].cmr =	 1<<AVR32_TC_CMR0_ACPA | 2<<AVR32_TC_CMR0_ACPC // Setzte TIOA bei RC 
                                                                          //compere und lösche es bei RA comparae
		 |   1<<AVR32_TC_CMR0_BCPB | 2<<AVR32_TC_CMR0_BCPC // TIOB Setze bei RC und lösche bei RC 
		 |   1 <<AVR32_TC_CMR0_WAVE // wave enable
		 |   2<<AVR32_TC_CMR0_WAVSEL //Wavemode 2 UP- RC copare und reset des Counters
		 |   1<<AVR32_TC_CMR0_TCCLKS //clock select
		 |   1<<AVR32_TC_CMR0_ENETRG // externe Trigger disable, da ansonsten TIOB nicht als Output ist 
		 |   1<<AVR32_TC_CMR0_EEVT; //keine Externel Trigger EVents behandeln 
		
		AVR32_TC0.channel[Timer].ra  = 3686<<AVR32_TC_RA;  //Register a
		AVR32_TC0.channel[Timer].rb  = 3686<<AVR32_TC_RB;  //Register b	==>  RA start TIOA     RB lösche
                                                                   // TIOB		RC lösche TIOA Start TIOB
		AVR32_TC0.channel[Timer].rc  = 0xffff<<AVR32_TC_RC; //Register c
		AVR32_TC0.channel[Timer].ccr = 1<<AVR32_TC_CCR0_CLKEN | 1<< AVR32_TC_CCR0_SWTRG; //clock enable und start


Interrupt

Jeder dieser Timer/counter ist in der Lage bei einem Timer-Overflow ein Interrupt auszulösen. Diese lassen sich über die IRQ auseinander halten.
Es ist möglich ein Interrupt bei einem RA - RB - RA - Comparare auslösen zu lassen, sowie bei einer Änderung am Externen Trigger oder einer RB bzw. RA Load-action.

TWI/I2C

Allgemein

Mit dem TWI-Bus hat Atmel einen Klon von Philips I2C-Bus implementiert.
Der AVR32uc3-L0064 und die meisten anderen AVR32-Prozessoren verfügen über 2x TWI-Master und über 2x TWI-Slaves, die sich einzeln Konfigurieren lassen.
Dies wird über folgendes Register durchgeführt:

    AVR32_TWIM0  //Master1
    AVR32_TWIM1  //Master2
    AVR32_TWIS0  //Slave1
    AVR32_TWIS1  //Slave2

Die Register Master0/1 sowie die Register Slave0/1 sind identisch aufgebaut.

Konfiguration

In den AVR32-Bibliothek werden die #Define's für die einzelnen Register nicht nach Master 0 und 1 unterschieden, da diese identisch sind. Deshalb müssen die Register wie folgt angesprochen werden:

    AVR32_TWIM_NCMDR_VALID


Grundkonfig

Um den TWI-Bus zum Laufen zu bringen muss zunächst einmal das Entsprechende Bit im Power-Management gesetzt werden(hierzu im Datenblatt nachschauen, welches dies ist).
Genauso wichtig ist es, darauf zu achten, dass die Ports richtig gemultiplext werden.
Nun kann im "CWGR"-Register(Clock Waveform Generator Register) der Clock Prescaler (EXP) mit den Werden 0-7 bespielt werden.
Eine weitere Besonderheit ist, dass sich die TWI-High- und Low Phasen, sowie wie Daten und die Start/Stopp-Zeiten Individuell einstellen lassen.
Dies hat den Vorteil, dass hier mit viel Erfahrung noch ein kleines bisschen Zeit herausgekitzelt werden kann. Ich empfehle jedoch, die Zeiten auf "1" zu stellen.

Daten-Übertragung

Nun sollen die ersten Daten übertragen werden:
Hierzu ist das Register "CMDR" (Command Register) und "NCMDR"(Next Command Register) wichtig. Diese Register sollen nach jeder Übertragung neu beschrieben werden.
Die wichtigsten Bits sind:

  • NBYTES: Hier wird die Anzahl der zu übertragenden Bytes eingeben. Eine Stopp-Bedingung wird erst gesendet, wenn all Bytes gesendet worden sind.
  • STOP: Sendet nach der Übertragung ein Stopp-Flanke
  • Start: Beginnt die Übertragung mit einer Start-Flanke
  • SADR: Slaveadresse
  • Read: Wenn gelesen werden soll muss hier eine 1 stehen, ansonsten weiß der Slave, dass geschrieben wird.


Jetzt weiß der TWI-Bus, wie gesendet werden soll. Nun muss ihm nur noch gesagt werden was gesendet wird. Die zu sendende Information wird in das Register "THR" (Transmit Holding Register) geschrieben.
Nun muss noch die Übertragung gestartet werden. Hierzu muss im CMDR-Register das Bit VALID gesetzt werden. Hat man eine Übertragung von mehr als 1 Byte, muss nach der Übertragung des ersten Bytes das THR-Register mit dem nächsten Byte gefüttert werden (jedoch ohne VALID!).

Daten Senden und Empfangen

Um z. B. Sensoren auszulesen wird normalerweise zunächst die Register-Adresse auf dem Bus geschrieben, und dann sendet der Slave den oder die Werte des angefragten Registers.
Hierzu muss eine Übertragung mit START = 1,SADR,Stopn = 0, Read = 0 und Nbytets = 1 begonnen werden. In THR wird das Register geschrieben.
Im nächsten Schritt wird dann (z. B. über das Ncmdr-Register -- wichtig hierbei: das VALID-Register muss dann auch im NCDMR-Register gesetzt werden und nicht im CMDR!) eine Übertragung gestartet: START = 0, SADR, Stopn = 1, Read = 1 und Nbytets = 1. Nun weiß der Slave, dass er etwas zurücksenden soll, und legt die Information auf die Datenleitung. Beim Zurücksenden wird der Takt (Clock) vom Master angelegt. Nach der Übertragung schreibt der Master die Information in das "RHR"-Register (Receive Holding Register).
Viele Slaves sind in der Lage einen Burst-Read durchzuführen: Hier wird einmal das Lesen gestartet und der Slave sendet denn immer weiter(Register abwärts oder aufwärts), bis eine STOP Bedingung gesendet wird. Einfach Nbytes auf die Anzahl der Bytes stellen.

Takt-Quelle

Im AVR32UC können übers Powermanagement verschiedene Einstellungen zur Takt-Source und zur Taktversorgung der Teilkomponenten des Prozessors getroffen werden.

RC-Oszillator

Der AVRUC3X verfügt über einen interne RC-Oszillator, der als Takt-Source verwendet werden kann. hierzu müssen folgende Register im Powermanagement(PM) gesetzt werden:

//Rc enable
	AVR32_SCIF.unlock =  0x0058 << AVR32_SCIF_UNLOCK_ADDR| 0xAA<<AVR32_SCIF_UNLOCK_KEY;
	AVR32_SCIF.rc120mcr = 0xff; //enable 120mhz oszilator
	
	AVR32_PM.unlock = 0xAA << AVR32_PM_UNLOCK_KEY   | 0x004 << AVR32_PM_UNLOCK_ADDR;
	AVR32_PM.cpusel = 1<< AVR32_PM_CPUSEL_CPUDIV | 2<< AVR32_PM_CPUSEL_CPUSEL; //pba auf ein 6 von mainclock 
        // hier ist auch twi drinnen
	
	AVR32_PM.unlock = 0xAA << AVR32_PM_UNLOCK_KEY  | 0x000 << AVR32_PM_UNLOCK_ADDR;
	AVR32_PM.mcctrl = 3; //clocksource auf RC-oszilator umstellen

Wichtig hierbei ist das die entsprechenden Register über ein Log-Register geschützt sind. Ist das zu beschreibende Register nicht entsperrt, kann es nicht beschrieben werden.
Um das entsprechende Register zu entsperren, muss im Log-Register der "Key" eingetragen werden und die Adresse des zu entsperrenden Registers.
Wichtig: das Register ist nur für einen weiten Befehl entsperrt.

Peripherie Komponenten

Ebenfals ist es möglich die Peripherie Komponenten einen eigen Takt zu geben dies geschieht über das PBAMASK-Register und dem PBASEL-Register im Power-Management.
Hier kann allen Peripherie Komponenten einen Main-Clock unabhängigem Takt gegeben werden. Hierbei wird die Main-Clock durch den Teiler im PBASEL geteilt. Der Takt ist nun Mainclock/2^(teiler+1)

Links und Literaturempfehlungen

Es ist empfehlenswert, im Datenblatt nicht nur den entsprechende Abschnitt durchzulesen, sondern auch nach den Begriffen zu suchen, da viele wichtige Informationen nicht in dem entsprechenden Abschnitt stehen (wie z.B. Modulkonfiguration).


Autoren

Ersteller : unbekannt
Bearbeitung: Sebastian Balz


--Basti195 (Diskussion) 15:39, 6. Sep. 2014 (CEST)