ubuntuusers.de

AVR

Dieser Artikel wurde für die folgenden Ubuntu-Versionen getestet:


Du möchtest den Artikel für eine weitere Ubuntu-Version testen? Mitarbeit im Wiki ist immer willkommen! Dazu sind die Hinweise zum Testen von Artikeln zu beachten.

Zum Verständnis dieses Artikels sind folgende Seiten hilfreich:

AVR sind Mikrocontroller des Herstellers Microchip Technology Inc. 🇬🇧 zuvor Atmel, die sich vor allem im Hobbybereich großer Beliebtheit erfreuen. AVR sind 8 Bit RISC Mikrocontroller. Weiterhin gibt es noch die AVR32 mit 32 Bit Datenbreite, welche aber bis auf die Bezeichnung nichts mit den AVRs gemeinsam haben.

Die Typen ATtiny 🇬🇧, ATmega 🇬🇧 und AT90 unterscheiden sich in Gehäusegröße und Peripherieausstattung. Der neueste Typ ist die Familie Xmega 🇬🇧, welche mehr Rechenleistung und komplett überarbeitete Peripheriemodule bietet.

Praktischer Aufbau

Ein kompletter Aufbau zum Programmieren und Testen von AVR-Mikrocontrollern könnte folgendermaßen aussehen:

AVR_Grundschaltung_Schema.png

Wie auf dem Bild zu sehen, werden die folgenden Komponenten benötigt:

Diese Komponenten werden nachfolgend einzeln erklärt.

avr_beispiel_Steckplatine.png avr_beispiel_Schaltplan.png
Beispielaufbau auf einer Steckplatine Minimalbeschaltung eines ATtiny13A mit LED

Grundschaltungen

Empfohlene Mindestanforderungen, um einen AVR-Controller zu betreiben, sind:

  • 5V Betriebsspannung (viele AVR erlauben auch andere Spannungen: z.B. 3.3V. Am besten das Datenblatt konsultieren)

  • ein 100-nF-Kondensator möglichst direkt an den Versorgungspins (VCC und GND) des Controllers

Da solch eine Schaltung noch nicht sehr viel Interessantes bietet, sollten folgende Komponenten hinzugefügt werden:

  • Eine Buchse zum Aufspielen des Programms auf den Mikrocontroller (üblich ist hier ein 6- oder 10-poliger Wannenstecker)

  • Pull-up-Widerstand (z.B. 10 kOhm) und Taster für den Reset

  • Etwas Peripherie, z.B.:

    • Leuchtdioden (Widerstand in Reihe schalten, z.B. 470 Ohm)

    • Taster

    • Potenziometer

Die Schaltung kann auf einer Lochrasterplatine oder auf einem Steckbrett aufgebaut werden. Alternativ kann man auch fertige Experimentierboards verwenden.

Programmierwerkzeug

Um das Programm auf den Mikrocontroller zu übertragen, muss der Computer mit jenem verbunden werden. Vor ein paar Jahren war es noch aktuell, die Controller direkt mit der seriellen oder parallelen Schnittstelle des Computers zu programmieren. Da es kaum noch Computer mit "echter" serieller oder Paralleler Schnittstelle gibt und sich die meisten USB-Seriell-Adapter bzw. USB-Parallel-Adapter für diesen Zweck als ungeeignet erwiesen, ist es meist besser, nur noch auf die neueren Programmer mit eigenem USB-Anschluss zu setzen.

Solche Programmer für die USB-Schnittstelle sind als Original-Atmel-Zubehör erhältlich, man findet sie aber auch sehr preisgünstig über Internet-Auktionen. Außerdem gibt es sehr viele Anleitungen für den Eigenbau. Ein sehr populäres Beispiel ist der "USBasp" von fischl.de 🇬🇧, der ebenfalls als Bausatz erhältlich ist:

usbasp_kit1.jpg USBasp_t85_aufbau.jpg
Originalschaltung mit Gehäuse Minimal-Aufbau auf einer Steckplatine

Die zweite Variante ist eine Weiterentwicklung des originalen USBasp und sowohl als Platinenversion als auch als Breadboardversion unter dem Namen "guloprog" bekannt. Da für den Minimalaufbau nur sechs Bauteile benötigt werden, ist der Nachbau sehr einfach. Die Firmware ist quelloffen und kann über die URL https://guloshop.de/f/sources/ ⮷ heruntergeladen werden.

Wer trotzdem einen fertigen Programmer verwenden möchte, findet neben den Geräten von Atmel ein vielfältiges und preiswertes Angebot vor. Ein Beispiel wäre "mySmartUSB light", der bei fast allen Elektronikversandhändlern erhältlich ist. Es gibt auch Boards, die sowohl eine Grundschaltung mit viel Peripherie zum experimentieren, als auch einen Programmer beinhalten. Das hat den Vorteil, dass man nur ein Board mit dem Computer verbinden muss und die Fehlerquellen minimiert werden. Ein Beispiel für solch ein Board ist der "EasyAVR6":

Programmierung in Assembler

Die meisten Programme für Mikrocontroller werden heutzutage in C geschrieben. Assembler verwendet man dann, wenn es um besonders zeitkritische Anwendungen geht oder wenn es innerhalb von C-Programmen besonders zeitkritische Abschnitte gibt. Kaum jemand würde auf die Idee kommen, eine komplexe Anwendung komplett in Assembler zu schreiben. Gerade längere Assembler-Programme sind deutlich schwerer les- und wartbar als vergleichbare C-Programme.

Trotzdem sind Assembler und C für den Einstieg in die Mikrocontroller-Programmierung grundsätzlich gleichermaßen geeignet. Für viele Einsteiger hat C den Vorteil, dass sie die Sprache schon kennen und daher mit den wichtigsten Sprachelementen gut zurecht kommen. Dazu gehört insbesondere die Bitmanipulation (bitweises Und, Oder, Exklusiv-Oder, Shiften usw.). Assembler hingegen bietet den Vorteil, dass man die Funktionsweise des Mikrocontrollers wirklich auf der untersten Ebene kennen lernt. Man kann selbst entscheiden, welches Byte man in welchem Register ablegt. Bei einem späteren Umstieg auf C hat man dadurch einen nützlichen Wissensvorsprung, der klassische Fehler vermeiden hilft und es einem ermöglicht, C-Programme zu schreiben, die für den Einsatz auf Mikrocontrollern optimiert sind.

Assembler bietet einen weiteren – kleinen – Vorteil: der Weg bis zum ersten lauffähigen Programm ist kürzer als bei C. Das liegt aber nicht an der Programmiersprache, sondern lediglich daran, dass bei C zuerst eine komplexere (und leistungsfähigere) Programmierumgebung eingerichtet werden muss als bei Assembler.

Unabhängig von der Entscheidung für Assembler oder C läuft der Programmiervorgang immer nach dem gleichen Schema ab:

  • Es wird mit einem Editor ein Programm-Text geschrieben.

  • Das Programm – genauer: die Programm-Text-Datei – wird übersetzt (Assemblierer oder C-Compiler).

  • Die Ausgabedatei der Übersetzung (Endung ".hex") wird in binärer Form an den Mikrocontroller gesendet.

Einrichten einer Entwicklungsumgebung

Das Installieren und Einrichten der benötigen Software ist ausgesprochen einfach. Der Assemblierer AVRA ist gut geeignet, da er ohne Makefiles auskommt und von seiner Syntax her dem Original-Assembler von Atmel weitgehend entspricht. Außerdem benötigt werden ein Texteditor, sowie das Programm avrdude, mit dem später das assemblierte Programm auf den Mikrocontroller übertragen wird. Die Installation beschränkt sich also auf zwei relativ kleine Pakete:

  • avra (universe)

  • avrdude (universe)

Befehl zum Installieren der Pakete:

sudo apt-get install avra avrdude 

Oder mit apturl installieren, Link: apt://avra,avrdude

An Stelle von AVRA kann natürlich auch der Assemblierer aus dem Paket gcc-avr verwendet werden. Damit wäre allerdings der Installationsvorgang komplizierter; auch die Syntax wiche dann in Teilen vom Atmel-Assembler ab, was die Nutzung der in der Original-Dokumentation veröffentlichten Beispiele erschwert. Wer jedoch bereits mit dem GNU-Assembler vertraut ist, kann diesen als Alternative zu AVRA verwenden.

Anhand eines Beispiels für den kleinen 8-Pin-Mikrocontroller ATtiny13A soll gezeigt werden, wie ein Assembler-Programm erstellt, übersetzt und auf den Controller übertragen wird. Dazu wird ein eigenes Verzeichnis eingerichtet, z.B. einen neuen Ordner avr im persönlichen Ordner:

mkdir ~/avr
cd ~/avr 

Jetzt fehlt nur noch die "Include-Datei" für diesen Mikrocontroller. Leider ist diese Datei von Microchip (zuvor Atmel) nicht einzeln herunterladbar. Man müsste ein riesiges Softwarepaket, das sogenannte Microchip Studio for AVR and SAM Devices 🇬🇧 (bis Version 7.0.2389 Atmel Studio) herunterladen, unter Windows installieren, und diese Datei dann aus einem der Verzeichnisse kopieren. Einfacher ist es, die benötigte Include-Datei im Internet ausfindig zu machen. Am besten sucht man nach den Begriffen "tn13Adef.inc" und "AVR000". Man benötigt eine Datei, die so beginnt:

;***** THIS IS A MACHINE GENERATED FILE - DO NOT EDIT ********************
;***** Created: 2011-02-09 12:03 ******* Source: ATtiny13A.xml ***********
;*************************************************************************
;* A P P L I C A T I O N   N O T E   F O R   T H E   A V R   F A M I L Y

Diese Datei kann man nun per wget herunterladen und gleichzeitig umformen:

wget "http://hier_die_Fundstelle_eintragen/tn13Adef.inc" -O - | sed "s/#/;/" | sed "s/.device ATtiny13A/.device ATtiny13/" >~/avr/tn13Adef.inc 

Hinweis:

Einige wenige "Include-Dateien" stehen nach der Installation des Paktes avra unter /usr/share/avra zur Verfügung, darunter die "Include-Datei" tn13def.inc für den "Vorgänger" des ATtiny13A. Die wenigen Unterschiede beider Bausteine können der Dokumentation von Microchip 🇬🇧 entnommen werden.

Am besten, man besorgt sich gleich die beiden wichtigsten Nachschlagewerke für die AVR-Programmierung, das AVR-Instructionset und das Datenblatt des verwendeten Mikrocontrollertyps:

wget https://ww1.microchip.com/downloads/en/Devicedoc/atmel-0856-avr-instruction-set-manual.pdf -O ~/avr/instructionset.pdf
wget https://ww1.microchip.com/downloads/en/DeviceDoc/doc8126.pdf -O ~/avr/datenblatt_attiny13a.pdf 

Register des Mikrocontrollers

Der ATtiny13A besitzt eine Reihe von Speicherregistern, die für Rechenoperatoren direkt verwendet werden können. Dabei unterscheidet sich der kleine ATtiny13A kaum von den größeren AVR-8-Bit-Mikrocontrollern wie z.B. dem ATtiny861A, ATmega8A, ATmega328 usw. Die wichtigsten dieser Register sind hier beschrieben.

Arbeitsregister r16 bis r31

Der ATtiny13A besitzt wie fast alle Typen der 8-Bit-AVR-Familie 32 Arbeitsregister: r0 bis r31. Manche Assembler-Befehle funktionieren nur mit den Registern aus der oberen Hälfte, nämlich r16 bis r31. Aus Gründen der Einfachheit werden im Beispiel nur diese Register benutzt. Mit einem Arbeitsregister kann man beispielsweise Daten lesen, schreiben, bitweise nach links oder rechts schieben, addieren, subtrahieren, vergleichen und bitweise verknüpfen.

Datenrichtungsregister DDRB (Data Direction Register of Port B)

Dieses Register bestimmt, welcher Mikrocontroller-Anschluss als Eingang und welcher als Ausgang verwendet wird: eine 0 im entsprechenden Bit bedeutet Eingang, eine 1 bedeutet Ausgang. Der ATtiny13A besitzt nur einen "Port", nämlich den Port B. Ein solcher Port umfasst maximal 8 Ein- und Ausgänge, beim ATtiny13A sind es 5 (in Sonderfällen 6).

Datenausgaberegister PORTB (Port Output B)

Für Ausgänge wird hier festgelegt, ob sie niedriges oder hohes Spannungsniveau erhalten sollen. Eine 0 im entsprechenden Bit bedeutet "low" (0 Volt), eine 1 bedeutet "high" (normalerweise 5 Volt). Für Eingänge legen die jeweiligen Bits dieses Registers fest, ob ein Mikrocontroller-interner Pullup-Widerstand aktiviert werden soll.

Dateneingaberegister PINB (Port Input B)

Für jeden als Eingang definierten Anschluss enthält dieses Register ein Bit, das das Spannungsniveau repräsentiert: 0 für "low" (unter ca. 2,5 Volt), 1 für "high" (ca. 2,5 Volt oder mehr). Die Abkürzung "PIN" im Registernamen sollte nicht mit "Pin" ("Anschluss") verwechselt werden, sie steht hier für "Port Input".

Spezialregister

Neben den oben vorgestellten Datenregistern gibt es noch eine Reihe von Spezialregistern, die in unserem einfachen Beispiel nicht verwendet werden. Hier die wichtigsten:

  • Register ADMUX, Bit ADLAR: aktiviert 8 Bit Auflösung für den AD-Wandler

  • Register ADMUX, Bits MUX1 und MUX0: bestimmt den Pin für die AD-Wandlung

  • Register ADCSRA, Bit ADEN: aktiviert den AD-Wandler

  • Register TCCR0B, Bits CS00 bis CS02: bestimmt den Wert des Timer-Vorteilers

  • Register TIMSK0, Bit TOIE0: aktiviert den Überlauf-Interrupt für den Timer

Assembler-Befehle

Hier ein kurzer Überblick über die wichtigsten Assembler-Befehle. In dem einfachen Beispielprogramm werden nur wenige davon gebraucht.

Beispielbefehl Erklärung
ldi r16,123 schreibe die konstante Zahl 123 ins Register R16 (Load Immediate)
inc r16 erhöhe den Inhalt des Registers r16 um eins (Increment)
dec r16 veringere den Inhalt des Registers r16 um eins (Decrement)
add r16,r17 addiere den Inhalt von r17 zum Register r16 (Add)
sub r16,r17 subtrahiere den Inhalt von r17 von Register r16 (Subtract)
subi r16,3 subtrahiere die Konstante 3 von Register r16 (Subtract Immediate)
subi r16,-5 addiere die Konstante 5 zum Register r16 (Subtract Immediate)
lsl r16 verschiebe den Inhalt von r16 um eine Binärstelle nach links (Logical Shift Left)
lsr r16 verschiebe den Inhalt von r16 um eine Binärstelle nach rechts (Logical Shift Right)
in r16,ADCH übertrage den Inhalt von Datenregister ADCH ins Arbeitsregister r16 (Load an I/O Location to Register)
out DDRB,r16 übertrage den Inhalt von Arbeitsregister r16 ins Datenregister DDRB (Store Register to I/O Location)
cbi PORTB,0 setze das Bit Nummer 0 im Datenregister PORTB auf 0 (Clear Bit in I/O Register)
sbi PORTB,0 setze das Bit Nummer 0 im Datenregister PORTB auf 1 (Set Bit in I/O Register)
sbic PINB,4 überspringe nächsten Befehl, falls Bit 4 im Register PINB auf "low" liegt (Skip if Bit in I/O Register is Cleared)
sbis PINB,4 überspringe nächsten Befehl, falls Bit 4 im Register PINB auf "high" liegt (Skip if Bit in I/O Register is Set)
sei Freigabe aller aktivierten Interrupts (Set Global Interrupt Flag)
eine_position: Sprungmarke – hierher kann mit rjmp oder rcall gesprungen werden (Label)
rjmp eine_position springe zur entsprechend markierten Stelle (Relative Jump)
rjmp PC-1 springe zum vorherigen Befehl (Relative Jump)
breq eine_position springe, falls das letzte Ergebnis 0 war (Branch if Equal)
brne eine_position springe, falls das letzte Ergebnis nicht 0 war (Branch if Not Equal)
rcall ein_unterprogramm springe zum entsprechend markierten Unterprogramm(Relative Call to Subroutine)
ret beende das Unterprogramm und springe zurück zum Hauptprogramm (Return from Subroutine)
reti beende das Interrupt-Unterprogramm und setze das normale Programm fort (Return from Interrupt)

Schreiben eines Programms

Das Beispielprogramm soll einen Anschluss des Mikrocontrollers, nämlich PB0 – das ist beim ATtiny13A der Pin 5 – ständig zwischen Low und High umschalten. Dazu wird im vorhin angelegten Verzeichnis avr/ eine neue Datei mit dem Namen beispiel.asm erstellt. Die Endung .asm deutet darauf hin, dass es sich um den Assembler-Quelltext handelt. Der Inhalt dieser Beispieldatei:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
; Beispiel-Projekt beispiel.asm
.include "tn13Adef.inc" ; Definitionen für unseren Mikrocontrollertyp
  ldi r16,0b00001 ; schreibe den Zahlenwert 1 ins Register Nummer 16
  out DDRB,r16 ; Inhalt des Registers 16 ins Datenrichtungsregister
  ldi r16,0b11110 ; binären Zahlenwert 11110 ins Register Nummer 16
  out PORTB,r16 ; Inhalt des Registers 16 ins Ausgaberegister
loop: ; ab hier der Teil des Programms, der ständig wiederholt wird
  sbi PORTB,0 ; schalte die Leuchtdiode ein
   rcall warten ; Aufruf des Warte-Unterprogramms
  cbi PORTB,0 ; schalte die Leuchtdiode aus
   rcall warten ; Aufruf des Warte-Unterprogramms
  rjmp loop ; springe zum Label 'loop'

warten: ; ca. 1/6 Sekunde verzögern
  inc r21
  brne PC-1
  inc r22
  brne PC-3
  ret ; Rücksprung ins Hauptprogramm

Übersetzen des Programms

Das Übersetzen eines Assembler-Programmtexts wird meist als "Assemblieren" bezeichnet. Beim Assemblieren wird jede Befehlszeile in den zugehörigen Maschinencode umgewandelt. Erst dieser Maschinencode kann vom Mikrocontroller direkt gelesen und "verstanden" werden. AVRA speichert den Maschinencode in eine neue Datei und gibt dieser die Endung .hex. In dieser Datei befindet sich nicht direkt binärer Code, vielmehr wurde der binäre Code in menschenlesbarer Form, also in Form von druckbaren Zeichen gespeichert. Erst beim Übertragen zum Mikrocontroller wird dieser lesbare Code wieder in seine ursprüngliche binäre Form zurückverwandelt. Aber dazu später. Zunächst muss das kleine Assemblerprogramm übersetzt werden. Dazu reicht eine einzige Befehlszeile:

avra beispiel.asm 

Falls der Programmtext ohne Fehler war, hat der Assemblierer jetzt die Datei beispiel.hex erstellt.

atmel_AVR_ATtiny13.png
Anschlussbelegung des ATtiny13

Programm aufspielen

Vor dem Übertragen des Programms auf den Mikrocontroller muss dieser korrekt an den verwendeten Programmer angeschlossen werden. Zu verbinden sind die Leitungen VCC, GND, MOSI, MISO, SCK und RESET, auf dem oberen Bild in den Farben Rot, Schwarz, Grün, Weiß, Blau und Gelb dargestellt.

Die Datei beispiel.hex muss nun in binärer Form übertragen werden. Dazu wird das Programm avrdude verwendet. Es ist zweckmäßig, vor der Übertragung zu prüfen, ob der Programmer korrekt an den Computer und der Mikrocontroller korrekt an den Programmer angeschlossen ist. Dazu eignet sich ein harmloser Befehl, der nur das so genannte Fuse Low Byte aus dem Mikrocontroller ausliest:

avrdude -c usbasp -p t13 -B 60 -P usb -U lfuse:r:/dev/stdout:b 

Falls ein anderer Programmer als USBasp verwendet wird, muss der Parameter -c entsprechend angepasst werden. Je nach Berechtigungen des Benutzers kann es erforderlich sein, vor den Befehl ein "sudo" zu setzen. Die Ausgabe sollte so aussehen:

avrdude: set SCK frequency to 16000 Hz
avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.02s

avrdude: Device signature = 0x1e9007 (probably t13)
avrdude: reading lfuse memory:

Reading | ################################################## | 100% 0.01s

avrdude: writing output file "/dev/stdout"
0b1101010

avrdude: safemode: Fuses OK (E:FF, H:FF, L:6A)

avrdude done.  Thank you.

Die Ausgabe "0b1101010" zeigt das Low Fuse Byte in Fabrikeinstellung. Falls bei diesem Test Fehler angezeigt wurden, sollte zuerst den Hinweisen von avrdude nachgegangen werden. Einzige Ausnahme ist der möglicherweise angezeigte Vorschlag "Override with -F". Genau das sollte man bei gängigen Controllertypen wie dem ATtiny13A nicht tun, weil der betreffende Fehler dann zwar ignoriert wird, aber trotzdem noch vorhanden ist.

Lief der Test fehlerfrei, kann nun das Programm übertragen werden:

avrdude -c usbasp -p t13 -B 60 -P usb -U flash:w:beispiel.hex:i 

Im Fall des USBasp wird das Programm sofort nach der Übertragung starten und die LED blinkt in schneller Folge (ca. 3 Hz). Bei anderen Programmern kann es notwendig sein, die Schaltung von den sechs Programmierleitungen zu trennen und eigens per VCC und GND mit einer Spannung zu versorgen (z.B. 5 Volt).

Und – hat etwas nicht geklappt? Dann hilft vielleicht der Abschnitt Fehlerbehandlung weiter.

Programmierung in C

Vorab sollte erwähnt werden, dass der Begriff "programmieren" im Bereich Mikrocontroller zweideutig sein kann. Mit "C programmieren" ist das Schreiben eines Programms für den Mikrocontroller in der Sprache C gemeint. Das "Programmieren eines Mikrocontrollers" ist der Prozess des Übertragens des geschriebenen Programms mit einem Programmieradapter auf den Mikrocontroller.

Die Programmierung von AVRs wird hier für C gezeigt, da C eine weit verbreitete Programmiersprache im Mikrocontrollerbereich ist. Dies hängt damit zusammen, dass aktuelle Compiler den Code so effizient und optimiert in Assembler übersetzen können, dass der Geschwindigkeitsnachteil gegenüber handgeschriebenem Assemblercode für die meisten Anwendungen nur noch klein ist und die Vorteile dieser Hochsprache überwiegen. Bei besonders zeitkritischen Steuerungen oder in Fällen, in denen mit minimaler Hardware maximale Rechenleistung erzielt werden soll (Kostenoptimierung bei sehr großen Stückzahlen) wird trotzdem in der Regel auch auf Assembler zurückgegriffen.

Einrichten einer Entwicklungsumgebung

Das Schreiben des Programms erfolgt oft in einer IDE. Dies ist ein Editor, der mit Zusatzfunktionen ausgestattet ist, die das Schreiben und Testen eines Programms erleichtern. Die offizielle IDE für AVRs ist das Microchip Studio for AVR and SAM Devices 🇬🇧 (ehemals Atmel Studio), welches allerdings nur für Windows erhältlich ist. Als Alternative stehen unter Linux von je her sehr viele sehr gute Editoren zur Verfügung. Diese bieten meist eine Möglichkeit Tastenkürzel und Schaltflächen nach eigenen Wünschen zu konfigurieren und können so einfach mit Hilfe von Skripten zu einer IDE erweitert werden.

Wie dies funktioniert, soll hier am Beispiel des Editors Geany erläutert werden. Des Weiteren benötigt man einige Werkzeuge, um den Quellcode in Maschinensprache zu übersetzen und diesen auf den AVR übertragen zu können. Die nachfolgenden Pakete ermöglichen die Programmierung eines AVR in C und C++.

  • avr-libc (universe)

  • binutils-avr (universe)

  • gcc-avr (universe)

  • avrdude (universe)

  • make (main bzw. devel)

Befehl zum Installieren der Pakete:

sudo apt-get install avr-libc binutils-avr gcc-avr avrdude make 

Oder mit apturl installieren, Link: apt://avr-libc,binutils-avr,gcc-avr,avrdude,make

Experten-Info:

Genau genommen kann man mit Hilfe dieser Pakete den AVR ebenfalls in Assembler programmieren, dies ist aber etwas umständlich. Unter Ubuntu verfügbare AVR-Assembler lassen sich leicht mit

apt-cache search avr  

auffinden.

Nach der Installation startet man Geany und speichert eine leere Datei. Dazu erstellt man einen Projektordner und speichert darin die Datei als main.c. Der Pfad zur Datei könnte dann beispielsweise so lauten: ~/Programmierung/AVRs/AVRTutorial/main.c

Geany_Einstellungen.png

Jetzt müssen mikrocontrollerspezifische Skripte eingebunden werden. Dazu geht man im Menü auf "Erstellen → Kommandos zu erstellen Konfigurieren". Jetzt kann man die Kommandos für eine C-Datei (desshalb wurde im ersten Schritt die Datei mit der Endung .c gespeichert) anpassen. Die bisherigen Einstellungen werden durch folgende ersetzt:

  • "Kompilieren": make all. "Arbeitsverzeichnis": %d

  • "Erstellen": make program, "Arbeitsverzeichnis": %d

Anschließend muss man in Geany eine weitere Datei erstellen, die das mikrocontrollerspezifische Skript darstellt. Es heißt makefile (siehe auch Makefile) und wird im selben Ordner wie main.c gespeichert. Inhalt ist folgender (Kopie aus WinAVR-IDE von 2004):

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# Hey Emacs, this is a -*- makefile -*-
#
# WinAVR makefile written by Eric B. Weddington, Jörg Wunsch, et al.
# Released to the Public Domain
# Please read the make user manual!
#
# Additional material for this makefile was submitted by:
#  Tim Henigan
#  Peter Fleury
#  Reiner Patommel
#  Sander Pool
#  Frederik Rouleau
#  Markus Pfaff
#
# On command line:
#
# make all = Make software.
#
# make clean = Clean out built project files.
#
# make coff = Convert ELF to AVR COFF (for use with AVR Studio 3.x or VMLAB).
#
# make extcoff = Convert ELF to AVR Extended COFF (for use with AVR Studio
#                4.07 or greater).
#
# make program = Download the hex file to the device, using avrdude.  Please
#                customize the avrdude settings below first!
#
# make filename.s = Just compile filename.c into the assembler code only
#
# To rebuild project do "make clean" then "make all".
#

# mth 2004/09 
# Differences from WinAVR 20040720 sample:
# - DEPFLAGS according to Eric Weddingtion's fix (avrfreaks/gcc-forum)
# - F_OSC Define in CFLAGS and AFLAGS


# MCU name
MCU = atmega16

# Main Oscillator Frequency
# This is only used to define F_OSC in all assembler and c-sources.
F_OSC = 3686400

# Output format. (can be srec, ihex, binary)
FORMAT = ihex

# Target file name (without extension).
TARGET = main


# List C source files here. (C dependencies are automatically generated.)
SRC = $(TARGET).c


# List Assembler source files here.
# Make them always end in a capital .S.  Files ending in a lowercase .s
# will not be considered source files but generated files (assembler
# output from the compiler), and will be deleted upon "make clean"!
# Even though the DOS/Win* filesystem matches both .s and .S the same,
# it will preserve the spelling of the filenames, and gcc itself does
# care about how the name is spelled on its command-line.
ASRC = 



# Optimization level, can be [0, 1, 2, 3, s]. 
# 0 = turn off optimization. s = optimize for size.
# (Note: 3 is not always the best optimization level. See avr-libc FAQ.)
OPT = s

# Debugging format.
# Native formats for AVR-GCC's -g are stabs [default], or dwarf-2.
# AVR (extended) COFF requires stabs, plus an avr-objcopy run.
#DEBUG = stabs
DEBUG = dwarf-2

# List any extra directories to look for include files here.
#     Each directory must be seperated by a space.
EXTRAINCDIRS = 


# Compiler flag to set the C Standard level.
# c89   - "ANSI" C
# gnu89 - c89 plus GCC extensions
# c99   - ISO C99 standard (not yet fully implemented)
# gnu99 - c99 plus GCC extensions
CSTANDARD = -std=gnu99

# Place -D or -U options here
CDEFS =

# Place -I options here
CINCS =


# Compiler flags.
#  -g*:          generate debugging information
#  -O*:          optimization level
#  -f...:        tuning, see GCC manual and avr-libc documentation
#  -Wall...:     warning level
#  -Wa,...:      tell GCC to pass this to the assembler.
#    -adhlns...: create assembler listing
CFLAGS = -g$(DEBUG)
CFLAGS += $(CDEFS) $(CINCS)
CFLAGS += -O$(OPT)
CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
CFLAGS += -Wall -Wstrict-prototypes
CFLAGS += -Wa,-adhlns=$(<:.c=.lst)
CFLAGS += $(patsubst %,-I%,$(EXTRAINCDIRS))
CFLAGS += $(CSTANDARD)
CFLAGS += -DF_OSC=$(F_OSC)



# Assembler flags.
#  -Wa,...:   tell GCC to pass this to the assembler.
#  -ahlms:    create listing
#  -gstabs:   have the assembler create line number information; note that
#             for use in COFF files, additional information about filenames
#             and function names needs to be present in the assembler source
#             files -- see avr-libc docs [FIXME: not yet described there]
ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs 
ASFLAGS += -DF_OSC=$(F_OSC)


#Additional libraries.

# Minimalistic printf version
PRINTF_LIB_MIN = -Wl,-u,vfprintf -lprintf_min

# Floating point printf version (requires MATH_LIB = -lm below)
PRINTF_LIB_FLOAT = -Wl,-u,vfprintf -lprintf_flt

PRINTF_LIB = 

# Minimalistic scanf version
SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min

# Floating point + %[ scanf version (requires MATH_LIB = -lm below)
SCANF_LIB_FLOAT = -Wl,-u,vfscanf -lscanf_flt

SCANF_LIB = 

MATH_LIB = -lm

# External memory options

# 64 KB of external RAM, starting after internal RAM (ATmega128!),
# used for variables (.data/.bss) and heap (malloc()).
#EXTMEMOPTS = -Wl,-Tdata=0x801100,--defsym=__heap_end=0x80ffff

# 64 KB of external RAM, starting after internal RAM (ATmega128!),
# only used for heap (malloc()).
#EXTMEMOPTS = -Wl,--defsym=__heap_start=0x801100,--defsym=__heap_end=0x80ffff

EXTMEMOPTS =

# Linker flags.
#  -Wl,...:     tell GCC to pass this to linker.
#    -Map:      create map file
#    --cref:    add cross reference to  map file
LDFLAGS = -Wl,-Map=$(TARGET).map,--cref
LDFLAGS += $(EXTMEMOPTS)
LDFLAGS += $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB)




# Programming support using avrdude. Settings and variables.

# Programming hardware: alf avr910 avrisp bascom bsd 
# dt006 pavr picoweb pony-stk200 sp12 stk200 stk500
#
# Type: avrdude -c ?
# to get a full listing.
#
AVRDUDE_PROGRAMMER = stk500

# com1 = serial port. Use lpt1 to connect to parallel port.
AVRDUDE_PORT = usb    # programmer connected to serial device

AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex
#AVRDUDE_WRITE_EEPROM = -U eeprom:w:$(TARGET).eep


# Uncomment the following if you want avrdude's erase cycle counter.
# Note that this counter needs to be initialized first using -Yn,
# see avrdude manual.
#AVRDUDE_ERASE_COUNTER = -y

# Uncomment the following if you do /not/ wish a verification to be
# performed after programming the device.
#AVRDUDE_NO_VERIFY = -V

# Increase verbosity level.  Please use this when submitting bug
# reports about avrdude. See <http://savannah.nongnu.org/projects/avrdude> 
# to submit bug reports.
#AVRDUDE_VERBOSE = -v -v

AVRDUDE_FLAGS = -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER)
AVRDUDE_FLAGS += $(AVRDUDE_NO_VERIFY)
AVRDUDE_FLAGS += $(AVRDUDE_VERBOSE)
AVRDUDE_FLAGS += $(AVRDUDE_ERASE_COUNTER)



# ---------------------------------------------------------------------------

# Define directories, if needed.
DIRAVR = c:/winavr
DIRAVRBIN = $(DIRAVR)/bin
DIRAVRUTILS = $(DIRAVR)/utils/bin
DIRINC = .
DIRLIB = $(DIRAVR)/avr/lib


# Define programs and commands.
SHELL = sh
CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
SIZE = avr-size
NM = avr-nm
AVRDUDE = avrdude
REMOVE = rm -f
COPY = cp




# Define Messages
# English
MSG_ERRORS_NONE = Errors: none
MSG_BEGIN = -------- begin --------
MSG_END = --------  end  --------
MSG_SIZE_BEFORE = Size before: 
MSG_SIZE_AFTER = Size after:
MSG_COFF = Converting to AVR COFF:
MSG_EXTENDED_COFF = Converting to AVR Extended COFF:
MSG_FLASH = Creating load file for Flash:
MSG_EEPROM = Creating load file for EEPROM:
MSG_EXTENDED_LISTING = Creating Extended Listing:
MSG_SYMBOL_TABLE = Creating Symbol Table:
MSG_LINKING = Linking:
MSG_COMPILING = Compiling:
MSG_ASSEMBLING = Assembling:
MSG_CLEANING = Cleaning project:




# Define all object files.
OBJ = $(SRC:.c=.o) $(ASRC:.S=.o) 

# Define all listing files.
LST = $(ASRC:.S=.lst) $(SRC:.c=.lst)


# Compiler flags to generate dependency files.
### GENDEPFLAGS = -Wp,-M,-MP,-MT,$(*F).o,-MF,.dep/$(@F).d
GENDEPFLAGS = -MD -MP -MF .dep/$(@F).d

# Combine all necessary flags and optional flags.
# Add target processor to flags.
ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) $(GENDEPFLAGS)
ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS)





# Default target.
all: begin gccversion sizebefore build sizeafter finished end

build: elf hex eep lss sym

elf: $(TARGET).elf
hex: $(TARGET).hex
eep: $(TARGET).eep
lss: $(TARGET).lss 
sym: $(TARGET).sym



# Eye candy.
# AVR Studio 3.x does not check make's exit code but relies on
# the following magic strings to be generated by the compile job.
begin:
	@echo
	@echo $(MSG_BEGIN)

finished:
	@echo $(MSG_ERRORS_NONE)

end:
	@echo $(MSG_END)
	@echo


# Display size of file.
HEXSIZE = $(SIZE) --target=$(FORMAT) $(TARGET).hex
ELFSIZE = $(SIZE) -A $(TARGET).elf
sizebefore:
	@if [ -f $(TARGET).elf ]; then echo; echo $(MSG_SIZE_BEFORE); $(ELFSIZE); echo; fi

sizeafter:
	@if [ -f $(TARGET).elf ]; then echo; echo $(MSG_SIZE_AFTER); $(ELFSIZE); echo; fi



# Display compiler version information.
gccversion : 
	@$(CC) --version



# Program the device.  
program: $(TARGET).hex $(TARGET).eep
	$(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM)




# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB.
COFFCONVERT=$(OBJCOPY) --debugging \
--change-section-address .data-0x800000 \
--change-section-address .bss-0x800000 \
--change-section-address .noinit-0x800000 \
--change-section-address .eeprom-0x810000 


coff: $(TARGET).elf
	@echo
	@echo $(MSG_COFF) $(TARGET).cof
	$(COFFCONVERT) -O coff-avr $< $(TARGET).cof


extcoff: $(TARGET).elf
	@echo
	@echo $(MSG_EXTENDED_COFF) $(TARGET).cof
	$(COFFCONVERT) -O coff-ext-avr $< $(TARGET).cof



# Create final output files (.hex, .eep) from ELF output file.
%.hex: %.elf
	@echo
	@echo $(MSG_FLASH) $@
	$(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@

%.eep: %.elf
	@echo
	@echo $(MSG_EEPROM) $@
	-$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
	--change-section-lma .eeprom=0 -O $(FORMAT) $< $@

# Create extended listing file from ELF output file.
%.lss: %.elf
	@echo
	@echo $(MSG_EXTENDED_LISTING) $@
	$(OBJDUMP) -h -S $< > $@

# Create a symbol table from ELF output file.
%.sym: %.elf
	@echo
	@echo $(MSG_SYMBOL_TABLE) $@
	$(NM) -n $< > $@



# Link: create ELF output file from object files.
.SECONDARY : $(TARGET).elf
.PRECIOUS : $(OBJ)
%.elf: $(OBJ)
	@echo
	@echo $(MSG_LINKING) $@
	$(CC) $(ALL_CFLAGS) $(OBJ) --output $@ $(LDFLAGS)


# Compile: create object files from C source files.
%.o : %.c
	@echo
	@echo $(MSG_COMPILING) $<
	$(CC) -c $(ALL_CFLAGS) $< -o $@ 


# Compile: create assembler files from C source files.
%.s : %.c
	$(CC) -S $(ALL_CFLAGS) $< -o $@


# Assemble: create object files from assembler source files.
%.o : %.S
	@echo
	@echo $(MSG_ASSEMBLING) $<
	$(CC) -c $(ALL_ASFLAGS) $< -o $@



# Target: clean project.
clean: begin clean_list finished end

clean_list :
	@echo
	@echo $(MSG_CLEANING)
	$(REMOVE) $(TARGET).hex
	$(REMOVE) $(TARGET).eep
	$(REMOVE) $(TARGET).obj
	$(REMOVE) $(TARGET).cof
	$(REMOVE) $(TARGET).elf
	$(REMOVE) $(TARGET).map
	$(REMOVE) $(TARGET).obj
	$(REMOVE) $(TARGET).a90
	$(REMOVE) $(TARGET).sym
	$(REMOVE) $(TARGET).lnk
	$(REMOVE) $(TARGET).lss
	$(REMOVE) $(OBJ)
	$(REMOVE) $(LST)
	$(REMOVE) $(SRC:.c=.s)
	$(REMOVE) $(SRC:.c=.d)
	$(REMOVE) .dep/*



# Include the dependency files.
-include $(shell mkdir .dep 2>/dev/null) $(wildcard .dep/*)


# Listing of phony targets.
.PHONY : all begin finish end sizebefore sizeafter gccversion \
build elf hex eep lss sym coff extcoff \
clean clean_list program

Zusätzlich muss das Makefile an das eigene Projekt angepasst werden:

1
2
# MCU name
MCU = atmega16

Hier muss der verwendete Mikrocontroller eingetragen werden. In unserem Beispiel der attiny13a (wurde gcc-avr von den Installationquellen von Ubuntu 18.04 installiert, so steht nur attiny13 zur Verfügung).

1
2
3
# Main Oscillator Frequency
# This is only used to define F_OSC in all assembler and c-sources.
F_OSC = 3686400

Hier wird die verwendete Taktrate angegeben. Wird diese falsch angegeben, stimmen Timerzeiten nicht und Schnittstellen wie UART funktionieren nicht mehr. Kauft man einen neuen AVR, so ist der intern erzeugte Standardtakt meist 1 MHz (beim ATtiny13A 1,2 MHz).

Etwas weiter unten (etwas Mitte des Skripts) findet man dann folgendes:

1
2
3
4
5
6
7
8
9
# Programming support using avrdude. Settings and variables.

# Programming hardware: alf avr910 avrisp bascom bsd 
# dt006 pavr picoweb pony-stk200 sp12 stk200 stk500
#
# Type: avrdude -c ?
# to get a full listing.
#
AVRDUDE_PROGRAMMER = stk500

Hier wird der verwendete Programmer eingetragen, z.B. USBasp.

1
2
# com1 = serial port. Use lpt1 to connect to parallel port.
AVRDUDE_PORT = usb    # programmer connected to serial device

Hier wird festgelegt, wie sich avrdude mit dem Programmer verbindet. Im Fall des USBasp ist usb die richtige Angabe.

Experten-Info:

Wenn das Programmiergerät beim Programmiervorgang nicht antwortet, dann kann man die entsprechende Zeile im Makefile um den Parameter -B und den Wert 60 erweitern:

AVRDUDE_FLAGS = -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) -B 60

Der Abschnitt Fehlerbehandlung gibt zudem weitere Tipps.

Hinweis:

Die nachfolgende udev-Regel ist unter Ubuntu 18.04 Bionic Beaver und unter Ubuntu 20.04 Focal Fossa nicht notwendig.

Des Weiteren hat der normale Benutzer unter Ubuntu meist nicht die Rechte, den Programmer zu verwenden. Im einfachsten Fall kann man mit Hilfe einer udev-Regel dem Benutzer diese Rechte verschaffen. Dazu muss folgender Text nach /etc/udev/rules.d/99_usbprog.rules kopiert werden:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Atmel AVR ISP mkII
SUBSYSTEM=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2104", GROUP="plugdev", MODE="0660" 

# usbprog bootloader
ATTRS{idVendor}=="1781", ATTRS{idProduct}=="0c62", GROUP="plugdev", MODE="0660"
 
# USBasp programmer
ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05dc", GROUP="plugdev", MODE="0660"
 
# USBtiny programmer
ATTRS{idVendor}=="1781", ATTRS{idProduct}=="0c9f", GROUP="plugdev", MODE="0660"

Damit sollten die meist benutzen Programmer abgedeckt sein. Sollte man einen anderen Programmer haben, so kann man ihn nach diesem Schema hinzufügen. Die Nummer idVendor und idProdukt findet man über das Terminal [2] mit Hilfe

lsusb 

während der Programmer angesteckt ist. Nach dem die Datei angelegt wurde, muss der Programmer ab- und wieder angesteckt werden, damit die Regel übernommen wird.

Schreiben eines Programms

In diesem Schritt wird dem Mikrocontroller gesagt, was er zu tun hat. Dazu müssen die entsprechenden Anweisungen in die eben gespeicherte main.c geschrieben werden. Ein Grundgerüst für diesen Quelltext sieht folgendermaßen aus:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <avr/io.h>			// Einbinden von Einstellungen/Definitionen/usw. für den Mikrocontroller

int main (void)				// Hauptprogramm, hier startet der Mikrocontroller
{
	// Initialisierung
	while(1)			// Nie endende Hauptschleife (Endlosschleife)
	{
	// Einlesen
	// Verarbeiten
	// Ausgeben
	}				// Ende der Endlosschleife (Es wird wieder zu "while(1)" gesprungen)
	return 0;			// Wird nie erreicht, aber ohne schreibt der GCC eine Warnung
}					// Ende des Hauptprogramms

Dieses Programm könnte man jetzt auch schon übersetzen und auf einen Mikrocontroller übertragen. Allerdings wird man keinen Unterschied zu einem unprogrammierten Mikrocontroller erkennen, da das Programm bis jetzt noch nichts macht, außer in einer Endlosschleife zu rotieren, was man von Außen nicht sehen kann.

Nun kann man das Programm beispielsweise folgendermaßen erweitern:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <avr/io.h>			// Einbinden von Einstellungen/Definitionen/usw. für den Mikrocontroller
#define F_CPU 1200000UL			// Frequenz des Microcontrollers fuer delay.h festlegen
#include <util/delay.h>			// Einbinden der _delay_ms()-Funktion um Wartezeiten zu erzeugen

int main (void)				// Hauptprogramm, hier startet der Mikrocontroller
{
	unsigned char zwischenspeicher;		// Initialisierung
	DDRB = 0b00000001;		
	while(1)			// Nie endende Hauptschleife (Endlosschleife)
	{
		zwischenspeicher = PORTB;				// Einlesen
		zwischenspeicher = zwischenspeicher ^ 0b00000001;	// Verarbeiten
		PORTB = zwischenspeicher;				// Ausgeben
		_delay_ms(500);						// Wartezeit von 500ms
	}				// Ende der Endlosschleife (Es wird wieder zu "while(1)" gesprungen)
	return 0;			// Wird nie erreicht, aber ohne schreibt der GCC eine Warnung
}					// Ende des Hauptprogramms

Eine kurze Erklärung zum Quellcode:

  1. Initialisierung: Zuerst wird die Variable zwischenspeicher angelegt, diese ist 8 Bit breit und kann somit eine Ganzzahl von 0-255 aufnehmen. Eine Zeile weiter wird ein Ausgang erstellt. Bei AVRs sind die Pins in Ports zu jeweils 8 Pins zusammengefasst. Jeder Port hat somit maximal so viele Pins, wie ein Byte Bits hat. Damit steht jedes Bit im Byte (0b00000001) für einen Pin des Port. Beim ATtiny13A hat der Port B (DDRB) jedoch nur 5 Pins (in Sonderfällen 6), die oberen Bits werden daher nicht berücksichtigt. Man kann jetzt sehen, dass die sieben höherwertigen Pins eine 0 und der niederwertigste Pins eine 1 in deren DDR-Register geschrieben bekommen. Das hat zur Folge, dass der niederwertigste PortB-Pin (PB0) zu einem Ausgang wird. Das heißt 0 = Eingang und 1 = Ausgang.

  2. Einlesen: Nun folgt das Einlesen des Status eines Ausgangs. Da man nicht einen Pin alleine auslesen kann, sondern immer nur einen ganzen Port, wird der ganze 8 Pins breite Port in die ebenfalls 8 Bit breite Variable zwischenspeicher kopiert. Dort ist nun abgelegt, welcher Ausgang high (1) und welcher low (0) ist.

  3. Verarbeiten: Als nächstes soll der Status des niederwertigsten Bits von zwischenspeicher umgekehrt werden. Dies ist mit der logischen Verknüpfung XOR (Kontravalenz) am einfachsten. Alle Bits, die in dem rechten Byte 1 sind werden umgekehrt.

  4. Ausgeben: Da das niederwertigste Bit jetzt umgekehrt ist, kann der Inhalt von zwischenspeicher wieder ausgegeben werden.

  5. Wartezeit: Ein Mikrocontroller arbeitet sehr schnell, deshalb wurde eine Wartezeit eingebaut, damit man den Signalwechsel nachverfolgen kann. Ohne diese Wartezeit würde das Programm so schnell ablaufen, so dass ein Mensch diesen nicht mehr wahrnehmen kann.

Anschließend kommt der Mikrocontroller bei der Ausführung am Ende der while-Schleife an. Das hat zur Folge, dass er wieder zu while(1) springt und die Bedingung überprüft. Da die Bedingung immer noch erfüllt ist, wird das Programm dann wieder ab dieser Stelle ausgeführt und der Ablauf beginnt so von vorn.

Das Programm macht nun nichts anderes, als ständig Pin 0 des Ports B (PB0) abwechselnd auf High und Low zu setzen, d.h. abwechselnd liegen dort 0V und 5V (bei einem 5V Controller) an. Jetzt kann man dort eine Leuchtdiode mit passendem Widerstand anschließen und diese leuchtet dann ein Mal pro Sekunde auf.

Dies soll nur ein kurzes einführendes Beispiel darstellen. Wie das Programmieren von AVRs genau funktioniert, kann man zum Beispiel der Anleitung AVR-GCC-Tutorial 🇩🇪 entnehmen. Dort wird zwar nur auf die AVRs an sich und nicht auf die Grundelemente von C eingegangen, aber es werden C-Tutorials verlinkt.

Übersetzen des Programms

Da der AVR nur Maschinensprache und kein C versteht, muss der soeben erstellte Quelltext in Maschinensprache übersetzt werden. Dazu wird das weiter oben erwähnte Programm gcc-avr und dessen Zusätze avr-libc, sowie binutils-avr benötigt. Um den Vorgang des Übersetzens zu vereinfachen zudem das Programm make.

Um den Quelltext jetzt zu übersetzen, muss man in den Projektordner wechseln und dort ein make all im Terminal [2] ausführen. Das Programm make sieht dann im Makefile nach, welche Aufgaben bei Verwendung des Parameters all vorgesehen sind. Unter anderem ist dies die Übersetzung des Programms durch AVR-GCC in Maschinensprache.

Anstatt den Befehl im Terminal auszuführen, kann dieser in die Entwicklungsumgebung bzw. den Editor eingebunden werden. Ist das wie oben am Beispiel Geany schon geschehen, so muss man nur noch die entsprechende Schaltfläche drücken und alles läuft automatisch ab. Im Fall von Geany ist das die Schaltfläche "Kompilieren" Geany_kompilierenButton.png. Alternativ funktioniert auch das Tastenkürzel F8 .

Wenn der Kompiler AVR-GCC das Programm ohne Probleme übersetzen kann, so wird das im Compiler-Fenster von Geany angezeigt und im Projektordner wird eine Datei mit Namen main.hex angelegt. Darin befindet sich das Programm in Maschinensprache. Kann der AVR-GCC einen Befehl nicht verstehen, weil man sich beispielsweise vertippt hat, so wird eine Fehlermeldung im Compiler-Fenster ausgegeben.

Programm aufspielen

Da das Programm jetzt in Maschinensprache vorhanden ist, kann es auf den Mikrocontroller übertragen werden, sofern die folgenden Voraussetzungen erfüllt sind:

Sofern diese Bedingungen erfüllt sind und alle Komponenten korrekt miteinander verbunden sind, so kann im Projektordner im Terminal [2] make program ausgeführt werden. Natürlich kann man sich alternativ die Entwicklungsumgebung entsprechend einrichten. Ist das wie oben am Beispiel Geany gezeigt, schon geschehen, so muss man nur noch die entsprechende Schaltfläche drücken. Im Fall von Geany ist das die Schalftfläche "Erstellen" Geany_erstellenButton.png. Alternativ funktioniert dies ebenfalls über F9 .

Hiermit wird wiederum avrdude aufgerufen, welches dann über den Programmieradapter eine Verbindung zum Mikrocontroller aufbaut und das Programm in Maschinensprache überträgt. Der Controller speichert dieses in seinem Flash-Speicher. Sobald avrdude das Programm komplett übertragen hat (es kann einige Sekunden dauern), beginnt der Mikrocontroller sofort damit, das Programm auszuführen.

Debugmöglichkeiten

Mit einem Debugger können die einzelnen Programmanweisungen Schritt für Schritt nachvollzogen werden, während das Programm auf dem Mikrocontroller läuft. Dies erleichtert das Auffinden von Fehlern im Programm. Man kann dies beispielsweise folgendermaßen tun:

  • An einem bestimmten Punkt im Programm eine LED blinken lassen, um zu sehen, dass der Mikrocontroller diesen Punkt soeben erreicht hat.

  • Mit Hilfe einer LED kann man Registerinhalte per Blinkzeichen ausgeben; zum Beispiel jedes Bit eines Bytes einzeln, und zwar jeweils ein langes Aufblinken für eine 1 und ein kurzes Aufblinken für eine 0.

  • An mehreren stellen im Programm Texte auf einem LC-Display ausgeben oder mithilfe des UART an den Computer senden.

  • Die für Debugzwecke vorgesehene JTAG-Schnittstelle des Mikrocontrollers verwenden. Dazu benötigt man zusätzliche Hardware und zum Einstieg in die Mikrocontrollerwelt reichen die beiden erstgenannten Debugmöglichkeiten aus.

Fehlerbehandlung

Programmer wird nicht erkannt

Die Fehlermeldung dazu kann zum Beispiel folgendermaßen aussehen:

avrdude: error: could not find USB device "USBasp" with vid=0x16c0 pid=0x5dc

Dies kann folgende Ursachen haben:

  • Der Benutzer, der den Mikrocontroller programmieren möchte, hat nicht die Rechte, auf den jeweiligen Programmieradapter zuzugreifen. Dies kann man überprüfen, indem man das "make program" als Root ausführt. Wird der Programmer dann erkannt, liegt ein Rechte-Problem vor, das sich üblicherweise mit einer udev-Regel lösen lässt. Die Lösung ist unter Einrichten einer Entwicklungsumgebung beschrieben.

  • Wenn man einen Programmer-Bausatz verwendet, hat man eventuell Fehler beim Aufbauen bzw. Löten des Programmers gemacht.

AVR wird nicht erkannt

Eine Fehlermeldung, dass der AVR nicht erkannt wurde, kann beispielsweise so aussehen:

initialization failed, rc=-1 
Double check connections and try again, or use -F to override this check 

Die häufigsten Ursachen hierfür sind:

  • Der Mikrocontroller hat keine Stromversorgung (vergessen einzuschalten o.ä.).

  • Eine der vier Programmierleitungen (MOSI, MISO, SCK, RESET) ist nicht korrekt verbunden (evtl. Wackelkontakt).

  • Der Mikrocontroller läuft aus irgendeinem Grund mit langsamem Takt und kann deswegen nur mit sehr langsamer Geschwindigkeit programmiert werden. Hier hilft die avrdude-Option -B 60 oder -B 600

  • Die sogenannten Fuses 🇩🇪 (Grundeinstellung des AVR) wurden falsch eingestellt. Wenn man den AVR mit den Fuses beispielsweise auf externe Taktversorgung mittels Rechteck umstellt, funktioniert auch kein Quarz mehr als Taktquelle. Dann muss man an den XTAL1-Pin ein TTL-Rechteck mit einer Frequenz von etwa 1–10 Mhz anlegen (kann man mit einem zweiten AVR erzeugen) und die Fuses des Mikrocontrollers wieder auf interne Taktquelle oder Quarz (sofern einer angeschlossen ist) umstellen.

Diese Revision wurde am 12. April 2021 14:52 von mubuntuHH erstellt.
Die folgenden Schlagworte wurden dem Artikel zugewiesen: Bildung, Programmierung