Die AVR-Mikrocontroller von Atmel sind wegen ihrer bersichtlichen internen Struktur, der InSystem-Programmierbarkeit, und der Vielzahl von kostenlosen Programmen zur
Softwareentwicklung (Assembler, Compiler) beliebt. Diese Eigenschaften und der Umstand,
dass viele Typen in einfach handhabbaren DIL-Gehusen (DIP) verfgbar sind, machen den
AVR zum idealen Mikrocontroller fr Anfnger.
ber die Bedeutung des Namens "AVR" gibt es verschiedene Ansichten; manche meinen er sei
eine Abkrzung fr Advanced Virtual RISC, andere vermuten dass der Name aus den
Anfangsbuchstaben der Namen der Entwickler (Alf Egin Bogen und Vegard Wollan RISC)
zusammengesetzt wurde. Laut Atmel ist der Name bedeutungslos.
[Bearbeiten]Architektur
Die Architektur ist eine 8-Bit-Harvard-Architektur, das heit, es gibt getrennte Busse zum
Programmspeicher (Flash-ROM, dieser ist 16 bit breit) und Schreib-Lese-Speicher (RAM).
Programmcode kann auschlielich aus dem Programmspeicher ausgefhrt werden. Weiterhin
sind die Adressrume getrennt (d.h. die erste Speicherstelle im Flash-Speicher hat die gleiche
Adresse (0) wie die erste Speicherstelle im RAM). Bei der Programmierung in Assembler und
einigen C-Compilern bedeutet dies, dass sich Konstanten aus dem ROM nicht mit dem gleichen
Code laden lassen wie Daten aus dem RAM. Abgesehen davon ist der Aufbau des Controllers
recht bersichtlich und birgt wenige Fallstricke.
Speicher
o
1-256 kB Flash-ROM
0-4 kB EEPROM
0-16 kB RAM
Peripherie: AD-Wandler 10 bit, 8- und 16-Bit-Timer mit PWM, SPI, IC (TWI), UART,
Analog-Komparator, Watchdog
[Bearbeiten]Software
Code::Blocks: Freie Entwicklungsumgebung (Win, Linux, Mac), die auch fr AVRProjekte Untersttzung anbietet
mkAvrCalculator: User friendly fuse bits calculator and GUI for avrdude
[Bearbeiten]Boards
& Starterkits
Siehe dazu auch die Artikel in der Kategorie AVR-Boards und den Artikel zur AVR
Programmierung.
diverse im Mikrocontroller.net-Shop
STK200
STK500
STK600
AVR Dragon
AVR Butterfly
AVR Raven
AVR-ISP-Stick
ISP-Stick
AVR JTAG-ICE
RN-Control
C-Control PRO
myAVR Board
Rumpus von lochraster.org ist ein gnstiges und gut dokumentiertes Starterkit mit
Atmega 168
Modularis - AVR Mikrocontroller-Boards (z.T. mit Zusatz-Speicher und USB) die ber
Flachbandkabel erweitert werden knnen. Es gibt bis jetzt Zubehr-Module mit Taster,
Motor H-Brcke, XBee und Winkelsensor.
AVR Mikrocontroller Lehrbuch (R. Walter, 3. Auflage 2009) und Bauteilesatz incl.
Leiterplatte; www.b-redemann.de
Atmegaboard Eagle Daten fr ein Testboard zur Nutzung 2er Atmegas gleichzeitig.
Weitere Adapterplatinen sowie Source Code sind ebenfalls verfgbar.
tinyUSBboard - Ein sehr sehr preiswertes, Arduino und BASCOM kompatibles Board
mit onboard USB Interface
[Bearbeiten]Projekte
Siehe dazu auch die Artikel in der Kategorie AVR-Projekte.
Digitaler Funktionsgenerator
Schrittmotor-Controller (Stepper)
Fahrradcomputer
AVR RFM12
ATtiny-Mikrokontroller fr Schulbedarf
[Bearbeiten]Tutorials
AVR-Tutorial
AVR-GCC-Tutorial
http://www.avr-asm-tutorial.net
weigu.lu/a: Gratis Assembler Kurs (pdf). Mehrere hundert Seiten mit vielen neuen
Grafiken. Besonders zum Selbststudium geeignet. Es existiert auch
ein Entwicklungsboard zum Kurs.
[Bearbeiten]Literatur
F.Schffer AVR: Hardware und C-Programmierung in der Praxis Elektor 2008 ISBN
3895762008 Webseite des Autors, Codebeispiele und Leseprobe
G.Schmitt Mikrocomputertechnik mit Controllern der Atmel AVR-RISCFamilie...Oldenbourg 4.Aufl.2008 ISBN 3486587900 ISBN 3486580167 (2006) ISBN
3486577174 (2005) Verlags-Webseite mit Buchauszgen
W.Trampert Messen,Steuern und Regeln mit AVR Mikrocontrollern Franzis 2004 ISBN
3772342981
[Bearbeiten]Tipps
& Hinweise
(Little) Endianess Guide for Atmel AVR bersicht ber die Endianess der AVR und
AVR32
Fuse-Bits - Das Setzen der Fuse-Bits ist ein berchtigter Fallstrick bei den AVRs; vor
dem Rumspielen damit unbedingt diese Hinweise lesen!
[Bearbeiten]Weblinks
Weitere Verweise (Links) auf externe Informationen und Projekte finden sich in
der Linksammlung.
[Bearbeiten]Anmerkungen
Es gibt nur wenige Typen mit D/A-Wandler (z.B. AT90PWM2); hierfr benutze man PWM oder
externe Bausteine.
Die Takterzeugung ist bei AVRs recht einfach gehalten. So gibt es bei den meisten Modellen
keine internen PLLs um krumme Prozessor- oder Peripherietaktfrequenzen zu erzeugen, noch
ist der Peripherie-Takt vom Prozessortakt abkoppelbar. Einige AVR verfgen ber eine PLL, um
damit z.B. einen Timer mit Frequenzen ber der Systemfrequenz zu takten oder hhere
Systemfrequenz aus niederfrequenteren Taktquellen zu erzeugen (vgl. u.a. Datenblatt
ATtiny861). Die Baudrate serieller Schnittstellen lsst sich nicht gebrochen einstellen, so dass
gegebenenfalls ein zur Baudrate passender Quarz oder Resonator zu verwenden ist.
Fr die serielle Programmierung des Flash-Speichers sind 4 Datenleitungen erforderlich und die
Taktversorgung muss sicher gestellt sein. Es ist darauf zu achten, dass bei Einstellung der
Taktquelle (Fuses) auch die vorhandene Taktquelle ausgewhlt wird. Fr die HochvoltProgrammierung (so genannt wegen 12 V am RESET-Anschluss) werden je nach Chip sehr
viele Leitungen bentigt. Einige Modelle verfgen ber eine Debugwire-Schnittstelle, fr die im
Betrieb zwei Leitungen ausreichen.
Nicht zu verwechseln ist die 8-bit-AVR-Serie mit AVR32. Letztere ist eine 32-bit-Architektur mit
recht viel hnlichkeit zu Controllern auf Basis eines ARM-Cores. Controller der ATxmega-Serie
verfgen ber mehr Funktionen als die "traditionellen" AVR (z.B. DMA- und Eventsystem, 12Bit
A-D-Wandler). ATxmega sind jedoch fr 3,3V-Betrieb ausgelegt und ausschlielich in SMDBauform erhltlich.
Kategorien: Mikrocontroller | AVR
AVR-Tutorial
Ausser diesem Tutorial gibt es noch das AVR-GCC-Tutorial sowie die Artikel in der Kategorie:avr-gcc Tutorial.
Inhaltsverzeichnis
[Verbergen]
1 Aufbau des Tutorials
2 Was ist ein Mikrocontroller?
3 Wozu ist ein Mikrocontroller gut?
4 Welchen Mikrocontroller soll ich verwenden?
5 In welcher Sprache soll programmiert werden?
o
5.1 Vorbemerkung
5.2 Assembler
5.3 Basic
5.4 C
6 Mit Betriebssystem oder ohne ?
6.1 Mit OS
6.2 Ohne OS
7 Weitere Informationen
Bentigte Ausrstung: Welche Hard- und Software brauche ich, um AVR-Mikrocontroller zu programmieren?
I/O-Grundlagen: Wie kann ich Taster und LEDs an einen AVR anschlieen und benutzen?
Der Stack: Was ist der Stack und wie funktionieren Unterprogrammaufrufe?
Interrupts: Was sind Interrupts und wie kann ich sie verwenden?
Der UART: Wie kann ich Daten zwischen einem Mikrocontroller und einem PC austauschen?
Flash, EEPROM, RAM: Die verschiedenen Speicherarten des AVR und ihre Anwendung.
Der ADC: Die Brcke von der analogen zur digitalen Welt.
Ladegerte
Motorsteuerungen
Roboter
Temperaturregler
MP3-Player
Schaltuhren
Alarmanlagen
...
Handliche Bauform: Ein Controller mit 20 Pins ist leichter zu handhaben als einer mit 128
Flash-ROM: Der Controller sollte mindestens 1000 mal neu programmiert werden knnen
In-System-Programmierbarkeit (ISP): Man bentigt kein teures Programmiergert und muss den Controller
zur Programmierung nicht aus der Schaltung entfernen
Vorbemerkung
Warum ist dieses Tutorial fr Assembler geschrieben, wo es doch einen kostenlosen C-Compiler (WinAVR, AVR-GCC)
und einen billigen Basic-Compiler gibt?
Assembler ist fr den Einstieg "von der Pike auf" am besten geeignet. Nur wenn man Assembler anwendet, lernt man
den Aufbau eines Mikrocontrollers richtig kennen und kann ihn dadurch besser nutzen; auerdem stt man bei jedem
Compiler irgendwann mal auf Probleme, die sich nur oder besser durch das Verwenden von Assemblercode lsen
lassen und sei es nur, dass man das vom Compiler generierte Assemblerlisting studiert, um zu entscheiden, ob und wie
man eine bestimmte Sequenz im C-Code umschreiben soll, um dem Compiler das Optimieren zu
ermglichen/erleichtern.
Allerdings muss auch erwhnt werden, dass das Programmieren in Assembler besonders fehleranfllig ist und dass es
damit besonders lange dauert, bis das Programm erste Ergebnisse liefert. Genau aus diesem Grund wurden "hhere"
Programmiersprachen erfunden, weil man damit nicht immer wieder "das Rad neu erfinden" muss. Das gilt besonders,
wenn vorbereitete Programmblcke zur Verfgung stehen, die man miteinander kombinieren kann. Auch der
Geschwindigkeitsvorteil ist selten und nur bei kritischen Anwendungen von Bedeutung. Heutige Compiler generieren
zudem oft schnelleren oder kleineren Code als handgeschriebene Assemblerroutinen. Wer regelmig programmieren
und auch lngere Programme schreiben mchte, dem sei deshalb geraten, nach diesem Assembler-Tutorial C zu
lernen, zum Beispiel mit dem AVR-GCC-Tutorial.
Wer C schon kann, fr den bietet es sich an, das Tutorial parallel in C und Assembler abzuarbeiten. Die meisten hier
vorgestellten Assemblerprogramme lassen sich relativ einfach in C umsetzen. Dabei sollte groes Augenmerk darauf
gelegt werden, dass die dem Programm zugrunde liegende Idee verstanden wurde. Nur so ist ein vernnftiges
Umsetzen von Assembler nach C (oder umgekehrt) mglich. Vllig verkehrt wre es, nach sich entsprechenden
'Befehlen' zu suchen und zu glauben, damit htte man dann ein Programm von Assembler nach C bersetzt.
Assembler
Die Vorteile von Assembler wurden bereits genannt:
- direkter Einstieg in die Hardware - keine Abhnhigkeit von Compilern und deren Fehlern, bzw Misinterpretation optimaler Code erzeugbar - sehr schnell in der Ausfhrung - Feintuning der Geschwindigkeitsreserven - kurzer Weg zu
hardwarenahen Funktionen - sehr langer Weg zu komplexeren Funktionen
Basic
Basic bietet den einfachsten Einstieg, wenn man bereits eine hhere Programmiersprache beherrscht und wenig
Kenntnisse ber die Hardware hat und sich zudem auf komplexere Steuerungen ohne optimale Ausschpfung der HWResourcen beschrnkt.
- direkter Einstieg in komplizierte Ablufe - einfacher Einstieg in die Programmiersprache - Abhngigkeit von Compilern
und deren Fehlern - Code ist schnell erzeugbar - sehr langsam in der Ausfhrung - kurzer Weg zu komplexeren
Funktionen - keine hardwarenahen Funktionen verfgbar
C
C bietet den optimalen Kompromiss, da man durch Funktionen und Prozeduren sehr leicht hochsprachliche Strukturen
und Datentypen nutzen kann und dennoch sehr effektiven Code produzieren (lassen) kann. Allerdings ist C strukturell
am schwierigsten zu verstehen.
- schwieriger Einstieg in die Programmiersprache - Abhngigkeit von Compilern und deren Fehlern, allerdings
verifizierbar - Code ist automatisch erzeugbar, manuell aber kompliziert - sehr schnell in der Ausfhrung - akzeptabler
Weg zu komplexeren Funktionen - hardwarenahe Funktionen verfgbar - mit Assembler kombinierbar
Mit OS
Prinzipiell Echtzeitfunktion einfacher mglich, da ein Multitasking die parallele Reaktion des Systems auf
ussere Einflsse erleichtert
die Multitasking-relevanten Teile des Codes sind OS-spezifisch und nicht bertragbar
der gesamte Code ist weniger gut auf andere Controller portierbar
Ohne OS
C ist mit viel weniger overhead belastet, da nur bentigte Funktionen eingebaut werden
Echtzeitfunktion ebenfalls mglich, - muss einerseits genauer betrachtet werden, - ist andererseits effektiver
und besitzt hhere Reserve
Weitere Informationen
Weiterfhrende Informationen u. A. zu den berchtigten Fuse-Bits, zu Programmier-Hard- und Software, dem AVR
Softwarepool und einer Checkliste mit Hinweisen zur Lsung blicher Probleme finden sich im Hauptartikel AVR.
vor zum ersten Kapitel
AVR-Tutorial: Equipment
Inhaltsverzeichnis
[Verbergen]
1 AVR-Tutorial - Bentigte Ausrstung
1.1 Hardware
1.1.1.2 STK500
1.1.1.6 RN-Control
1.1.1.7 Arduino
1.1.1.8 Andere
1.1.2 Selbstbau
werden)
1.1.3.1 Keramikresonator
1.1.3.2 Quarz
1.1.4 Spannungsversorgung
1.1.7 Sonstiges
1.2 Software
1.2.1 Assembler
1.2.2 C
1.2.3 Pascal
1.2.4 Basic
1.2.5 Forth
1.3 Literatur
[Bearbeiten]
[Bearbeiten]Hardware
Ein Mikrocontroller alleine ist noch zu nichts ntzlich. Damit man etwas damit anfangen kann,
braucht man eine Schaltung, in die der Controller eingesetzt wird. Dazu werden bei
Elektronikhndlern Platinen angeboten, die alles ntige (Taster, LEDs, Steckverbinder...)
enthalten. Hufig enthalten diese Platinen nicht nur Platz fr den Mikroprozessor, sondern auch
einen ISP-Programmierer (Nheres dazu spter)
[Bearbeiten]Fertige Evaluations-Boards und Starterkits
[Bearbeiten]AVR
Hinweis: Damit der Programmer mit AS5.x zusammen arbeitet, muss die Firmware aktualisiert
werden: http://www.usbprog.org/index.php/Firmwares (siehe Update Hinweis)
Sehr gut fr dieses Tutorial geeignet ist das AVR-Starterkit aus dem Mikrocontroller.net-Shop.
Das Kit enthlt eine Platine mit dem Controller ATmega8, einen USB-ISP-Programmieradapter
und ein Steckernetzteil. Die im Starterkit enthaltene AVR Entwicklungsplatine fr 28-pol.
AVRs gibt es auch einzeln. Diese enthlt eine Fassung fr den Controller, einen
Spannungswandler, die Beschaltung fr die serielle Schnittstelle und einen Anschluss fr den
Programmieradapter. Die restliche Hardware wie LEDs und Taster kann man sich selber nach
Belieben auf das Lochrasterfeld lten.
[Bearbeiten]STK500
Das STK500 ist das Standard-Board fr AVR Entwicklung, direkt von Atmel. Es enthlt auch
einen ISP-Programmer und ist fertig aufgebaut. Es ist unter Entwicklern sehr beliebt und wird
natrlich von Atmel untersttzt. Es gilt allgemein als gute Investition, wenn man ernsthaft in das
Thema einsteigen mchte.
Das STK500 kostet bei Reichelt ca. 80 Euro (ein geeignetes Netzteil muss zustzlich erworben
werden).
[Bearbeiten]Pollin
Bei Pollin Elektronik gibt es fr ca. 15 Euro ein Evaluations-Board als Bausatz zum Selbstlten.
Im Bausatz sind die Aufbauanleitung, die Platine und Bauteile enthalten. Der/die Mikrocontroller
und eine Stromversorgung mssen separat beschafft werden. Auf dem Board ist ein einfacher
ISP-Programmer (serielles bit-banging) integriert.
Siehe:
http://www.pollin.de
[Bearbeiten]Pollin
Funk-AVR-Evaluationsboard v1.x
Bei diesem Board besteht die Mglichkeit, Funkmodule wie das RFM12, RFM01 oder RFM02
auf dem Board aufzulten.
Siehe:
Pollin Funk-AVR-Evaluationsboard
http://www.pollin.de
[Bearbeiten]Rumpus
Lochraster.org bietet ein Entwicklungsboard namens Rumpus an. Es kommt als Bausatz mit
allen Teilen und Microcontroller (Atmega 168), auf dem Microcontroller ist bereits ein USB
Bootloader installiert so dass man nach dem Zusammenbau sofort starten kann. Das Board
wird direkt ber USB mit Strom versorgt und auch ber USB programmiert, es kann auch selbst
als Programmer fr AVR Microcontroller benutzt werden. Das Board ist mit recht umfangreicher
Peripherie ausgestattet, so das sich von sehr einfachen Anwendungen wie dem Blinken einer
LED bis hin zu komplexen Aufgaben wie senden und empfangen von Infrarot Signalen eine
Vielzahl von Anwendungen realisieren lassen. Mit 45 Euro gehrt es sicher nicht zu den ganz
billigen Einsteigerboards, fr den ambitionierten Amateur bietet die reichhaltige Peripherie den
Vorteil, das Board whrend des gesamten Lernprozesses zu nutzen ohne fr die Realisierung
komplexerer Aufgaben neue Hardware auflten zu mssen. Auch relativiert sich dieser Preis
wieder dadurch, dass kein ISP Programmer bentigt wird. Beim Umstieg auf ein anderes Board,
fr welches man dann einen ISP Programmer bentigt, kann der Rumpus diese Aufgabe
bernehmen anstatt zum alten Eisen geworfen zu werden (s. Infos im Forumbeitrag von
Sebastian Noack).
Weitere Infos unter http://www.lochraster.org/ und http://wiki.lochraster.org/
[Bearbeiten]RN-Control
Die Forengemeinde von Roboternetz hat ebenfalls ein Evaluierungsboard entwickelt, das
mittlerweile sehr ausgereift ist und viele Erweiterungsmglichkeiten bietet.
Siehe:
http://robotikhardware.de/
http://www.roboternetz.de/
[Bearbeiten]Arduino
Die Boards der Arduino-Familie bieten z.B. einen ATmega328P mit 16MHz und lassen sich ber
einen integrierten USB-seriell-Wandler und Bootloader programmieren. Die Ports sind auf
Buchsenleisten herausgefhrt. Arduino-Boards knnen auch unabhngig von der ArduinoEntwicklungsumgebung (Arduino-IDE) als AVR-Entwicklungsboard genutzt werden.
[Bearbeiten]Andere
ATMEL oder ATMEL-kompatible ISP-Adapter benutzt werden, die Pinbelegung aus AVR042 (PDF)
bzw.AVR_In_System_Programmer#Pinbelegung benutzen
ber den Takteingang XTAL1 ist der Mikrocontroller mit dem Quarzoszillator verbunden, der
den bentigten Takt von 4 MHz liefert (siehe unten). Achtung: die Pins werden, wenn man den
Oszillator mit der Schrift nach oben vor sich liegen hat, von unten links aus abgezhlt. Unten
links ist Pin 1, unten rechts Pin 7, oben rechts Pin 8 und oben links Pin 14 (natrlich hat der
Oszillator nur 4 Pins. Die Nummerierung kommt daher, dass bei einem normalen IC dieser
Gre an den gleichen Positionen die Pins Nr. 1, 7, 8 und 14 wren). Zu den Pins Datenblatt
beachten [1].
PD0-PD7 und PB0-PB5 sind die IO-Ports des Mikrocontrollers. Hier knnen Bauteile wie LEDs,
Taster oder LCDs angeschlossen werden. Der Port C (PC0-PC5) spielt beim
Atmega8/AT90S4433 eine Sonderrolle: mit diesem Port knnen Analog-Spannungen gemessen
werden. Aber dazu spter mehr! An Pin 17-19 ist die Stiftleiste zur Verbindung mit dem ISPProgrammer angeschlossen, ber den der AVR vom PC programmiert wird (Achtung: Pins in
Abbildung entsprechen nicht der Belegung des AVRISP mkII. Die korrekte Pin-Belegung kann
im Handbuch des AVRISP mkII eingesehen werden). Die Resetschaltung, bestehend
aus R1 und C1, sorgt dafr, dass der Reseteingang des Controllers standardmig auf
Vcc=+5V liegt. Zum Programmieren zieht der ISP-Adapter die Resetleitung auf Masse (GND),
die Programmausfhrung wird dadurch unterbrochen und der interne Speicher des Controllers
kann neu programmiert werden. Zwischen Vcc und GND kommen noch jeweils ein 100nF
Keramik- oder Folienkondensator C3 und C4, um Strungen in der Versorgungsspannung zu
unterdrcken. Diese Abblockkondensatoren sollten so nah wie mglich am Controller platziert
werden. An den Ausgang ARef wird ebenfalls ein 100nF Kondensator angeschlossen. Dieser
wird allerdings erst bentigt, wenn der Analog/Digital Konverter des C in Betrieb genommen
wird.
Fr den Anschluss des ISP-Programmiergertes kann man im Grunde jede beliebige PinBelegung des ISP Steckers benutzen, solange nur alle bentigten Leitungen mit dem
Programmiergert verbunden sind. In der Praxis haben sich allerdings bestimmte Belegungen
durchgesetzt. Im Schaltbild ist eine eigene Belegung des 6-poligen Steckers gezeigt. Die
alternative Pinbelegung eines 2-reihigen/10-poligen Steckers ist eine bliche Belegung. Benutzt
man so eine bliche Belegung, so reicht normalerweise ein 10-poliges Flachbandkabel, um den
vorhandenen ISP-Programmer so mit der Schaltung zu verbinden, dass alle Signale am
richtigen Prozessorpin ankommen. Siehe auch AVR_In_System_Programmer.
Hier die Liste der bentigten Bauteile:
R1 Widerstand 10 kOhm
C1 Keramikkondensator 47 nF
Stiftleiste 6-polig
Quarzoszillator 4 MHz
Beim Steckbrett ist darauf zu achten, dass man die parallellaufenden Schienen fr GND (blau)
und Vcc (rot) jeweils mit Drhten verbindet (nicht Vcc und GND miteinander!).
Eine Zusammenstellung der bentigten Bauteile befindet sich in der Bestellliste.
Eine weitere Beschreibung fr ein Minimalsystem gibt es hier.
[Bearbeiten]Ergnzende Hinweise zur Taktversorgung (kann bersprungen werden)
Ein Mikrocontroller bentigt, wie jeder Computer, eine Taktversorgung. Der Takt ist notwendig, um die
internen Ablufe im Prozessor in einer zeitlich geordneten Reihenfolge ausfhren zu knnen. Die
Frequenz des Taktes bestimmt im Wesentlichen, wie schnell ein Mikrocontroller arbeitet. Bei einem
ATMega8 gibt es viele Mglichkeiten zur Taktversorgung, die Wichtigsten sollen hier gezeigt werden.
Keramikresonator
Quarz
Quarzoszillator
Typ
interner
RC-Oszillator
Genauig
keit
1-5%
Vorteile
Nachteile
ungenau
Keramikreson
0,5-1%
ator
ausreichend genau
fr UART
XTAL1/2 Pins nicht
in sehr hohen Stckzahlen
verfgbar
billiger als Quarz
Platzbedarf
schnelleres Einschwingen
als Quarz (ca. 1ms)
Quarz
sehr genau
10-
100ppm
Quarzoszillato 1r
100ppm
temperaturstabil
verfgbar
Platzbedarf
Kosten bei sehr
hohen Stckzahlen
(1000++)
langsames
Anschwingen (ca.
10ms)
hochgenau
sehr temperaturstabil
liefert selbst ein Signal,
kann dadurch verfuste
AVRs retten
kann mehrere
Takteingnge treiben
1ppm = 0,0001% (engl. one part per million, der millionste Teil)
Achtung: Ein ATMega8 wird mit aktiviertem internen RC-Oszillator ausgeliefert. Um eine andere Taktquelle
zu aktivieren, mssen die AVR Fuses des Prozessors verndert werden. Das muss jedoch sehr vorsichtig
gemacht werden, siehe Artikel.
[Bearbeiten]Keramikresonator
Resonator Standardbeschaltung
Es werden keine Kondensatoren bentigt, diese sind schon eingebaut, daher ist der Anschluss eines
Keramikschwingers kinderleicht. Achtung: Keramikresonatoren gibt es mit zwei oder drei Pins. Nur die mit
drei Pins besitzen interne Kondensatoren.
[Bearbeiten]Quarz
Quarz Standardbeschaltung
Die beiden Kondensatoren C3 und C4 sind zum Betrieb des Quarzes notwendig. Ihre Gre ist abhngig
von den Daten des Quarzes. Zur Berechnung ihrer Gre gibt es die folgende Formel:
C=2xCL-(CP+CI)
[Bearbeiten]Spannungsversorgung
Die Versorgungsspannung Vcc betrgt 5V und kann z. B. mit der in diesem Kapitel
beschriebenen Schaltung erzeugt werden. Falls zum Programmieren des Mikrocontrollers
ein ISP-Programmiergert verwendet wird, das an die USB-Schnittstelle angeschlossen ist,
kann man die Schaltung auch darber mit Strom versorgen und dieses Kapitel berspringen.
Bauteile:
Hauptelement der Schaltung ist das IC 7805. Seine Aufgabe ist es aus der
Versorgungsspannung stabile 5V zu erzeugen. Dieses IC gibt es seit vielen Jahren und wird
von vielen Chipherstellern produziert. Er stellt die einfachste und simpelste Mglichkeit dar, aus
einer vorhandenen Gleichspannung definierte 5V zu erzeugen. Den 7805 gibt es in
verschiedenen Ausfhrungen, was seine maximale Strombelastung angeht. Fr die Zwecke
dieses Tutorials ist die Standard-Variante, welche maximal 1A abgeben kann, vllig
ausreichend. Der 7805 enthlt eine bertemperatursicherung, so dass er abschaltet, wenn es
ihm zu hei wird.
Die beiden 100nF Kondensatoren haben die Aufgabe, eine mgliche Schwingneigung des 7805
zu unterdrcken. Sie mssen so nahe wie mglich an den Anschlusspins des 7805
angeschlossen werden, um ihre Wirkung zu entfalten.
An den Eingang (+ und - im Schaltplan) wird ein Steckernetzteil mit einer Spannung von 7 - 12V
angeschlossen. Der 7805 bentigt an seinem Eingang eine Gleichspannung, die mindestens 7V
betrgt. Auf der anderen Seite ergibt es auch keinen Sinn, wesentlich ber 12V
Eingangsspannung hinauszugehen. Der 7805 ist ein Linearregler. Salopp gesagt, wird die
berschssige Spannung in Form von Wrme vernichtet. Liegt die Eingangsspannung weit ber
12V, so wird schon wesentlich mehr Energie in Form von Wrme umgesetzt, als am Ausgang
entnommen werden kann. Mal ganz davon abgesehen, dass der 7805 davon brennend hei
werden wird.
Hier ist die oben beschriebene Selbstbauschaltung zu sehen. Spannungsversorgung (links), 6poliger ISP-Anschluss (rechts hinter dem C), Quarz mit 2 Kondensatoren statt Oszillator,
erweitert um eine LED mit Vorwiderstand an PB0 (rechts vor dem C), einem Resettaster (links
vor dem C) und einem Sttzkondensator zwischen +5V und GND (rechts unten).
[Bearbeiten]Der ISP-Programmierer (In-System-Programmer)
ISP Programmierer
Dann braucht man nur noch den ISP-Programmieradapter, ber den man die Programme vom
PC in den Controller bertragen kann. Eine bersicht ber mgliche ISP-Programmer Varianten
findet sich im Artikel AVR_In_System_Programmer.
Fertige ISP-Programmer zum Anschluss an den Parallelport oder USB gibt es z. B.
auf http://shop.mikrocontroller.net/.
Eine Bauanleitung gibt es u.a. auf http://www.rn-wissen.de/index.php/AVRISP_Programmierkabel oder http://rumil.de/hardware/avrisp.html.
Den ISP-Adapter schliet man an den Parallelport an und verbindet ihn mit der Stiftleiste SV1
ber ein 6-adriges Kabel (siehe Schaltplan).
[Bearbeiten]Sonstiges
Wer vorausschauend kauft, kauft mehr als einen Mikrocontroller. Bis der erste Controller defekt
ist, oder man durch Austauschen sicher gehen mchte, ob der Fehler im Programm oder im
Controller ist, vergeht nur wenig Zeit.
Tipp: Die Preise fr Mikrocontroller haben eine deutliche Spannweite, nicht selten ist ein und
derselbe Typ fr 3 oder 8 Euro zu haben. Oft sind neuere oder grere Modelle billiger
(ATmega8A statt ATmega8, ATmega328 statt ATmega8A). Eine Suche im Internet lohnt sich.
Das Gleiche gilt fr den Kauf von ISP-Programmierern.
Fr weitere Kapitel dieses Tutorials sollte man sich noch die folgenden Bauteile besorgen:
Teil 2 (I/O-Grundlagen)
5 Taster
6 Widerstnde 1k
5 Widerstnde 10k
Teil 6 (LC-Display)
1 Potentiometer 10k
besitzt das LCD eine Hintergrundbeleuchtung, dann noch einen Vorwiderstand dafr.
Details dazu stehen im Datenblatt des LCD. Ein Wert von 50 sollte aber in jedem Fall
passen. Schlimmstenfalls ist die Hintergrundbeleuchtung dann etwas zu dunkel.
5 Kondensatoren
o
Die Kondensatoren drfen auch grer sein. Ist man sich nicht sicher, welchen
MAX232 man hat (A oder nicht A), dann die greren Kondensatoren 1F nehmen, die
funktionieren auch beim MAX232A oder MAX202.
Teil 14 (ADC)
1 Kondensator 100n
1 Potentiometer 10k
nach Lust und Laune temperatur- oder lichtabhngige Widerstnde und jeweils einen
Widerstand in der gleichen Grenordnung wie der Sensor
Teil 17 (Schieberegister)
2 Schieberegister 74HC595
einige LED, damit man an die Schieberegister auch etwas anschlieen kann, samt
passenden Vorwiderstnden
Teil 19 (7-Segmentanzeige)
4 PNP-Transistoren BC328
4 Widerstnde 1k
7 Widerstnde 100
Fr weitere Bauteile, die man als angehender C Bastler auch des fteren mal bentigt,
empfiehlt sich ein Blick in die Liste der Standardbauelemente bzw. in die Grundausstattung.
Wenn Ihr Hndler Gropackungen (zb. 100 Stck) von 100n Kondensatoren, 10k, 1k oder 100
Widerstnden anbietet, sollten Sie deren Erwerb in Erwgung ziehen. Diese Bauteile bentigt
man oft, und derartige Gropackungen sind meist nicht teurer, als wenn man einige wenige
Exemplare einzeln kauft. Dies hngt damit zusammen, dass das Herauszhlen von 9 Bauteilen
fr den Verkufer teurer kommt, als 100 Bauteile abgepackt aus dem Regal zu nehmen.
[Bearbeiten]Software
In diesem Tutorial wird nur auf die Programmierung in Assembler eingegangen, da Assembler
fr das Verstndnis der Hardware am besten geeignet ist.
[Bearbeiten]Assembler
Zuerst braucht man einen Assembler, der in Assemblersprache geschriebene Programme in
Maschinencode bersetzt. Windows-User knnen das AVR-Studio von Atmel verwenden, das
neben dem Assembler auch einen Simulator enthlt, mit dem sich die Programme vor der
bertragung in den Controller testen lassen; fr Linux gibt es tavrasm, avra und gavrasm.
Um die vom Assembler erzeugte ".hex"-Datei ber den ISP-Adapter in den Mikrocontroller zu
programmieren, kann man unter Windows z. B. das Programm yaap verwenden, fr Linux gibt
es uisp, fr beide avrdude.
[Bearbeiten]C
Wer in C programmieren mchte, kann den kostenlosen GNU-C-Compiler AVR-GCC (unter
Windows "WinAVR") ausprobieren. Dieser C-Compiler kann auch in das fr AssemblerProgrammierung notwendige AVR-Studio integriert werden. In der Artikelsammlung gibt es ein
umfangreiches Tutorial zu diesem Compiler;
Wer unter Windows und Linux gleichermassen kostenlos entwickeln will, der sollte sich die IDE
Eclipse for C/C++ Developers und das AVR-Eclipse Plugin ansehen, beide sind unter Windows
und Linux einfach zu installieren. Hier wird auch der AVR-GCC benutzt. In der Artikelsammlung
gibt es ein umfangreiches AVR Eclipse Tutorial zu dieser IDE. Ebenfalls unter Linux und
Windows verfgbar ist die Entwicklungsumgebung Code::Blocks (aktuelle, stabile Versionen
sind als Nightly Builds regelmig im Forum verfgbar). Innerhalb dieser
Entwicklungsumgebung knnen ohne die Installation zustzlicher Plugins "AVR-Projekte"
angelegt werden.
Fragen dazu stellt man am besten hier im GCC-Forum.
[Bearbeiten]Pascal
Wer in Pascal programmieren muss, kann AVRPascal ausprobieren.
Dieser Pascalcompiler ist kostenfrei bis 4kb Code und bietet viele ausgereifte Bibliotheken fr
Servoansteuerung, Serielle Schnittstellen (COM, TWI, SPI), PWM, Timernutzung, LC-Displays
usw.
Auerdem gibt es eine kostenfreie Version fr den Mega8 und den Mega88. E-LAB.
[Bearbeiten]Basic
Auch Basic-Fans kommen nicht zu kurz, fr die gibt es z. B. Bascom AVR ($69, Demo
verfgbar).
[Bearbeiten]Forth
Wer einen direkten und interaktiven Zugang zum Controller haben will, sollte
sich Forth anschauen. Voraussetzung ist ein serieller Anschlu (Max232), also etwas mehr als
die Minimalbeschaltung.
[Bearbeiten]Literatur
Bevor man anfngt, sollte man sich die folgenden PDF-Dateien runterladen und zumindest mal
reinschauen:
Das Datenblatt eines Controllers ist das wichtigste Dokument fr einen Entwickler. Es enthlt
Informationen ber die Pinbelegung, Versorgungsspannung, Beschaltung, Speicher, die
Verwendung der IO-Komponenten und vieles mehr.
Im Befehlssatz sind alle Assemblerbefehle der AVR-Controllerfamilie aufgelistet und erklrt.
AVR-Tutorial: IO-Grundlagen
Inhaltsverzeichnis
[Verbergen]
1 Hardware
2 Zahlensysteme
3 Ausgabe
o
3.1 Assembler-Sourcecode
3.2 Assemblieren
3.4 Programmerklrung
4 Eingabe
4.1 mgliche Zeitverzgerungen
5 Pullup-Widerstand
6 Zugriff auf einzelne Bits
7 Zusammenfassung der Portregister
8 Ausgnge benutzen, wenn mehr Strom bentigt wird
Hardware
Fr die ersten Versuche braucht man nur ein paar Taster und LEDs an die IO-Ports des AVRs
anzuschlieen. An PB0-PB5 schliet man 6 LEDs ber einen Vorwiderstand von je 1 k gegen
Vcc (5V) an. In der Praxis ist es unerheblich, ob der Widerstand vor oder nach der Diode liegt,
wichtig ist nur, dass er da ist. Weitere Details zu LEDs und entsprechenden Vorwiderstnden
findet ihr im Artikel ber LEDs und in diesem Thread im Forum.
Dass die LEDs an den gleichen Pins wie der ISP-Programmer angeschlossen sind, strt
brigens normalerweise nicht. Falls wider Erwarten deshalb Probleme auftreten sollten, kann
man versuchen, den Vorwiderstand der LEDs zu vergrern.
Zahlensysteme
Bevor es losgeht, hier noch ein paar Worte zu den verschiedenen Zahlensystemen.
Binrzahlen werden fr den Assembler im Format 0b00111010 geschrieben,
Hexadezimalzahlen als 0x7F. Umrechnen kann man die Zahlen z. B. mit dem WindowsRechner. Hier ein paar Beispiele:
Dezimal
Hexadezimal
Binr
0x00
0b00000000
0x01
0b00000001
0x02
0b00000010
0x03
0b00000011
0x04
0b00000100
0x05
0b00000101
0x06
0b00000110
0x07
0b00000111
0x08
0b00001000
0x09
0b00001001
10
0x0A
0b00001010
11
0x0B
0b00001011
12
0x0C
0b00001100
13
0x0D
0b00001101
14
0x0E
0b00001110
15
0x0F
0b00001111
100
0x64
0b01100100
255
0xFF
0b11111111
"0b" und "0x" haben fr die Berechnung keine Bedeutung, sie zeigen nur an, dass es sich bei
dieser Zahl um eine Binr- bzw. Hexadezimalzahl handelt.
Wichtig dabei ist es, dass Hexadezimal- bzw. Binrzahlen bzw. Dezimalzahlen nur
unterschiedliche Schreibweisen fr immer das Gleiche sind: eine Zahl. Welche Schreibweise
bevorzugt wird, hngt auch vom Verwendungszweck ab. Je nachdem kann die eine oder die
andere Schreibweise klarer sein.
Auch noch sehr wichtig: Computer und Cs beginnen immer bei 0 zu zhlen, d.h. wenn es 8
Dinge (Bits etc.) gibt, hat das erste die Nummer 0, das zweite die Nummer 1, ..., und das letzte
(das 8.) die Nummer 7(!).
Ausgabe
Assembler-Sourcecode
Unser erstes Assemblerprogramm, das wir auf dem Controller laufen lassen mchten, sieht so
aus:
.include "m8def.inc"
ldi r16, 0xFF
out DDRB, r16
rjmp ende
Assemblieren
Das Programm muss mit der Endung ".asm" abgespeichert werden, z. B. als "leds.asm". Diese
Datei knnen wir aber noch nicht direkt auf den Controller programmieren. Zuerst mssen wir
sie dem Assembler fttern. Bei wavrasm funktioniert das z. B., indem wir ein neues Fenster
ffnen, den Programmtext hineinkopieren, speichern und auf "assemble" klicken. Wichtig ist,
dass sich die Datei "m8def.inc" (wird beim Atmel-Assembler mitgeliefert) im gleichen
Verzeichnis wie die Assembler-Datei befindet. Der Assembler bersetzt die Klartext-Befehle des
Assemblercodes in fr den Mikrocontroller verstndlichen Binrcode und gibt ihn in Form einer
sogenannten "Hex-Datei" aus. Diese Datei kann man dann mit der entsprechenden Software
direkt in den Controller programmieren.
Mikrocontrollers laden kann. Wenn alles geklappt hat, leuchten jetzt die ersten beiden
angeschlossenen LEDs.
Programmerklrung
In der ersten Zeile wird die Datei m8def.inc eingebunden, welche die prozessortypischen
Bezeichnungen fr die verschiedenen Register definiert. Wenn diese Datei fehlen wrde,
wsste der Assembler nicht, was mit "PORTB", "DDRD" usw. gemeint ist. Fr jeden AVRMikrocontroller gibt es eine eigene derartige Include-Datei, da zwar die Registerbezeichnungen
bei allen Controllern mehr oder weniger gleich sind, die Register aber auf unterschiedlichen
Controllern unterschiedlich am Chip angeordnet sind und nicht alle Funktionsregister auf allen
Prozessoren existieren. Fr einen ATmega8 beispielsweise wrde die einzubindende Datei
m8def.inc heien. Normalerweise ist also im Namen der Datei der Name des Chips in
irgendeiner Form, auch abgekrzt, enthalten. Kennt man den korrekten Namen einmal nicht, so
sieht man ganz einfach nach. Alle Include-Dateien wurden von Atmel in einem gemeinsamen
Verzeichnis gespeichert. Das Verzeichnis ist bei einer Standardinstallation am PC auf
C:\Programme\Atmel\AVR Tools\AvrAssembler\Appnotes\. Einige Include-Dateien heien
AT90s2313:
ATmega8:
ATmega16:
ATmega32:
ATTiny12:
ATTiny2313:
2313def.inc
m8def.inc
m16def.inc
m32def.inc
tn12def.inc
tn2313def.inc
Um sicher zu gehen, dass man die richtige Include-Datei hat, kann man diese mit einem
Texteditor (AVR-Studio oder Notepad) ffnen. Der Name des Prozessors wurde von Atmel
immer an den Anfang der Datei geschrieben:
;***************************************************************************
;* 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
;*
;* Number
:AVR000
;* File Name
:"2313def.inc"
;* Title
:Register/Bit Definitions for the AT90S2313
;* Date
:99.01.28
;* Version
:1.30
;* Support E-Mail
:avr@atmel.com
;* Target MCU
:AT90S2313
...
Aber jetzt weiter mit dem selbstgeschriebenen Programm.
In der 2. Zeile wird mit dem Befehl ldi r16, 0xFF der Wert 0xFF (entspricht 0b11111111) in das
Register r16 geladen (mehr Infos unter Adressierung). Die AVRs besitzen 32 Arbeitsregister, r0r31, die als Zwischenspeicher zwischen den I/O-Registern (z. B. DDRB, PORTB, UDR...) und
dem RAM genutzt werden. Zu beachten ist auerdem, dass die ersten 16 Register (r0-r15) nicht
von jedem Assemblerbefehl genutzt werden knnen. Ein Register kann man sich als eine
Speicherzelle direkt im Mikrocontroller vorstellen. Natrlich besitzt der Controller noch viel mehr
Speicherzellen, die werden aber ausschlielich zum Abspeichern von Daten verwendet. Um
diese Daten zu manipulieren, mssen sie zuerst in eines der Register geladen werden. Nur dort
ist es mglich, die Daten zu manipulieren und zu verndern. Ein Register ist also vergleichbar
mit einer Arbeitsflche, whrend der restliche Speicher eher einem Stauraum entspricht. Will
man arbeiten, so muss das Werkstck (= die Daten) aus dem Stauraum auf die Arbeitsflche
geholt werden und kann dann dort bearbeitet werden. Der Befehl Befehl ldi ldt jetzt einen
bestimmten konstanten Wert in so ein Arbeitsregister. In diesem Fall kommt der zu ladende
Wert also nicht aus dem Stauraum, sondern der Programmierer kennt ihn bereits. Auch
Assemblerbefehle sind nicht einfach willkrlich gewhlte Buchstabenkombinationen, sondern
sind oft Abkrzungen fr eine bestimmte Aktion. ldi bedeutet in Langform Load immediate.
Load ist klar - laden. Und immediate bedutet, dass der zu ladende Wert beim Befehl selber
angegeben wurde (engl. immediate - unmittelbar).
Die Erklrungen nach dem Semikolon sind Kommentare und werden vom Assembler nicht
beachtet.
Der 3. Befehl, out, gibt den Inhalt von r16 (=0xFF) in das Datenrichtungsregister fr Port B aus.
Das Datenrichtungsregister legt fest, welche Portpins als Ausgang und welche als Eingang
genutzt werden. Steht in diesem Register ein Bit auf 0, wird der entsprechende Pin als Eingang
konfiguriert, steht es auf 1, ist der Pin ein Ausgang. In diesem Fall sind also alle 6 Pins von Port
B Ausgnge. Datenrichtungsregister knnen ebenfalls nicht direkt beschrieben werden, daher
muss man den Umweg ber eines der normalen Register r16 - r31 gehen.
Der nchste Befehl, ldi r16, 0b11111100 ldt den Wert 0b11111100 in das Arbeitsregister r16,
der durch den darauffolgenden Befehl out PORTB, r16 in das I/O-Register PORTB (und damit
an den Port, an dem die LEDs angeschlossen sind) ausgegeben wird. Eine 1 im PORTBRegister bedeutet, dass an dem entsprechenden Anschluss des Controllers die Spannung 5V
anliegt, bei einer 0 sind es 0V (Masse).
Schlielich wird mit rjmp ende ein Sprung zur Marke ende: ausgelst, also an die gleiche
Stelle, wodurch eine Endlosschleife entsteht. Sprungmarken schreibt man gewhnlich an den
Anfang der Zeile, Befehle in die 2. und Kommentare in die 3. Spalte. Ein Marke ist einfach nur
ein symbolischer Name, auf den man sich in Befehlen beziehen kann. Sie steht stellvertretend
fr die Speicheradresse des unmittelbar folgenden Befehls. Der Assembler, der den
geschriebenen Text in eine fr den C ausfhrbare Form bringt, fhrt ber die Marken Buch und
ersetzt in den eigentlichen Befehlen die Referenzierungen auf die Marken mit den korrekten
Speicheradressen.
Bei Kopier- und Ladebefehlen (ldi, in, out...) wird immer der 2. Operand in den ersten kopiert:
ldi r17, 15
mov r16, r17
out PORTB, r16
in r16, PIND
;
;
;
;
das
das
das
das
Wer mehr ber die Befehle wissen mchte, sollte sich die PDF-Datei Instruction Set (1,27
MB) runterladen (bentigt Acrobat Reader oder in der Hilfe von Assembler oder AVR-Studio
nachschauen. Achtung: nicht alle Befehle sind auf jedem Controller der AVR-Serie verwendbar!
Nun sollten die beiden ersten LEDs leuchten, weil die Portpins PB0 und PB1 durch die Ausgabe
von 0 (low) auf Masse (0V) gelegt werden und somit ein Strom durch die gegen Vcc (5V)
geschalteten LEDs flieen kann. Die 4 anderen LEDs sind aus, da die entsprechenden Pins
durch die Ausgabe von 1 (high) auf 5V liegen.
Warum leuchten die beiden ersten LEDs, wo doch die beiden letzen Bits auf 0 gesetzt sind?
Das liegt daran, dass man die Bitzahlen von rechts nach links schreibt. Ganz rechts steht das
niedrigstwertige Bit ("LSB", Least Significant Bit), das man als Bit 0 bezeichnet, und ganz links
das hchstwertige Bit ("MSB", Most Significant Bit), bzw. Bit 7. Das Prefix "0b" gehrt nicht zur
Zahl, sondern sagt dem Assembler, dass die nachfolgende Zahl in binrer Form interpretiert
werden soll.
Das LSB steht fr PB0, und das MSB fr PB7... aber PB7 gibt es doch z. B. beim AT90S4433
gar nicht, es geht doch nur bis PB5? Der Grund ist einfach: Am Gehuse des AT90S4433 gibt
es nicht genug Pins fr den kompletten Port B, deshalb existieren die beiden obersten Bits nur
intern.
Eingabe
Im folgenden Programm wird Port B als Ausgang und Port D als Eingang verwendet:
Download leds+buttons.asm
.include "m8def.inc"
ldi r16, 0xFF
out DDRB, r16
ldi r16, 0x00
out DDRD, r16
loop:
in r16, PIND
out PORTB, r16
rjmp loop
Wenn der Port D als Eingang geschaltet ist, knnen die anliegenden Daten ber das IORegister PIND eingelesen werden. Dazu wird der Befehl in verwendet, der ein IO-Register (in
diesem Fall PIND) in ein Arbeitsregister (z. B. r16) kopiert. Danach wird der Inhalt von r16 mit
dem Befehl out an Port B ausgegeben. Dieser Umweg ist notwendig, da man nicht direkt von
einem IO-Register in ein anderes kopieren kann.
rjmp loop sorgt dafr, dass die Befehle in r16, PIND und out PORTB, r16 andauernd
wiederholt werden, so dass immer die zu den gedrckten Tasten passenden LEDs leuchten.
Achtung: Auch wenn es hier nicht explizit erwhnt wird: Man kann natrlich jeden Pin eines
jeden Ports einzeln auf Ein- oder Ausgabe schalten. Dass hier ein kompletter Port jeweils als
Eingabe bzw. Ausgabe benutzt wurde, ist reine Bequemlichkeit.
In komplexeren Situationen als der einfachen Verbindung eines Port-Pins mit einem Taster, der
zuverlssig auf GND-Potential zieht, ist die Schaltschwelle des Eingangstreibers zu beachten.
Diese liegt bei etwa 50 % der Versorgungsspannung. In dieser Testschaltung wird dieser Aspekt
genauer untersucht.
mgliche Zeitverzgerungen
Vorsicht! In bestimmten Situationen kann es passieren, dass scheinbar Pins nicht richtig
gelesen werden.
Speziell bei der Abfrage von Matrixtastaturen kann der Effekt auftreten, dass Tasten scheinbar
nicht reagieren. Typische Sequenzen sehen dann so aus:
ldi
out
ldi
out
in
r16,0x0F
DDRD,r16
r16,0xFE
PORTD,r16
r17,PIND
Warum ist das problematisch? Nun, der AVR ist ein RISC-Microcontroller, welcher die meisten
Befehle in einem Takt ausfhrt. Gleichzeitig werden aber alle Eingangssignale ber FlipFlops
abgetastet (synchronisiert), damit sie sauber im AVR zur Verfgung stehen. Dadurch ergibt sich
eine Verzgerung (Latenz) von bis zu 1,5 Takten, mit der auf externe Signale reagiert werden
kann. Die Erklrung dazu findet man im Datenblatt unter der berschrift "I/O Ports - Reading
the Pin Value".
Was tun? Wenn der Wert einer Port-Eingabe von einer unmittelbar vorangehenden PortAusgabe abhngt, muss man wenigstens einen weiteren Befehl zwischen beiden einfgen, im
einfachsten Fall ein nop. nop bedeutet in Langform no operation, und genau das macht der
Befehl auch: nichts. Er dient einzig und alleine dazu, dass der Prozessor einen Befehl
abarbeitet, also etwas zu tun hat, aber ansonsten an den Registern oder sonstigen Internals
nichts verndert.
ldi
out
ldi
out
NOP
in
r16,0x0F
DDRD,r16
r16,0xFE
PORTD,r16
r17,PIND
Pullup-Widerstand
Bei der Besprechung der notwendigen Beschaltung der Ports wurde an einen Eingangspin
jeweils ein Taster mit einem Widerstand nach Vcc vorgeschlagen. Diesen Widerstand nennt
man einen Pullup-Widerstand. Wenn der Taster geffnet ist, so ist es seine Aufgabe, den
Eingangspegel am Pin auf Vcc zu ziehen. Daher auch der Name: 'pull up' (engl. fr
hochziehen). Ohne diesen Pullup-Widerstand wrde ansonsten der Pin bei geffnetem Taster in
der Luft hngen, also weder mit Vcc noch mit GND verbunden sein. Dieser Zustand ist aber
unbedingt zu vermeiden, da bereits elektromagnetische Einstreuungen auf Zuleitungen
ausreichen, dem Pin einen Zustand vorzugaukeln, der in Wirklichkeit nicht existiert. Der PullupWiderstand sorgt also fr einen definierten 1-Pegel bei geffnetem Taster. Wird der Taster
geschlossen, so stellt dieser eine direkte Verbindung zu GND her und der Pegel am Pin fllt auf
GND. Durch den Pullup-Widerstand rinnt dann ein kleiner Strom von Vcc nach GND. Da PullupWiderstnde in der Regel aber relativ hochohmig sind, strt dieser kleine Strom meistens nicht
weiter.
Anstelle eines externen Widerstandes wre es auch mglich, den Widerstand wegzulassen und
stattdessen den in den AVR eingebauten Pullup-Widerstand zu aktivieren. Die Beschaltung
eines Tasters vereinfacht sich dann zum einfachst mglichen Fall: Der Taster wird direkt an den
Eingangspin des C angeschlossen und schaltet nach Masse durch.
Das geht allerdings nur dann, wenn der entsprechende Mikroprozessor-Pin auf Eingang
geschaltet wurde. Ein Pullup-Widerstand hat nun mal nur bei einem Eingangspin einen Sinn.
Bei einem auf Ausgang geschalteten Pin sorgt der Mikroprozessor dafr, dass ein dem PortWert entsprechender Spannungspegel ausgegeben wird. Ein Pullup-Widerstand wre in so
einem Fall kontraproduktiv, da der Widerstand versucht, den Pegel am Pin auf Vcc zu ziehen,
whrend eine 0 im Port-Register dafr sorgt, dass der Mikroprozessor versuchen wrde, den
Pin auf GND zu ziehen.
Ein Pullup-Widerstand an einem Eingangspin wird durch das PORT-Register gesteuert.
Das PORT-Register erfllt also 2 Aufgaben. Bei einem auf Ausgang geschalteten Port steuert
es den Pegel an den Ausgangspins. Bei einem auf Eingang geschalteten Port steuert es, ob die
internen Pullup-Widerstnde aktiviert werden oder nicht. Ein 1-Bit aktiviert den entsprechenden
Pullup-Widerstand.
DDRx
PORTx
IO-Pin-Zustand
.include "m8def.inc"
ldi r16, 0xFF
out DDRB, r16
ldi r16, 0x00
out DDRD, r16
loop:
in r16, PIND
out PORTB, r16
rjmp loop
Werden auf diese Art und Weise die AVR-internen Pullup-Widerstnde aktiviert, so sind keine
externen Widerstnde mehr notwendig und die Beschaltung vereinfacht sich zu einem Taster,
der einfach nur den C-Pin mit GND verbindet.
Der Befehl sbic ("skip if bit cleared") berspringt den darauffolgenden Befehl, wenn das
angegebene Bit 0 (low) ist.
sbis ("skip if bit set") bewirkt das Gleiche, wenn das Bit 1 (high) ist.
Mit cbi ("clear bit") wird das angegebene Bit auf 0 gesetzt.
Achtung: Diese Befehle knnen nur auf die IO-Register angewandt werden!
Der groe Vorteil, vor allen Dingen der cbi bzw. sbi Instruktionen ist es, dass sie tatschlich nur
ein einziges Bit am Port manipulieren. Dies ist insbesonders dann interessant, wenn an einem
Port mehrere LED hngen, die unterschiedliche Dinge anzeigen. Will man dann eine bestimmte
LED ein bzw. ausschalten, dann sollen sich ja deswegen die anderen LED nicht verndern.
Greift man mittel out auf den kompletten Port zu, dann muss man dies bercksichtigen. Wird
die eine LED aber mittels cbi bzw. sbi manipuliert, dann braucht man sich um die anderen LED
an diesem Port nicht kmmern - deren Zustand verndert sich durch cbi bzw. sbi nicht.
Am besten verstehen kann man das natrlich an einem Beispiel:
Download bitaccess.asm
.include "m8def.inc"
loop:
ende:
sbic PIND, 0
rjmp loop
cbi PORTB, 3
rjmp ende
; Endlosschleife
Dieses Programm wartet so lange in einer Schleife ("loop:"..."rjmp loop"), bis Bit 0 im
Register PIND 0 wird, also die erste Taste gedrckt ist. Durch sbic wird dann der Sprungbefehl
zu "loop:" bersprungen, die Schleife wird also verlassen und das Programm danach
fortgesetzt. Ganz am Ende schlielich wird das Programm durch eine leere Endlosschleife
praktisch "angehalten", da es ansonsten wieder von vorne beginnen wrde.
Steht es auf Eingang, so beeinflusst das PORTx-Bit den internen PullupWiderstand an diesem Mikroprozessor-Pin. Bei einer 0 wird der Widerstand
abgeschaltet, bei einer 1 wird der Widerstand an den Eingangs-Pin
zugeschaltet.
Transistor Treiberstufe
Die LED samt zugehrigen Widerstnden dienen hier lediglich als Sinnbild fr den Verbraucher,
der vom C ein und ausgeschaltet werden soll. Welcher Transistor als Schalter benutzt werden
kann, hngt vom Stromverbrauch des Verbrauchers ab. Die Widerstnde R1 und R2 werden
als Basiswiderstnde der Transistoren bezeichnet. Fr ihre Berechnung siehe z. B. hier. Um
eine sichere Strfestigkeit im Resetfall des Mikrocontrollers zu gewhren (wenn der C daher
die Ausgnge noch nicht ansteuert), sollte man noch einen Pulldown Widerstand zwischen
Basis und Emitter schalten oder einen digitalen Transistor (z. B. BCR135) mit integriertem
Basis- und Basisemitterwiderstand benutzen.
Um ein Relais an einen C-Ausgang anzuschlieen, siehe hier.
3 Entstrung
o
3.1 Freilaufdiode
3.3 Spannungsbegrenzung
3.4 Lschglieder
4 Logikschaltungen mit Relais
4.1 Selbsthaltung
4.2 Selbstunterbrecher
4.4 Stromstorelais
o
5 Links
[Bearbeiten]Einleitung
Hufig sollen mit C-Schaltungen "grere Dinge bewegt werden", das heit ein hherer
Laststrom oder Netzspannung geschaltet werden. Dieser Artikel soll dem Anfnger dabei helfen,
beliebte Probleme zu umgehen. Die hier fr Relais aufgefhrten Manahmen sollen natrlich
sinngem auch bei anderen induktiven Lasten in Betracht gezogen werden.
[Bearbeiten]Schaltstufen
Wenn normale Bauelemente zum Einsatz kommen sollen, endet man erfahrungsgem bei
Schaltungen, bei denen mit der Logikspannung ein Bipolartransistor oder MOSFET im
Schaltbetrieb angesteuert wird und die in der Regel hhere Betriebsspannung der Relaisspule
geschaltet wird.
[Bearbeiten]Schaltstufe fr kleine Lasten
Schaltstufe fr kleine Lasten mit NPN-Bipolartransitor (links) oder N-Channel-MOSFET (rechts) als LowSide-Schalter
Links im Bild ist die Ansteuerung mit einem NPN-Bipolartransistor gezeigt. Hier wird mit einem
Steuersignal durch den Vorwiderstand der Steuerstrom erzeugt, der den Transistor Q2
durchschaltet. Die maximal schaltbare Spannung hngt von dem Transistor ab, bei hheren
Laststrmen ist darauf zu achten, da mglicherweise der Steuerstrom nicht mehr hoch genug
ist um den Laststrom sicher zu schalten. Deshalb ist die Stromverstrkung des Transistors zu
beachten. Voll durchgesteuert oder voll gesperrt sind die Lieblingszustnde des
Schalttransistors, bei unvollstndiger Ansteuerung (Linearbetrieb) fllt am Transistor eine
erhhte Verlustleistung ab.
Schaltstrom [mA]
Transistortyp
500
Q2=BC337
100
Q2=BC846(SMD)
Steuerspannung [V]
Bauteilwert
R2=470
3,3
R2=270
R2=2,2k
3,3
R2=1,3k
Werden andere Transistoren eingesetzt oder muss fr das Relais mehr oder weniger Strom zur
Verfgung gestellt werden, dann findet sich hier die Berechnung des Basiswiderstands.
Rechts im Bild wird das Relais mit einem N-Channel MOSFET gesteuert. Der Vorteil ist hier der
wesentlich geringere Steuerstrom im statischen HIGH Zustand (praktisch Null). Wichtig ist hier
R1. Dieser Pull-Down-Widerstand sorgt dafr, dass der MOSFET sicher sperrt wenn der
steuernde Mikrocontroller sich im Reset befindet oder gerade programmiert wird. Dann sind
nmlich die IO-Pins als Eingnge geschaltet und das Gate des MOSFET wrde "in der Luft
hngen" (engl. float). R1 verhindert das. Der Wert von R1 kann je nach Anwendung variieren.
blich sind auch 10k, um den Eingang gegen Streinstrahlungen zu festigen.
Schaltstrom [mA]
Transistortyp
Steuerspannung [V]
500
Q1=BS170
5-10
200
Q1=BSS138(SMD)
3,3-10
Um Relais vom PC aus zu schalten gibt es diverse Relais-Karten, aber es geht auch direkt mit
dem Parallelport um ein 5 V-Relais mit 110 Ohm Spulenwiderstand anzusteuern, mit dem man
bis zu 230 V / 10 A schalten kann. Dafr reichen die acht Daten-Pins vom Parallelport,
zusammengeschaltet mit 75 Ohm-Widerstnden.
[Bearbeiten]Schaltstufe fr groe Lasten
Bei groen zu schaltenden Leistungen kommt daher oft vor dem Schalttransistor/FET ein
Treiber zum Einsatz. Groe MOSFETs brauchen meist 10-15V Gatespannung um voll
durchzusteuern, deshalb wird ein Pegelwandler (bzw. Treiber) bentigt und wir haben etwas
Mehrstufiges. Ausnahmen sind sogenannte Logic Level MOSFETs, welche schon mit 4,5V
praktisch voll durchgesteuert sind. Diese knnen direkt von 5V Logikausgngen betrieben und
somit wie im vorherigen Kapitel angeschlossen werden, wie man in
diesem Forumsbeitrag sehen kann. Entsprechende Typen findet man im Artikel MOSFETbersicht. Diese direkte Ansteuerung per CMOS-Ausgang reicht dann sogar fr niedrige PWMFrequenzen von vielleicht 100Hz und noch etwas mehr.
Zu beachten ist hier, da durch den Treiber eine Invertierung stattfindet, d.h. ist der
Steuereingang HIGH ist der MOSFET gesperrt und die Last wird nicht von Strom durchflossen.
R2 ist dieBasisstrombegrenzung, er wird so gewhlt da der Transistor gerade so bersteuert
wird um sicherzugehen da er komplett und schnell durchgesteuert wird. R3 begrenzt den
Kollektorstrom des Treibertransistors, wenn dieser leitet, das Gate des MOSFET Q2 liegt dann
auf 0V. Wenn er nicht leitet wird ber R3 das Gate des MOSFET geladen und dieser ist dann
leitend ( Open Collector). Die hier gezeigte Schaltung kann bis zu 30A schalten, allerdings
braucht der MOSFET Q2 ab ca. 5A einen Khlkrper. Die Versorgungsspannung VCC kann
10V bis 20V betragen.
Achtung! Diese Schaltung ist nur fr langsame Ansteuerung mit ein paar Hertz
geeignet. PWM mit Frequenzen von 50 Hz und hher ist damit nicht mglich, da die erste
Schaltstufe dafr viel zu langsam ist. Der Leistungstransistor kann nicht schnell ein und aus
geschaltet werden, dadurch befindet er sich whrend der Umschaltung im Linearbetrieb und
erzeugt viel Verlustleistung (=Wrme). Fr PWM muss ein schneller MOSFETTreiber eingesetzt werden.
Gemeinsam ist diesen Schaltungen allerdings, da sie sich prima fr ohmsche Lasten eignen,
aber bei induktiven Lasten gerne Probleme bereiten:
Die Logikschaltung strzt beim Schalten gelegentlich oder immer ab, insbesondere
beim Abschalten
Bauteile verabschieden sich beim ersten Schalten oder nach einigen problemlosen
Schaltvorgngen
[Bearbeiten]Entstrung
Das Hauptproblem ist die Gegeninduktionsspannung der Spule, eine Eigenschaft die in
Schaltnetzteilen erwnscht sein mag, mit ihren u.U. mehreren hundert Volt im Logiksystem sich
aber eher schdlich auswirkt. Beim Abschalten von Induktivitten bricht deren Magnetfeld
zusammen. Die im Magnetfeld gespeicherte Energie kann nicht einfach verschwinden. Damit
wird die Induktivitt zur Energiequelle, welche sehr hohe Spannungen erzeugen kann (Prinzip
der Zndspule).
Diese Strungen knnen durch Schaltungsergnzungen gemildert oder beseitigt werden.
[Bearbeiten]Freilaufdiode
In den obigen Bildern ist die Freilaufdiode als D1 und D2 sichtbar. Dieses Bauteil ist ein
absolutes Muss bei induktiven Lasten wie Relais, Motoren etc. Teilweise in Relais schon
eingebaut, handelt es sich um eine Diode, die fr die Betriebsspannung in Sperrrichtung
eingebaut ist. Mit ihr wird die Selbstinduktionsspannung der induktiven Last im Abschaltmoment
kurzgeschlossen. Sie sollte mindestens die Versorgungsspannung als Sperrspannung
verkraften (plus Reserve von 20% und mehr). Der zulssige Durchlasstrom muss nicht so hoch
ausfallen, da die meisten Relais nur mit geringen Frequenzen schalten (einige Hertz). Hier
reicht es, wenn der zulssige Pulsstrom der Diode dem Nennstrom des Relais entspricht. Eine
kleine 1N4148 (Nachfolger von der 1N914) kann somit bis zu 1A schalten. Wer auf Nummer
sicher gehen will, whlt den Nennwert des Diodenstroms gleich dem Relaisstrom. Einfache
Gleichrichterdioden wie z. B. 1N400x sind hier entgegen der oft gehrten Meinung ausreichend,
es mssen keine schnellen Schaltdioden verwendet werden. Denn entscheidend fr die
Freilaufdiode ist die Einschaltzeit (forward recovery time), und die ist auch bei einer langsamen
Diode sehr kurz (einige Nanosekunden). Eine umfassende Erklrung findet man auf
dieser Seite. Achtung! Das gilt nur fr Relais, da diese nicht sehr oft schalten (wenige Hz) und
am Ende des Schaltvorgangs der Strom durch die Diode auf Null abgesunken ist. In einer
Anwendung mit PWM und hohen Frequenzen im kHz-Bereich mssen schnelle Schaltdioden
verwendet werden. Denn hier ist der Strom am Ende des Schaltvorgangs nicht Null, sondern
meistens ziemlich hoch. Schaltet dann die Diode von Flurichtung in Sperrichtung, kommt die
die Reverse Recovery Time zum tragen (trr). Ist sie sehr hoch, wird viel Verlustwrme in der
Diode erzeugt, was sowohl die Schaltung als auch die Diode zerstren kann.
Gelegentlich sieht man auch Dioden in Sperrichtung ber die Schaltstrecke (Kollektor-Emitter,
Source-Drain), die machen sowas hnliches. Das klappt aber nur bei Halb-und Vollbrcken!
Einfache Emitterschaltungen wie sie hier gezeigt sind brauchen eine Diode antiparallel zum
Relais!
Wenn ein schnelles Abschalten des Relais gewnscht ist, wie zum Beispiel beim Schalten
hoher Strme, sind andere Manahmen besser geeignet, um die Selbstinduktionspannung
sicher zu begrenzen. Dazu nutzt man in Reihe zur Freilaufdiode eine Z-Diode, deren ZSpannung mglichst hoch ist. Dadurch klingt der Spulenstrom wesentlich schneller ab, das
Relais fllt schneller ab und der Lichtbogen an den Kontakten wird schneller unterbrochen. Die
Kontaktlebensdauer steigt signifikant, ebenso werden weniger Strungen erzeugt. Zu beachten
ist dabei, dass der Schalttransistor die Summe aus Betriebsspannung und Z-Spannung als
Sperrspannung UCE bzw. UDS aushalten muss.
Suppressordioden eignen sich auch, sie schalten etwas schneller, knnen aber AFAIK nicht
soviel Pulsleistung aufnehmen.
[Bearbeiten]Lschglieder
Im Englischen Snubber Network genannt. Durch eine Beschaltung der Schaltkontakte des
Relais mit einem RC-Serienglied werden hochfrequente berschwingeffekte beim Schalten
gedmpft. Snubberglieder sind fast immer sinnvoll. Prinzipiell kann man sagen, dass der
Widerstand Rs hochfrequente Anteile dmpft und der Kondensator dafr sorgt, dass dieser
Vorgang beim Umschalten erfolgen kann. Bei Anschlu einer Wechselspannungsquelle stellt
sich jedoch ein kontinuierlicher Stromfluss ber die Kapazitt ein. Deshalb werden diese Glieder
parallel zum induktiven Verbraucher (Elektromotor, Drossel, Relaisspule...) und somit in Reihe
zur Spannungsversorgung und dem ffner angebracht. Wichtig ist dabei, dass der Widerstand
ausreichend dimensioniert ist, um die auftretende Verlustleistung auszuhalten. Ebenso muss
der Kondensator eine ausreichende Spannungsfestigkeit aufweisen, bei Netzspannung sollten
es mindestens 400V sein. Auerdem muss man recht groe Mindestabstnde zwischen den
Steuerkontakten und den 230V Schaltkontakten einhalten, wie in den
Artikeln Leiterbahnabstnde und Leiterbahnbreite beschrieben ist.
[Bearbeiten]Logikschaltungen
mit Relais
Wenn gleich Relais heute oft per Mikrocontroller und Transistoren angesteuert werden und die
Schaltlogik in der Software steckt, so gibt es dennoch immer mal wieder Flle, in denen man
auf reine Relaislogik zurckgreifen mchte. Die Grnde dafr sind z.B. robuster Aufbau,
Stromversorgung, Bauteillogistik etc.
[Bearbeiten]Selbsthaltung
Selbsthaltung
Eine oft genutzte Schaltung ist die Selbsthaltung. Dabei wird durch den Taster S2 das Relais
erstmalig mit Strom versorgt und zieht an. Der Strom kann jetzt auch ber den Schlieer von K1
flieen, auch wenn der Taster wieder losgelassen wird. Das Relais hlt sich selbst. Mit einem
Druck auf S1 wird der Strom unterbrochen, K1 fllt wieder ab. Solche Schaltungen werden z.B.
in Maschinen eingesetzt, wo nur mittels Tastendruck ein Start mglich sein soll. Fllt der Strom
aus oder muss eine schnelle Notabschaltung gemacht werden, so geht das Relais aus und
bleibt auch aus, auch wenn der Strom wieder eingeschaltet wird.
[Bearbeiten]Selbstunterbrecher
Selbstunterbrecher
Eine noch einfachere Schaltung ist der Selbstunterbrecher. Er ist ein elektromechanischer
Oszillator. Wird die Versorgungsspannung eingeschaltet, so zieht K1 an. Dabei unterbricht es
seinen eigenen Stromflu und fllt wieder ab. Rein statisch betrachtet klingt das wie ein
Widerspruch. Praktisch und dynamisch betrachtet funktioniert es aber, da die mechanische
Trgkeit des Kontaktes und der Hebelmechanik nicht sofort reagiert. Wie schnell das Relais
"flattert" hngt in erster Linie von der Masse der Schaltkontakte und der Rckstellfeder ab.
Beispiele findet man hier. So ein rasselndes Relais erzeugt durch den Schaltfunken am Kontakt
aus einiges an Strungen. Diese Schaltung findet man in allen einfachen elektromechanischen
Klingeln, sie ist auch als Wagnerscher Hammer bekannt.
Ferner kann diese Schaltung - bedingt durch die hohen (Selbst-)Induktionsspannungen - auch
zum Testen von Glimmlampen verwendet werden. Hierzu wird die Schutzdiode durch die zu
Testende Glimmlampe ersetzt.
Ausgangszustand: K1 und K2 sind ohne Strom, die Kontakte liegen wie im Schaltplan,
da kein Strom ber K1 oder S1 zu den Relais flieen kann
Taster S1 wird gedrckt: ber S1 und K2 wird Spannung an die Spule von K1 gelegt,
der Kontakt von K1 schliet
Taster S1 wird losgelassen, d.h. ein paar Millisekunden hngt der Schaltkontakt in der
Luft, der Strom fliet ber K1 und K2 weiter and K1 (Selbsthaltung)
Taster S1 erreicht Ruheposition, jetzt fliet Strom ber die Kontakte K1, S1 und D1 und
D2 an die Spulen von K1 und K2, wodurch der Kontakt K2 ffnet. K1 bekommt nun nur
noch ber K1, S1 und D1 Strom.
S1 wird zum 2. Mal gedrckt und hngt sehr kurz in der Luft. Die Selbsthaltung ber K1,
S1 und D1 wird unterbrochen, K1 fllt ab, dadurch ffnet K1. K2 wrde nun auch
abfallen. Tut es aber nicht, da die Umschaltung sehr schnell geht. Und hier liegt der
"Trick" der Schaltung. Die Umschaltung von S1 muss schneller sein als die
Abfallzeit der Relais K1 plus K2!
S1 wird wieder losgelassen und erreicht wieder die Ruheposition, die Selbsthaltung fr
K2 wird unterbrochen und K2 fllt ab.
Wie man sieht schaltet K1 immer dann, wenn die Taste gedrckt wird und K2 immer dann, wenn
die Taste losgelassen wird. Je nach gewnschter Funktion kann man das Signal fr weitere
Schaltfunktionen an der Spule fr K1 oder K2 abgreifen.
[Bearbeiten]Version
Hat man keine zwei Relais oder nicht den Platz um ein zweites Relais einzusetzen, kann man
das Umschalten auch mit Hilfe eines D-FlipFlops realisieren.
Funktion:
Wird auf den Takteingang von IC1A eine steigende Flanke gelegt, wird der Highpegel
vom Eingang bernommen, Q ist High, der MOSFET steuert durch und lt das Relais
anziehen, Q negiert ist Low.
Bei der nchsten positiven Taktflanke wird der Pegel des negierten Ausgangs wieder
bernommen, jetzt Low, und der Ausgang wird wieder auf Low geschaltet.
Wird ein Taster verwendet muss dieser unbedingt entprellt werden, hier im Beispiel mit
einem Schmitt-Trigger, welcher glcklicherweise schon im FlipFlop eingebaut ist (scheinbar
leider nicht bei jedem Hersteller!). Durch die Nutzung der alten, aber bis zu 15V einsetzbaren
4000er Logikserie spart man sich eine zustzliche 5V Stromversorgung. Ausserdem kann man
jeden normalen MOSFET direkt ansteuern, ein Logic Level Typ ist nicht ntig. Der IC enthlt
zwei D-FlipFlops, womit man die Funktion zweifach aufbauen kann. Alternativ kann man die
Schaltung aus diesem Forumsbetrag nutzen.
[Bearbeiten]Stromstorelais
Nachgebildetes Stromstossrelais
In der Installationstechnik fr Gebude wird man meist auf ein Stromstorelais zurckgreifen,
dort wird die Umschaltung meist ber die Mechanik im Relais erreicht. Dann reicht auch ein
einfacher Schlieer als Taster. Diese Relais bentigen nur zum Umschalten Strom und halten
dabei den Schaltzustand auch bei Stromausfall.
Diese Funktion kann man aber auch mit zwei bistabilen und einem monostabilen Relais
nachbilden. Die Schaltung ist sehr hnlich zum Vorgnger. Zwei bistabile Relais mit jeweils
einer Spule zum Setzen und Rcksetzen schalten wechselseitig um. Jeweils ein Kontakt von K1
und K2 steht zur freien Verfgung. Die Energie zum Schalten von K2 wird im Kondensator C1
gespeichert, welcher im Moment des Loslassens des Taster S1 ber die Kontakte K3 und K1
das Relais K2 kurzzeitig mit Strom versorgt. Die Schaltung bentig somit nur zum Umschalten
Strom. Der Widerstand R1 begrenzt den Ladestrom von C1 auf ertrgliche Werte. Bei der
Entladung von C1 fliet der Strom hauptschlich ber D7, damit kann die Energie von C1
besser ausgenutzt werden. Fr die Dimensionierung fr R1 und C1 gilt.
: Spulenwiderstand von K2
: Schaltzeit von K2
[Bearbeiten]Links
Forumsbeitrag: Einmal drcken ein, nochmal drcken aus (Toggeln per Relais)
Kategorie: Grundlagen
AVR-Tutorial: Logik
In weiterer Folge werden immer wieder 4 logische Grundoperationen auftauchen:
UND
ODER
NICHT
1 Allgemeines
2 Die Operatoren
2.1 UND
2.1.1 Verwendung
2.2.1 Verwendung
2.3.1 Verwendung
2.4.1 Verwendung
[Bearbeiten]Allgemeines
Die logischen Operatoren werden mit einem Register und einem zweiten Argument gebildet.
Das zweite Argument kann ebenfalls ein Register oder aber eine direkt angegebene Zahl sein.
Da ein Register aus 8 Bit besteht, werden die logischen Operatoren immer auf alle 8 Bit Paare
gleichzeitig angewendet.
Mit den logischen Grundoperationen werden die beiden Argumente miteinander verknpft und
das Ergebnis der Verknpfung im Register des ersten Argumentes abgelegt.
[Bearbeiten]Die
Operatoren
[Bearbeiten]UND
Wahrheitstabelle UND
Ergebnis
[Bearbeiten]AVR
Befehle
Die beiden Operanden werden miteinander UND verknpft, wobei jeweils die gleichwertigen
Bits der Operanden laut Wahrheitstabelle unabhngig voneinander verknpft werden.
Sei der Inhalt des Registers r16 = 0b11001100, so lautet die Verknpfung andi r16,
0b01011010
0b11001100
0b01011010
----------0b01001000
und
Wahrheitstabelle ODER
Ergebnis
Das Ergebnis ist genau dann 1, wenn A oder B oder beide 1 sind.
[Bearbeiten]Verwendung
[Bearbeiten]AVR
Befehle
or
ori
r16, r17
r16, 0b01011010
Die beiden Operanden werden miteinander ODER verknpft, wobei jeweils die jeweils
gleichwertigen Bits der Operanden laut Wahrheitstabelle unabhngig voneinander verknpft
werden.
Sei der Inhalt des Registers r16 = 0b11001100, so lautet die Verknpfung ori r16, 0b01011010
0b11001100
0b01011010
----------0b11011110
oder
Wahrheitstabelle NICHT
Ergebnis
[Bearbeiten]AVR
Befehle
com
r16
Sei der Inhalt des Registers r16 = 0b11001100, so lautet die Verknpfung com r16
0b11001100
----------0b00110011
nicht
Wahrheitstabelle XOR
Ergebnis
Das Ergebnis ist genau dann 1, wenn A oder B, aber nicht beide 1 sind.
[Bearbeiten]Verwendung
[Bearbeiten]AVR
Befehle
eor
r16, r17
Die beiden Operanden werden miteinander XOR verknpft, wobei jeweils die jeweils
gleichwertigen Bits der Operanden laut Wahrheitstabelle unabhngig voneinander verknpft
werden.
Sei der Inhalt des Registers r16 = 0b11001100 und der Inhalt des Registers r17 = 0b01011010,
so lautet die Verknpfung eor r16, r17
0b11001100
0b01011010
----------0b10010110
xor
GATE: The Logic Game - Eine kleine Abwechselung fr die Tutorialpause. (ab Windows
XP, Mac OS X)
AVR-Tutorial: Arithmetik8
Eine der Hauptaufgaben eines Mikrokontrollers bzw. eines Computers allgemein, ist es,
irgendwelche Berechnungen anzustellen. Der Lwenanteil an den meisten Berechnungen
entfllt dabei auf einfache Additionen bzw. Subtraktionen. Multiplikationen bzw. Divisionen
kommen schon seltener vor, bzw. knnen oft durch entsprechende Additionen bzw.
Subtraktionen ersetzt werden. Weitergehende mathematische Konstrukte werden zwar auch ab
und an bentigt, knnen aber in der Assemblerprogrammierung durch geschickte Umformungen
oft vermieden werden.
Inhaltsverzeichnis
[Verbergen]
1 Hardwareuntersttzung
2 8 Bit versus 16 Bit
3 8-Bit Arithmetik ohne Bercksichtigung eines Vorzeichens
o
5.1 Carry
7 Addition
7.1 AVR-Befehle
8 Subtraktion
8.1 AVR-Befehle
9 Multiplikation
9.1 Hardwaremultiplikation
9.1.1 AVR-Befehl
[Bearbeiten]Hardwareuntersttzung
Praktisch alle Mikroprozessoren untersttzen Addition und Subtraktion direkt in Hardware, das
heit: Sie haben eigene Befehle dafr. Einige bringen auch Untersttzung fr eine
Hardwaremultiplikation mit (so zum Beispiel der ATmega8), whrend Division in Hardware
schon seltener zu finden ist.
[Bearbeiten]8
In diesem Abschnitt des Tutorials wird gezielt auf 8 Bit Arithmetik eingegangen, um zunchst die
Grundlagen des Rechnens mit einem C zu zeigen. Die Erweiterung von 8 Bit auf 16 Bit
Arithmetik ist in einigen Fllen wie Addition und Subtraktion trivial, kann sich aber bei
Multiplikation und Division in einem betrchtlichen Codezuwachs niederschlagen.
Der im Tutorial verwendete ATmega8 besitzt eine sog. 8-Bit Architektur. Das heit, dass seine
Rechenregister (mit Ausnahmen) nur 8 Bit breit sind und sich daher eine 8-Bit Arithmetik als die
natrliche Form der Rechnerei auf diesem Prozessor anbietet. Berechnungen, die mehr als 8
Bit erfordern, mssen dann durch Kombinationen von Rechenvorgngen realisiert werden. Eine
Analogie wre z. B. das Rechnen, wie wir alle es in der Grundschule gelernt haben. Auch wenn
wir in der Grundschule (in den Anfngen) nur die Additionen mit Zahlen kleiner als 10
auswendig gelernt haben, so knnen wir dennoch durch die Kombination von mehreren
derartigen Additionen beliebig groe Zahlen addieren. Das gleiche gilt fr Multiplikationen. In
der Grundschule musste wohl jeder von uns das 'Kleine Einmaleins' auswendig lernen, um
Multiplikationen im Zahlenraum bis 100 quasi 'in Hardware' zu berechnen. Und doch knnen wir
durch Kombinationen solcher Einfachmultiplikationen und zustzlichen Additionen in beliebig
groe Zahlenrume vorstoen.
Die Einschrnkung auf 8 Bit ist also keineswegs eine Einschrnkung in dem Sinne, dass es
eine prinzipielle Obergrenze fr Berechnungen gbe. Sie bedeutet lediglich eine obere Grenze
dafr, bis zu welchen Zahlen in einem Rutsch gerechnet werden kann. Alles, was darber
hinausgeht, muss dann mittels Kombinationen von Berechnungen gemacht werden.
[Bearbeiten]8-Bit
Vorzeichens
Die Bits des Registers besitzen dabei eine Wertigkeit, die sich aus der Stelle des Bits im Byte
ergibt. Dies ist vllig analog zu dem uns vertrauten Dezimalsystem. Auch dort besitzt eine Ziffer
in einer Zahl eine bestimmte Wertigkeit, je nach dem, an welcher Position diese Ziffer in der
Zahl auftaucht. So hat z. B. die Ziffer 1 in der Zahl 12 die Wertigkeit 'Zehn', whrend sie in der
Zahl 134 die Wertigkeit 'Hundert' besitzt. Und so wie im Dezimalsystem die Wertigkeit einer
Stelle immer das Zehnfache der Wertigkeit der Stelle unmittelbar rechts von ihr ist, so ist im
Binrsystem die Wertigkeit einer Stelle immer das 2-fache der Stelle rechts von ihr.
Die Zahl 4632 im Dezimalsystem kann also so aufgefasst werden:
4632
=
+
+
+
4
6
3
2
*
*
*
*
1000
100
10
1
( 1000 = 10 hoch
( 100 = 10 hoch
(
10 = 10 hoch
(
1 = 10 hoch
3
2
1
0
)
)
)
)
Vllig analog ergibt sich daher folgendes fr z. B. die 8 Bit Binrzahl 0b10011011 (um
Binrzahlen von Dezimalzahlen zu unterscheiden, wird ein 0b vorangestellt):
0b10011011
1
0
0
1
1
0
1
1
+
+
+
+
+
+
+
*
*
*
*
*
*
*
*
128
64
32
16
8
4
2
1
( 128 = 2 hoch 7 )
( 64 = 2 hoch 6 )
( 32 = 2 hoch 5 )
( 16 = 2 hoch 4 )
(
8 = 2 hoch 3 )
(
4 = 2 hoch 2 )
(
2 = 2 hoch 1 )
(
1 = 2 hoch 0 )
=
+
+
+
+
+
+
+
1
1
1
1
1
1
1
1
*
*
*
*
*
*
*
*
128
64
32
16
8
4
2
1
Binr
0b00000000
0b00000001
0b00000010
0b00000011
0b00000100
0b00000101
...
0b01111100
0b01111101
0b01111110
Dezimal
0
1
2
3
4
5
124
125
126
Binr
0b10000000
0b10000001
0b10000010
0b10000011
0b10000100
0b10000101
...
0b11111100
0b11111101
0b11111110
Dezimal
128
129
130
131
132
133
252
253
254
0b01111111
127
0b11111111
[Bearbeiten]Die Umwandlung von Dezimal in das Binrsystem
255
Aus dem vorhergehenden ergibt sich vllig zwanglos die Vorschrift, wie Binrzahlen ins
Dezimalsystem umgewandelt werden knnen (nicht vergessen: Die Zahl selber wird ja gar nicht
verndert. Binr- und Dezimalsystem sind ja nur verschiedene Schreibweisen): Durch
Anwendung der Vorschrift, wie denn eigentlich ein Stellenwertsystem aufgebaut ist. Aber wie
macht man den umgekehrten Schritt, die Wandlung vom Dezimal ins Binrsystem. Der Weg
fhrt ber die Umkehrung des vorhergehenden Prinzips. Fortgesetzte Division durch 2
Es sei die Zahl 92 ins Binrsystem zu wandeln.
92
46
23
11
5
2
1
/
/
/
/
/
/
/
2
2
2
2
2
2
2
=
=
=
=
=
=
=
46
23
11
5
2
1
0
Rest
Rest
Rest
Rest
Rest
Rest
Rest
0
0
1
1
1
0
1
Die Division wird solange durchgefhrt, bis sich ein Divisionergebnis von 0 ergibt. Die Reste,
von unten nach oben gelesen, ergeben dann die Binrzahl. Die zu 92 gehrende Binrzahl
lautet also 1011100. Es wird noch eine fhrende 0 ergnzt um sie auf die standardmssigen 8Bit zu bringen: 0b01011100.
[Bearbeiten]8-Bit
Soll mit Vorzeichen (also positiven und negativen Zahlen) gerechnet werden, so erhebt sich die
Frage: Wie werden eigentlich positive bzw. negative Zahlen dargestellt? Alles was wir haben
sind ja 8 Bit in einem Byte.
[Bearbeiten]Problem der Kodierung des Vorzeichens
Die Lsung des Problems besteht darin, dass ein Bit zur Anzeige des Vorzeichens benutzt wird.
Im Regelfall wird dazu das am weitesten links stehende Bit benutzt. Von den verschiedenen
Mglichkeiten, die sich hiermit bieten, wird in der Praxis fast ausschlielich mit dem sog. 2-er
Komplement gearbeitet, da es Vorteile bei der Addition bzw. Subtraktion von Zahlen bringt. In
diesem Fall mu nmlich das Vorzeichen einer Zahl berhaupt nicht bercksichtigt werden.
Durch die Art und Weise der Bildung von negativen Zahlen kommt am Ende das Ergebnis mit
dem korrekten Vorzeichen heraus.
[Bearbeiten]2-er Komplement
Das 2-er Komplement verwendet das hchstwertige Bit eines Byte, das sog. MSB
(= Most Significant Bit) zur Anzeige des Vorzeichens. Ist dieses Bit 0, so ist die Zahl positiv. Ist
es 1, so handelt es sich um eine negative Zahl. Die 8-Bit Kombination 0b10010011 stellt also
eine negative Zahl dar, wenn und nur wenn diese Bitkombination berhaupt als
vorzeichenbehaftete Zahl aufgefasst werden soll. Anhand der Bitkombination alleine ist es
also nicht mglich, eine definitive Aussage zu treffen, ob es sich um eine vorzeichenbehaftete
Zahl handelt oder nicht. Erst wenn durch den Zusammenhang klar ist, dass man es mit
vorzeichenbehafteten Zahlen zu tun hat, bekommt das MSB die Sonderbedeutung des
Vorzeichens.
Um bei einer Zahl das Vorzeichen zu wechseln, geht man wie folgt vor:
Zunchst wird das 1-er Komplement gebildet, indem alle Bits umgedreht werden. Aus 0
wird 1 und aus 1 wird 0
Danach wird aus diesem Zwischenergebnis das 2-er Komplement gebildet, indem noch
1 addiert wird.
Diese Vorschrift kann immer dann benutzt werden, wenn das Vorzeichen einer Zahl gewechselt
werden soll. Er macht aus positiven Zahlen negative und aus negativen Zahlen positive.
Beispiel: Es soll die Binrdarstellung fr -92 gebildet werden. Dazu bentigt man zunchst die
Binrdarstellung fr +92, welche 0b01011100 lautet. Diese wird jetzt nach der Vorschrift fr 2-er
Komplemente negiert und damit negativ gemacht.
0b01011100
0b10100011
0b10100100
Ausgangszahl
1-er Komplement, alle Bits umdrehen
noch 1 addieren
Die Binrdarstellung fr -92 lautet also 0b10100100. Das gesetzte MSB weist diese Binrzahl
auch tatschlich als negative Zahl aus.
Beispiel: Gegeben sei die Binrzahl 0b00111000, welche als vorzeichenbehaftete Zahl
anzusehen ist. Welcher Dezimalzahl entspricht diese Binrzahl?
Da das MSB nicht gesetzt ist, handelt es sich um eine positive Zahl und die Umrechnung kann
wie im Fall der vorzeichenlosen 8-Bit Zahlen erfolgen. Das Ergebnis lautet also +56 ( = 0 * 128
+ 0 * 64 + 1 * 32 + 1 * 16 + 1 * 8 + 0 * 4 + 0 * 2 + 0 * 1 )
Beispiel: Gegeben sei die Binrzahl 0b10011001, welche als vorzeichenbehaftete Zahl
anzusehen ist. Welcher Dezimalzahl entspricht diese Binrzahl?
Da das MSB gesetzt ist, handelt es sich um eine negative Zahl. Daher wird diese Zahl zunchst
negiert um dadurch eine positive Zahl zu erhalten.
0b10011001
0b01100110
0b01100111
Originalzahl
1-er Komplement, alle Bits umdrehen
2-er Komplement, noch 1 addiert
Die zu 0b10011001 gehrende positive Binrzahl lautet also 0b01100111. Da es sich um eine
positive Zahl handelt, kann sie wiederum ganz normal, wie vorzeichenlose Zahlen, in eine
Dezimalzahl umgerechnet werden. Das Ergebnis lautet 103 ( = 0 * 128 + 1 * 64 + 1 * 32 + 0 * 16
+ 0 * 8 + 1 * 4 + 1 * 2 + 1 * 1). Da aber von einer negativen Zahl ausgegangen wurde,
ist 0b10011001 die binre Darstellung der Dezimalzahl -103.
Beispiel: Gegeben sei dieselbe Binrzahl 0b10011001. Aber diesmal sei sie als vorzeichenlose
Zahl aufzufassen. Welcher Dezimalzahl entspricht diese Binrzahl?
Da die Binrzahl als vorzeichenlose Zahl aufzufassen ist, hat das MSB keine spezielle
Bedeutung. Die Umrechnung erfolgt also ganz normal: 0b10011001 = 1 * 128 + 0 * 64 + 0 * 32
+ 1 * 16 + 1 * 8 + 0 * 4 + 0 * 2 + 1 * 1 = 153.
Beispiel: Wie lautet die Binrzahl zu -74?
Da es sich hier offensichtlich im eine vorzeichenbehaftete Zahl handelt, mssen die Regeln des
2-er Komplemnts angewendet werden. Zunchst ist also die Binrreprsentierung von +74 zu
bestimmen, welche dann durch Anwendung des 2-er Komplements negiert wird.
74
37
18
9
4
2
1
/
/
/
/
/
/
/
2
2
2
2
2
2
2
= 37
= 18
= 9
= 4
= 2
= 1
= 0
Rest
Rest
Rest
Rest
Rest
Rest
Rest
0
1
0
1
0
0
1
+74
1-er Komplement, alle Bits umdrehen
noch 1 addieren
- 11110011
--------10110000
verbleibt an der hchstwertigsten Stelle ein "geborgtes" Bit, welches durch das Carry angezeigt
wird.
[Bearbeiten]Signed- und Overflowflag
Wird mit vorzeichenbehafteten Zahlen gerechnet, so wird das Verlassen des 8-BitZahlenbereiches -128...+127 durch die Flags S und V angezeigt. Der Prozessor selbst
unterscheidet nicht zwischen vorzeichenlosen und -behafteten Zahlen. Der Programmierer legt
durch Wahl der ausgewerteten Flags (C bei vorzeichenlosen bzw. S/V bei
vorzeichenbehafteten) die Interpretation der Zahlen fest.
[Bearbeiten]Weitere Flags
Das Zero-Flag Z wird gesetzt, wenn das 8-Bit-Ergebnis der Berechnung null ist. Dies kann bei
der Addition auch durch berlauf geschehen, was durch ein zustzliches Carryflag angezeigt
wird.
Das Negative-Flag N wird gesetzt, wenn im Ergebnis das hchstwertige Bit gesetzt ist. Bei
vorzeichenbehafteter Arithmetik ist dies als negative Zahl zu interpretieren, sofern nicht durch
das V-Flag ein Verlassen des Zahlbereichs angezeigt wird.
Das Half-Carry-Flag H zeigt, analog zum Carry-Flag, einen bertrag zwischen Bit 3 und 4 an.
Dies kann in speziellen Anwendungen (z. B. zwei simultane Vier-Bit-Berechnungen mit einem
Befehl) ntzlich sein.
[Bearbeiten]bersicht ber die arithmetischen Flags
Ergebnis des Befehls ADD Rd, Rr
Rr|
0 |
1 |
64 | 127 | 128 | 129 | 192 | 255
Rd
|( +0)|( +1)|( +64)|(+127)|(-128)|(-127)|( -64)|( -1)
----------+------+------+------+------+------+------+------+-----0 ( +0)|
Z |
|
|
|S N
|S N
|S N
|S N
1 ( +1)|
|
|
| VN
|S N
|S N
|S N
|
CZ
64 ( +64)|
|
| VN
| VN
|S N
|S N
|
CZ |
C
127 (+127)|
| VN
| VN
| VN
|S N
|
CZ |
C |
C
128 (-128)|S N
|S N
|S N
|S N
|SV CZ |SV C |SV C |SV C
129 (-127)|S N
|S N
|S N
|
CZ |SV C |SV C |SV C |S NC
192 ( -64)|S N
|S N
|
CZ |
C |SV C |SV C |S NC |S NC
255 ( -1)|S N
|
CZ |
C |
C |SV C |S NC |S NC |S NC
Man erkennt: C=1 genau dann wenn die Addition Rd + Rr mit vorzeichenlosen Zahlen berluft.
V=1 genau dann wenn die Addition mit vorzeichenbehafteten Zahlen berluft.
0 |
63 |
64 |
127 |
128 |
191 |
192 |
255
Rd
|( +0)|( +63)|( +64)|(+127)|(-128)|( -65)|( -64)|( -1)
----------+------+------+------+------+------+------+------+-----0 ( +0)|
Z |S NC |S NC |S NC | VNC |
C |
C |
C
63 ( +63)|
|
Z |S NC |S NC | VNC | VNC |
C |
C
64 ( +64)|
|
|
Z |S NC | VNC | VNC | VNC |
C
127 (+127)|
|
|
|
Z | VNC | VNC | VNC | VNC
128 (-128)|S N
|SV
|SV
|SV
|
Z |S NC |S NC |S NC
191 ( -65)|S N
|S N
|SV
|SV
|
|
Z |S NC |S NC
192 ( -64)|S N
|S N
|S N
|SV
|
|
|
Z |S NC
255 ( -1)|S N
|S N
|S N
|S N
|
|
|
|
Z
Man erkennt: C=1 genau dann wenn die Subtraktion Rd - Rr mit vorzeichenlosen Zahlen
unterluft; quivalent dazu ist Rd < Rr (vorzeichenlos). S=1 genau dann wenn die Subtraktion
mit vorzeichenbehafteten Zahlen unterluft bzw. Rd > Rr (vorzeichenbehaftet).
[Bearbeiten]Inkrementieren
/ Dekrementieren
Erstaunlich viele Operationen in einem Computer-Programm entfallen auf die Operationen 'Zu
einer Zahl 1 addieren' bzw. 'Von einer Zahl 1 subtrahieren'. Dementsprechend enthalten die
meisten Mikroprozessoren die Operationen Inkrementieren (um 1 erhhen)
bzw. Dekrementieren (um 1 verringern) als eigenstndigen Assemblerbefehl. So auch der
ATmega8.
[Bearbeiten]AVR-Befehle
inc
r16
dec
r16
bzw.
Die Operation ist einfach zu verstehen. Das jeweils angegebene Register (hier wieder am
Register r16 gezeigt) wird um 1 erhht bzw. um 1 verringert. Dabei wird die Zahl im Register als
vorzeichenlose Zahl angesehen. Enthlt das Register bereits die grtmgliche Zahl
(0b11111111 oder dezimal 255), so erzeugt ein weiteres Inkrementieren die kleinstmgliche Zahl
(0b00000000 oder dezimal 0) bzw. umgekehrt dekrementiert 0 zu 255.
[Bearbeiten]Addition
Auf einem Mega8 gibt es nur eine Mglichkeit, um eine Addition durchzufhren: Die beiden zu
addierenden Zahlen mssen in zwei Registern stehen.
[Bearbeiten]AVR-Befehle
add
r16, r17
adc
r16, r17
;
;
;
;
Bei der Addition zweier Register wird ein mglicher berlauf in allen Fllen im Carry Bit
abgelegt. Daraus erklrt sich dann auch das Vorhandensein eines Additionsbefehls, der das
Carry-Bit noch zustzlich mitaddiert: Man bentigt ihn zum Aufbau einer Addition die mehr als 8
Bit umfasst. Die niederwertigsten Bytes werden mit einem add addiert und alle weiteren
hherwertigen Bytes werden, vom Niederwertigsten zum Hchstwertigsten, mittels adc addiert.
Dadurch werden eventuelle bertrge automatisch bercksichtigt.
[Bearbeiten]Subtraktion
Subtraktionen knnen auf einem AVR in zwei unterschiedlichen Arten ausgefhrt werden.
Entweder es werden zwei Register voneinander subtrahiert oder es wird von einem Register
eine konstante Zahl abgezogen. Beide Varianten gibt es wiederum in den Ausfhrungen mit und
ohne Bercksichtigung des Carry Flags
[Bearbeiten]AVR-Befehle
sub
r16, r17
sbc
r16, r17
;
;
;
;
;
;
;
;
;
;
[Bearbeiten]Multiplikation
Multiplikation kann auf einem AVR je nach konkretem Typ auf zwei unterschiedliche Arten
ausgefhrt werden. Whrend die greren ATMega Prozessoren ber einen
Hardwaremultiplizierer verfgen, ist dieser bei den kleineren Tiny Prozessoren nicht vorhanden.
Hier mu die Multiplikation quasi zu Fu durch entsprechende Addition von Teilresultaten
erfolgen.
[Bearbeiten]Hardwaremultiplikation
Vorzeichenbehaftete und vorzeichenlose Zahlen werden unterschiedlich multipliziert. Denn im
Falle eines Vorzeichens darf ein gesetztes 7. Bit natrlich nicht in die eigentliche Berechnung
mit einbezogen werden. Statt dessen steuert dieses Bit (eigentlich die beiden MSB der beiden
beteiligten Zahlen) das Vorzeichen des Ergebnisses. Die Hardwaremultiplikation ist auch
dahingehend eingeschrnkt, dass das Ergebnis einer Multiplikation immer in den
Registerprchen r0 und r1 zu finden ist. Dabei steht das LowByte (also die unteren 8 Bit) des
Ergebnisses in r0 und das HighByte in r1.
[Bearbeiten]AVR-Befehl
mul
r16, r17
;
;
;
;
muls
r16, r17
;
;
;
;
;
+
+
+
Im Binrsystem funktioniert Multiplikation vllig analog. Nur ist hier das kleine Einmaleins sehr
viel einfacher! Es gibt nur 4 Multiplikationen (anstatt 100 im Dezimalsystem):
0
0
1
1
*
*
*
*
0
1
0
1
=
=
=
=
0
0
0
1
Es gibt lediglich einen kleinen Unterschied gegenber dem Dezimalsystem: Anstatt zunchst
alle Zwischenergebnisse aufzulisten und erst danach die Summe zu bestimmen, werden wir ein
neues Zwischenergebnis gleich in die Summe einrechnen. Dies deshalb, da Additionen von
mehreren Zahlen im Binrsystem im Kopf sehr leicht zu Flchtigkeitsfehlern fhren (durch die
vielen 0-en und 1-en). Weiters wird eine einfache Tatsache benutzt: 1 mal eine Zahl ergibt
wieder die Zahl, whrend 0 mal eine Zahl immer 0 ergibt. Dadurch braucht man im Grunde bei
einer Multiplikation berhaupt nicht zu multiplizieren, sondern eigentlich nur die Entscheidung
treffen: Muss die Zahl geeignet verschoben addiert werden oder nicht?
0b00100011
* 0b10001001
-------------------------------00100011
<--+|||||||
00000000
<---+||||||
--------||||||
001000110
||||||
00000000
<----+|||||
---------|||||
0010001100
|||||
00000000
<-----+||||
----------||||
00100011000
||||
00100011
-----------001001010011
00000000
------------0010010100110
00000000
-------------00100101001100
00100011
--------------001001010111011
<------+|||
|||
|||
<-------+||
||
||
<--------+|
|
|
<---------+
Man sieht auch, wie bei der Multiplikation zweier 8 Bit Zahlen sehr schnell ein 16 Bit Ergebnis
entsteht. Dies ist auch der Grund, warum die Hardwaremultiplikation immer 2 Register zur
Aufnahme des Ergebnisses bentigt.
Ein Assembler Code, der diese Strategie im wesentlichen verwirklicht, sieht z. B. so aus. Dieser
Code wurde nicht auf optimale Laufzeit getrimmt, sondern es soll im Wesentlichen eine 1:1
Umsetzung des oben gezeigten Schemas sein. Einige der verwendeten Befehle wurden im
Rahmen dieses Tutorials an dieser Stelle noch nicht besprochen. Speziell die Schiebe- (lsl) und
Rotier- (rol) Befehle sollten in der AVR Befehlsbersicht genau studiert werden, um ihr
Zusammenspiel mit dem Carry Flag zu verstehen. Nur soviel als Hinweis: Das Carry Flag dient
in der lsl / rol Sequenz als eine Art Zwischenspeicher, um das hherwertigste Bit aus dem
Register r0 beim Verschieben in das Register r1 verschieben zu knnen. Der lsl verschiebt alle
Bits des Registers um 1 Stelle nach links, wobei das vorhergehende MSB ins Carry Bit wandert
und rechts ein 0-Bit nachrckt. Der rol verschiebt ebenfalls alle Stellen eines Registers um 1
Stelle nach links. Diesmal wird aber rechts nicht mit einem 0-Bit aufgefllt, sondern an dieser
Stelle wird der momentane Inhalt des Carry Bits eingesetzt.
ldi
ldi
r16, 0b00100011
r17, 0b10001001
ldi
clr
clr
clr
r18, 8
r19
r0
r1
;
;
;
;
r0
r1
r17
noadd
r0,r16
r1,r19
mult:
lsl
rol
lsl
brcc
add
adc
noadd:
dec r18
brne mult
; Multiplikator
; Multiplikand
; Berechne r16 * r17
8 mal verschieben und gegebenenfalls addieren
0 wird fr die 16 Bit Addition bentigt
Ergebnis Low Byte auf 0 setzen
Ergebnis High Byte auf 0 setzen
[Bearbeiten]Division
Anders als bei der Multiplikation, gibt es auch auf einem ATMega-Prozessor keine
hardwaremssige Divisionseinheit. Divisionen mssen also in jedem Fall mit einer speziellen
Routine, die im wesentlichen auf Subtraktionen beruht, erledigt werden.
[Bearbeiten]Division in Software
Um die Vorgangsweise bei der binren Division zu verstehen, wollen wir wieder zunchst
anhand der gewohnten dezimalen Division untersuchen wie sowas abluft.
Angenommen es soll dividiert werden: 938 / 4 ( 938 ist der Dividend, 4 ist der Divisor)
Wie haben Sie es in der Grundschule gelernt? Wahrscheinlich so wie der Autor auch:
938 : 4 = 234
---8
---1
13
-12
--1
18
-16
-2 Rest
Der Vorgang war: Man nimmt die erste Stelle des Dividenden (9) und ruft seine gespeicherte
Einmaleins Tabelle ab, um festzustellen, wie oft der Divisor in dieser Stelle enthalten ist. In
diesem konkreten Fall ist die erste Stelle 9 und der Divisor 4. 4 ist in 9 zweimal enthalten. Also
ist 2 die erste Ziffer des Ergebnisses. 2 mal 4 ergibt aber 8 und diese 8 werden von den 9
abgezogen, brig bleibt 1. Aus dem Dividenden wird die nchste Ziffer (3) heruntergezogen und
man erhlt mit der 1 aus dem vorhergehenden Schritt 13. Wieder dasselbe Spiel: Wie oft ist 4 in
13 enthalten? 3 mal (3 ist die nchste Ziffer des Ergebnisses) und 3 * 4 ergibt 12. Diese 12 von
den 13 abgezogen macht 1. Zu dieser 1 gesellt sich wieder die nchste Ziffer des Dividenden,
8, um so 18 zu bilden. Wie oft ist 4 in 18 enthalten? 4 mal (4 ist die nchste Ziffer des
Ergebnisses), denn 4 mal 4 macht 16, und das von den 18 abgezogen ergibt 2. Da es keine
nchste Ziffer im Dividenden mehr gibt, lautet also das Resultat: 938 : 4 ergibt 234 und es
bleiben 2 Rest.
Die binre Division funktioniert dazu vllig analog. Es gibt nur einen kleinen Unterschied, der
einem sogar das Leben leichter macht. Es geht um den Schritt: Wie oft ist x in y enthalten?
Dieser Schritt ist in der binren Division besonders einfach, da das Ergebnis dieser
Fragestellung nur 0 oder 1 sein kann. Das bedeutet aber auch: Entweder ist der Divisior in der
zu untersuchenden Zahl enthalten, oder er ist es nicht. Das kann aber ganz leicht entschieden
werden: Ist die Zahl grer oder gleich dem Divisior, dann ist der Divisor enthalten und zum
Ergebnis kann eine 1 hinzugefgt werden. Ist die Zahl kleiner als der Divisior, dann ist der
Divisior nicht enthalten und die nchste Ziffer des Ergebnisses ist eine 0.
Die Division liefert also das Ergebnis 0b00001100, wobei ein Rest von 1 bleibt. Der Dividend
0b01101101 entspricht der Dezimalzahl 109, der Divisor 0b00001001 der Dezimalzahl 9. Und
wie man sich mit einem Taschenrechner leicht berzeugen kann, ergibt die Division von 109
durch 9 einen Wert von 12, wobei 1 Rest bleibt. Die Binrzahl fr 12 lautet 0b00001100, das
Ergebnis stimmt also.
ldi
r16, 109
; Dividend
ldi
r17,
; Divisor
; Division r16 : r17
ldi
clr
clr
r18,
r19
r20
divloop:
lsl r16
rol r19
; 8 Bit Division
; Register fr die Zwischenergebnisse / Rest
; Ergebnis
; Zwischenergebnis mal 2 nehmen und das
; nchste Bit des Dividenden anhngen
lsl
r20
;
;
;
;
cp
brlo
sbr
sub
r19, r17
div_zero
r20, 1
r19, r17
;
;
;
;
div_zero:
dec r18
brne divloop
[Bearbeiten]Arithmetik
Es gibt eine Sammlung von Algorithmen zur AVR-Arithmetik mit mehr als 8 Bit, deren
Grundprinzipien im wesentlichen identisch zu den in diesem Teil ausgefhrten Prinzipien sind.
AVR-Tutorial: Stack
"Stack" bedeutet bersetzt soviel wie Stapel. Damit ist ein Speicher nach dem LIFO-Prinzip
("last in first out") gemeint. Das bedeutet, dass das zuletzt auf den Stapel gelegte Element auch
zuerst wieder heruntergenommen wird. Es ist nicht mglich, Elemente irgendwo in der Mitte des
Stapels herauszuziehen oder hineinzuschieben.
Bei allen aktuellen AVR-Controllern wird der Stack im RAM angelegt. Der Stack wchst dabei
von oben nach unten: Am Anfang wird der Stackpointer (Adresse der aktuellen Stapelposition)
auf das Ende des RAMs gesetzt. Wird nun ein Element hinzugefgt, wird dieses an der
momentanen Stackpointerposition abgespeichert und der Stackpointer um 1 erniedrigt. Soll ein
Element vom Stack heruntergenommen werden, wird zuerst der Stackpointer um 1 erhht und
dann das Byte von der vom Stackpointer angezeigten Position gelesen.
Inhaltsverzeichnis
[Verbergen]
[Bearbeiten]Aufruf
von Unterprogrammen
Dem Prozessor dient der Stack hauptschlich dazu, Rcksprungadressen beim Aufruf von
Unterprogrammen zu speichern, damit er spter noch wei, an welche Stelle zurckgekehrt
werden muss, wenn das Unterprogramm mit ret oder die Interruptroutine mit reti beendet wird.
Das folgende Beispielprogramm (AT90S4433) zeigt, wie der Stack dabei beeinflusst wird:
Download stack.asm
.include "4433def.inc"
; bzw. 2333def.inc
loop:
; Stackpointer initialisieren
rcall sub1
; sub1 aufrufen
rjmp loop
sub1:
ret
;
;
;
;
ret
rcall sub2
sub2:
.def temp = r16 ist eine Assemblerdirektive. Diese sagt dem Assembler, dass er berall, wo er
"temp" findet, stattdessen "r16" einsetzen soll. Das ist oft praktisch, damit man nicht mit den
Registernamen durcheinander kommt. Eine bersicht ber die Assemblerdirektiven findet
man hier.
Bei Controllern, die mehr als 256 Byte RAM besitzen (z. B. ATmega8), passt die Adresse nicht
mehr in ein Byte. Deswegen gibt es bei diesen Controllern das Stack-Pointer-Register aufgeteilt
inSPL (Low) und SPH (High), in denen das Low- und das High-Byte der Adresse gespeichert
wird. Damit es funktioniert, muss das Programm dann folgendermaen gendert werden:
Download stack-bigmem.asm
.include "m8def.inc"
.def temp = r16
ldi
out
ldi
out
temp, HIGH(RAMEND)
SPH, temp
temp, LOW(RAMEND)
SPL, temp
rcall sub1
loop:
rjmp loop
sub1:
ret
;
;
;
;
ret
rcall sub2
sub2:
Stack Pointer: Adresse im Datenspeicher (RAM), auf die der Stackpointer gerade zeigt
Time Elapsed: Zeit, die seit dem Beginn der Simulation vergangen ist
Wenn der gelbe Pfeil in der Zeile out SPL, temp vorbeikommt, kann man im Prozessor-Fenster
sehen, wie der Stackpointer auf 0xDF (ATmega8: 0x45F) gesetzt wird. Wie man im MemoryFenster sieht, ist das die letzte RAM-Adresse.
Wenn der Pfeil auf dem Befehl rcall sub1 steht, sollte man sich den Program Counter
anschauen: Er steht auf 0x02.
Drckt man jetzt nochmal auf F11, springt der Pfeil zum Unterprogramm sub1. Im RAM
erscheint an der Stelle, auf die der Stackpointer vorher zeigte, die Zahl 0x03. Das ist die
Adresse im ROM, an der das Hauptprogramm nach dem Abarbeiten des Unterprogramms
fortgesetzt wird. Doch warum wurde der Stackpointer um 2 verkleinert? Das liegt daran, dass
eine Programmspeicheradresse bis zu 2 Byte breit sein kann, und somit auch 2 Byte auf dem
Stack bentigt werden, um die Adresse zu speichern.
Das gleiche passiert beim Aufruf von sub2.
Zur Rckkehr aus dem mit rcall aufgerufenen Unterprogramm gibt es den Befehl ret. Dieser
Befehl sorgt dafr, dass der Stackpointer wieder um 2 erhht wird und die dabei eingelesene
Adresse in den "Program Counter" kopiert wird, so dass das Programm dort fortgesetzt wird.
Apropos Program Counter: Wer sehen will, wie so ein Programm aussieht, wenn es assembliert
ist, sollte mal die Datei mit der Endung ".lst" im Projektverzeichnis ffnen. Die Datei sollte
ungefhr so aussehen:
Im blau umrahmten Bereich steht die Adresse des Befehls im Programmspeicher. Das ist auch
die Zahl, die im Program Counter angezeigt wird, und die beim Aufruf eines Unterprogramms
auf den Stack gelegt wird. Der grne Bereich rechts daneben ist der OP-Code des Befehls, so
wie er in den Programmspeicher des Controllers programmiert wird, und im roten Kasten stehen
die "mnemonics": Das sind die Befehle, die man im Assembler eingibt. Der nicht eingerahmte
Rest besteht aus Assemblerdirektiven, Labels (Sprungmarkierungen) und Kommentaren, die
nicht direkt in OP-Code umgewandelt werden. Der grn eingerahmte Bereich ist das eigentliche
Programm, so wie es der C versteht. Die jeweils erste Zahl im grnen Bereich steht fr einen
Befehl, den sog. OP-Code (OP = Operation). Die zweite Zahl codiert Argumente fr diesen
Befehl.
[Bearbeiten]Sichern
von Registern
Eine weitere Anwendung des Stacks ist das "Sichern" von Registern. Wenn man z. B. im
Hauptprogramm die Register R16, R17 und R18 verwendet, dann ist es i.d.R. erwnscht, dass
diese Register durch aufgerufene Unterprogramme nicht beeinflusst werden. Man muss also
nun entweder auf die Verwendung dieser Register innerhalb von Unterprogrammen verzichten,
oder man sorgt dafr, dass am Ende jedes Unterprogramms der ursprngliche Zustand der
Register wiederhergestellt wird. Wie man sich leicht vorstellen kann ist ein "Stapelspeicher"
dafr ideal: Zu Beginn des Unterprogramms legt man die Daten aus den zu sichernden
Registern oben auf den Stapel, und am Ende holt man sie wieder (in der umgekehrten
Reihenfolge) in die entsprechenden Register zurck. Das Hauptprogramm bekommt also wenn
es fortgesetzt wird berhaupt nichts davon mit, dass die Register inzwischen anderweitig
verwendet wurden.
Download stack-saveregs.asm
.include "4433def.inc"
; bzw. 2333def.inc
; Port B = Ausgang
rcall sub1
loop:
; Stackpointer initialisieren
rjmp loop
; Endlosschleife
push R17
sub1:
; hier kann nach belieben mit R17 gearbeitet werden,
; als Beispiel wird es hier auf 0 gesetzt
ldi R17, 0
pop R17
ret
; R17 zurckholen
; wieder zurck zum Hauptprogramm
Wenn man dieses Programm assembliert und in den Controller ldt, dann wird man feststellen,
dass jede zweite LED an Port B leuchtet. Der ursprngliche Wert von R17 blieb also erhalten,
obwohl dazwischen ein Unterprogramm aufgerufen wurde, das R17 gendert hat.
Auch in diesem Fall kann man bei der Simulation des Programms im AVR-Studio die
Beeinflussung des Stacks durch die Befehle push und pop genau nachvollziehen.
[Bearbeiten]Sprung
zu beliebiger Adresse
Kleinere AVR besitzen keinen Befehl, um direkt zu einer Adresse zu springen, die in einem
Registerpaar gespeichert ist. Man kann dies aber mit etwas Stack-Akrobatik erreichen. Dazu
einfach zuerst den niederen Teil der Adresse, dann den hheren Teil der Adresse mit push auf
den Stack legen und ein ret ausfhren:
ldi ZH, high(testRoutine)
ldi ZL, low(testRoutine)
push ZL
push ZH
ret
...
testRoutine:
rjmp testRoutine
Auf diese Art und Weise kann man auch Unterprogrammaufrufe durchfhren:
ldi ZH, high(testRoutine)
ldi ZL, low(testRoutine)
rcall indirectZCall
...
indirectZCall:
push ZL
push ZH
ret
testRoutine:
...
ret
Grere AVR haben dafr die Befehle ijmp und icall. Bei diesen Befehlen muss das
Sprungziel in ZH:ZL stehen.
[Bearbeiten]Weitere
(Der in dieser Abhandlung angegebene Befehl MOV ZLow, SPL muss fr einen ATmega8 IN
ZL, SPL heien, da hier SPL und SPH ein I/O-Register sind. Ggf ist auch SPH zu
bercksichtigen --> 2byte Stack-Pointer)
AVR-Tutorial: LCD
Kaum ein elektronisches Gert kommt heutzutage noch ohne ein LCD daher. Ist doch auch
praktisch, Informationen im Klartext anzeigen zu knnen, ohne irgendwelche LEDs blinken zu
lassen. Kein Wunder also, dass die hufigste Frage in Mikrocontroller-Foren ist: "Wie kann ich
ein LCD anschlieen?"
Inhaltsverzeichnis
[Verbergen]
1 Das LCD und sein Controller
2 Anschluss an den Controller
3 Ansteuerung des LCDs im 4-Bit-Modus
4 Initialisierung des Displays
o
9.2 Registerbenutzung
[Bearbeiten]Das
Die meisten Text-LCDs verwenden den Controller HD44780 oder einen kompatiblen (z. B.
KS0070) und haben 14 oder 16 Pins.
Die Pinbelegung ist meist (Ausnahme z. B. TC1602E (Pollin 120420): VDD und VSS vertauscht)
folgendermaen:
ACHTUNG: Es gibt Displays mit abweichender Anschluss-Belegung, falscher Anschluss kann
zur Zerstrung fhren! Daher immer das zugehrige Datenblatt zu Rate ziehen!
Einzelheiten unter Artikel zum Controller HD44780
Pin #
Bezeichnung
Funktion
GND (selten: +5 V)
+5 V (selten: GND)
VEE, V0, V5
RS
RW
1=Read 0=Write
DB0
Datenbit 0
DB1
Datenbit 1
DB2
Datenbit 2
10
DB3
Datenbit 3
11
DB4
Datenbit 4
12
DB5
Datenbit 5
13
DB6
Datenbit 6
14
DB7
Datenbit 7
15
0=Disable 1=Enable
16
Achtung: Unbedingt von der richtigen Seite zu zhlen anfangen! Meistens ist das Pin1-Pad
eckig oder daneben eine kleine 1 auf der LCD-Platine, ansonsten im Datenblatt nachschauen.
Bei der DIL-Version (2x7, 2x8 Kontakte) auch darauf achten, auf welcher Platinen-Seite der
Stecker montiert wird: auf der falschen (meist hinteren) Seite sind dann die Flachbandleitungen
1 und 2, 3 und 4 usw. vertauscht. Das kann man kompensieren, indem man es auf der anderen
Kabelseite genauso permutiert oder es auf dem Layout bewusst so legt (Stecker auf der
Bottom-Seite plazieren). Man kann es NICHT kompensieren, indem man das Flachbandkabel
auf der anderen Seite in den Stecker fhrt.
Bei LCDs mit 16-poligem Anschluss sind die beiden letzten Pins fr die Hintergrundbeleuchtung
reserviert. Hier unbedingt das Datenblatt zu Rate ziehen. Die beiden Anschlsse sind je nach
Hersteller verdreht beschaltet. Falls kein Datenblatt vorliegt, kann man mit einem
Durchgangsprfer feststellen, welcher Anschluss mit Masse (GND) verbunden ist.
VSS wird ganz einfach an GND angeschlossen und VCC=VDD an +5 V. VEE = V0 = V5 kann man
testweise auch an GND legen. Wenn das LCD dann zu dunkel sein sollte, muss man ein 10kPotentiometer zwischen GND und 5 V schalten, mit dem Schleifer an V EE. Meist kann man den
+5 V-Anschluss am Poti weglassen, da im Display ein Pull-up-Widerstand ist:
Wenn der Kontrast zu schwach sein sollte (z.B. bei tiefen Temperaturen), kann man anstelle
von GND eine negative Spannung ans Kontrast-Poti legen. Diese kann bis -5 V gehen und kann
leicht aus einem Timerpin des C, einem Widerstand, zwei Dioden und zwei Kondensatoren
erzeugt werden. So wird auch ein digital einstellbarer Kontrast mittels PWM ermglicht.
Es gibt zwei verschiedene Mglichkeiten zur Ansteuerung eines solchen Displays: den 8Bit- und den 4-Bit-Modus.
Fr den 8-Bit-Modus werden (wie der Name schon sagt) alle acht Datenleitungen zur
Ansteuerung verwendet, somit kann durch einen Zugriff immer ein ganzes Byte
bertragen werden.
Der 4-Bit-Modus verwendet nur die oberen vier Datenleitungen (DB4-DB7). Um ein
Byte zu bertragen, braucht man somit zwei Zugriffe, wobei zuerst das
hherwertige "Nibble" (= 4 Bits), also Bit 4 bis Bit 7 bertragen wird und dann das
niederwertige, also Bit 0 bis Bit 3. Die unteren Datenleitungen des LCDs, die beim
Lesezyklus Ausgnge sind, lsst man offen (siehe Datasheets, z. B. vom KS0070).
Der 4-Bit-Modus hat den Vorteil, dass man 4 IO-Pins weniger bentigt als beim 8-Bit-Modus. 6
bzw. 7 Pins (eines Portes) reichen aus.
Neben den vier Datenleitungen (DB4, DB5, DB6 und DB7) werden noch die
Anschlsse RS, RW und E bentigt.
ber RS wird ausgewhlt, ob man einen Befehl oder ein Datenbyte an das LCD
schicken mchte. Beim Schreiben gilt: ist RS Low, dann wird das ankommende Byte als
Befehl interpretiert; Ist RS high, wird das Byte auf dem LCD angezeigt (genauer: ins
Data-Register geschrieben, kann auch fr den CG bestimmt sein).
RW legt fest, ob geschrieben oder gelesen werden soll. High bedeutet lesen, low
bedeutet schreiben. Wenn man RW auf lesen einstellt und RS auf Befehl, dann kann
man das Busy-Flag an DB7 lesen, das anzeigt, ob das LCD den vorhergehenden
Befehl fertig verarbeitet hat. Ist RS auf Daten eingestellt, dann kann man z. B. den
Inhalt des Displays lesen - was jedoch nur in den wenigsten Fllen Sinn macht.
Deshalb kann man RW dauerhaft auf low lassen (= an GND anschlieen), so dass man
noch ein IO-Pin am Controller einspart. Der Nachteil ist, dass man dann das Busy-Flag
nicht lesen kann, weswegen man nach jedem Befehl ca. 50 s (beim Return Home 2
ms, beim Clear Display 20 ms) warten sollte, um dem LCD Zeit zum Ausfhren des
Befehls zu geben. Dummerweise schwankt die Ausfhrungszeit von Display zu Display
und ist auch von der Betriebsspannung abhngig. Fr professionellere Sachen also
lieber den IO-Pin opfern und Busy abfragen.
Der E Anschluss schlielich signalisiert dem LCD, dass die brigen Datenleitungen jetzt
korrekte Pegel angenommen haben und es die gewnschten Daten von den
Datenleitungen bzw. Kommandos von den Datenleitungen bernehmen kann. Beim
Lesen gibt das Display die Daten / Status so lange aus, wie E high ist. Beim Schreiben
bernimmt das Display die Daten mit der fallenden Flanke.
[Bearbeiten]Anschluss
an den Controller
Jetzt, da wir wissen, welche Anschlsse das LCD bentigt, knnen wir das LCD mit dem
Mikrocontroller verbinden:
ACHTUNG: Es gibt Displays mit abweichender Anschluss-Belegung (z. B. TC1602E (Pollin
120420): Vdd und Vss vertauscht), falscher Anschluss kann zur Zerstrung fhren! Daher
immer das zugehrige Datenblatt zu Rate ziehen.
Einzelheiten unter Artikel zum Controller HD44780
Pinnummer
Bezeichnung
LCD
Anschluss
VSS
VCC
VEE
RS
PD4 am AVR
RW
GND
PD5 am AVR
DB0
nicht angeschlossen
DB1
nicht angeschlossen
DB2
nicht angeschlossen
10
DB3
nicht angeschlossen
11
DB4
PD0 am AVR
12
DB5
PD1 am AVR
13
DB6
PD2 am AVR
14
DB7
PD3 am AVR
15
16
GND
Ok. Alles ist verbunden. Wenn man jetzt den Strom einschaltet, sollten ein oder zwei schwarze
Balken auf dem Display angezeigt werden.
Doch wie bekommt man jetzt die Befehle und Daten in das Display? Dazu muss das LCD
initialisiert werden und man muss Befehle (Commands) und seine Daten an das LCD senden.
Weil die Initialisierung ein Spezialfall der bertragung von Befehlen ist, im Folgenden zunchst
die Erklrung fr die bertragung von Werten an das LCD.
[Bearbeiten]Ansteuerung
Um ein Byte zu bertragen, muss man es erstmal in die beiden Nibbles zerlegen, die getrennt
bertragen werden. Da das obere Nibble (Bit 4 - Bit 7) als erstes bertragen wird, die 4
Datenleitungen jedoch an die vier unteren Bits des Port D angeschlossen sind, muss man die
beiden Nibbles des zu bertragenden Bytes erstmal vertauschen. Der AVR kennt dazu
praktischerweise einen eigenen Befehl:
swap r16
Also: Das obere Nibble wird erst mit dem unteren vertauscht, damit es unten ist. Dann wird das
obere (das wir jetzt noch nicht brauchen) auf null gesetzt.
Jetzt mssen wir dem LCD noch mitteilen, ob wir Daten oder Befehle senden wollen. Das
machen wir, indem wir das Bit, an dem RS angeschlossen ist (PD4), auf 0 (Befehl senden) oder
auf 1 (Daten senden) setzen. Um ein Bit in einem normalen Register zu setzen, gibt es den
Befehl sbr (Set Bits in Register). Dieser Befehl unterscheidet sich jedoch von sbi (das nur fr
IO-Register gilt) dadurch, dass man nicht die Nummer des zu setzenden Bits angibt, sondern
eine Bitmaske. Das geht so:
sbr r16, 0b00010000
RS ist an PD4 angeschlossen. Wenn wir r16 an den Port D ausgeben, ist RS jetzt also high und
das LCD erwartet Daten anstatt von Befehlen.
Das Ergebnis knnen wir jetzt endlich direkt an den Port D bergeben:
out PORTD, r16
Natrlich muss vorher der Port D auf Ausgang geschaltet werden, indem man 0xFF ins
Datenrichtungsregister DDRD schreibt.
Um dem LCD zu signalisieren, dass es das an den Datenleitungen anliegende Nibble
bernehmen kann, wird die E-Leitung (Enable, an PD5 angeschlossen) auf high und kurz
darauf wieder auf low gesetzt. Ein Puls an dieser Leitung teilt also dem LCD mit, das die
restlichen Leitungen jetzt ihren vom Programm gewollten Pegel eingenommen haben und gltig
sind.
sbi PORTD, 5
nop
nop
nop
cbi PORTD, 5
; Enable high
; 3 Taktzyklen warten ("nop" = nichts tun)
; Enable wieder low
Die eine Hlfte des Bytes wre damit geschafft! Die andere Hlfte kommt direkt hinterher: Alles,
was an der obenstehenden Vorgehensweise gendert werden muss, ist, das "swap"
(Vertauschen der beiden Nibbles) wegzulassen.
[Bearbeiten]Initialisierung
des Displays
Allerdings gibt es noch ein Problem. Wenn ein LCD eingeschaltet wird, dann luft es zunchst
im 8 Bit Modus. Irgendwie muss das Display initialisiert und auf den 4 Bit Modus umgeschaltet
werden, und zwar nur mit den 4 zur Verfgung stehenden Datenleitungen.
Wenn es Probleme gibt, dann meistens an diesem Punkt. Die "kompatiblen" Kontroller sind
gelegentlich doch nicht 100% identisch. Es lohnt sich, das Datenblatt (siehe Weblinks im
Artikel LCD) genau zu lesen, in welcher Reihenfolge und mit welchen Abstnden (Delays) die
Initialisierungbefehle gesendet werden. Eine weitere Hilfe knnen Ansteuerungsbeispiele in
Forenbeitrgen geben z. B.
angeschlossen) ergibt sich eine Verschiebung, so dass das am Kontroller auszugebende Byte
nibblemssig vertauscht ist!
Die Sequenz, aus Sicht des Kontrollers, sieht so aus:
Nach dem Anlegen der Betriebsspannung muss eine Zeit von mindestens ca. 15ms
gewartet werden, um dem LCD-Kontroller Zeit fr seine eigene Initialisierung zu geben
$2 ins Steuerregister schreiben (RS = 0), dadurch wird auf 4 Bit Daten umgestellt
Ab jetzt muss fr die bertragung eines Bytes jeweils zuerst das hherwertige Nibble
und dann das niederwertige Nibble bertragen werden, wie oben beschrieben
Mit dem Konfigurier-Befehl $20 das Display konfigurieren (4-Bit, 1 oder 2 Zeilen, 5x7
Format)
Eine Begrndung, warum die ersten Befehle dreifach geschickt werden sollen, findet sich im
Forum.
[Bearbeiten]Initialisierung fr 8 Bit Modus
Der Vollstndigkeit halber hier noch die notwendige Initialiserungssequenz fr den 8 Bit Modus.
Da hier die Daten komplett als 1 Byte bertragen werden knnen, sind einige Klimmzge wie im
4 Bit Modus nicht notwendig. Begrndung fr die anfnglichen Wiederholungen siehe oben.
Nach dem Anlegen der Betriebsspannung muss eine Zeit von mindestens ca. 15ms
gewartet werden, um dem LCD-Kontroller Zeit fr seine eigene Initialisierung zu geben
Mit dem Konfigurier-Befehl 0x30 das Display konfigurieren (8-Bit, 1 oder 2 Zeilen, 5x7
Format)
[Bearbeiten]Routinen
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
"Sicherungskopie" fr
die bertragung des 2.Nibbles
Vertauschen
oberes Nibble auf Null setzen
entspricht 0b00010000 (Anm.1)
ausgeben
Enable-Routine aufrufen
2. Nibble, kein swap da es schon
an der richtigen stelle ist
obere Hlfte auf Null setzen
entspricht 0b00010000
ausgeben
Enable-Routine aufrufen
Delay-Routine aufrufen
zurck zum Hauptprogramm
rcall lcd_enable
rcall delay50us
ret
; erzeugt den Enable-Puls
;
; Bei hherem Takt (>= 8 MHz) kann es notwendig sein,
; vor dem Enable High 1-2 Wartetakte (nop) einzufgen.
; Siehe dazu http://www.mikrocontroller.net/topic/81974#685882
lcd_enable:
sbi PORTD, 5
; Enable high
nop
; mindestens 3 Taktzyklen warten
nop
nop
cbi PORTD, 5
; Enable wieder low
ret
; Und wieder zurck
; Pause nach jeder bertragung
delay50us:
ldi temp1, $42
delay50us_:dec temp1
brne delay50us_
ret
; Lngere Pause fr manche Befehle
delay5ms:
ldi temp1, $21
WGLOOP0:
ldi temp2, $C9
WGLOOP1:
dec temp2
brne WGLOOP1
dec temp1
brne WGLOOP0
ret
; wieder zurck
; 5ms Pause (bei 4 MHz)
; wieder zurck
; Cursor Home
temp1, LOW(RAMEND)
SPL, temp1
temp1, HIGH(RAMEND)
SPH, temp1
; Port D = Ausgang
rcall lcd_init
rcall lcd_clear
; Display initialisieren
; Display lschen
; Zeichen anzeigen
; Zeichen anzeigen
; Zeichen anzeigen
; Zeichen anzeigen
loop:
rjmp loop
.include "lcd-routines.asm"
Fr lngere Texte ist die Methode, jedes Zeichen einzeln in das Register zu laden und
"lcd_data" aufzurufen natrlich nicht sehr praktisch. Dazu spter aber mehr.
Bisher wurden in Register immer irgendwelche Zahlenwerte geladen, aber in diesem Programm
kommt pltzlich die Anweisung
ldi temp1, 'T'
vor. Wie ist diese zu verstehen? Passiert hier etwas grundlegend anderes als beim Laden einer
Zahl in ein Register?
Die Antwort darauf lautet: Nein. Auch hier wird letztendlich nur eine Zahl in ein Register
geladen. Der Schlssel zum Verstndnis beruht darauf, dass zum LCD, so wie zu allen
Ausgabegerten, fr die Ausgabe von Texten immer nur Zahlen bertragen werden, sog.
Codes. Zum Beispiel knnte man vereinbaren, dass ein LCD, wenn es den Ausgabecode 65
erhlt, ein 'A' anzeigt, bei einem Ausgabecode von 66 ein 'B' usw. Naturgem gibt es daher
viele verschiedene Code-Buchstaben Zuordnungen. Damit hier etwas Ordnung in das
potentielle Chaos kommt, hat man sich bereits in der Steinzeit der Programmierung auf
bestimmte Codetabellen geeinigt, von denen die verbreitetste sicherlich die ASCII-Zuordnung
ist.
[Bearbeiten]ASCII
ASCII steht fr American Standard Code for Information Interchange und ist ein standardisierter
Code zur Zeichenumsetzung. Die Codetabelle sieht hexadezimal dabei wie folgt aus:
x0
x1
x2
x3
0 NU SO
x L
H
ST
X
ET
X
x4
x5
x6
EO EN
T
Q
AC
K
x7
x8
BE
BS
L
x
xD xE xF
C
x9 xA
xB
HT LF
VT FF
C
R
S
SI
O
1 DL
x E
DC DC DC DC NA
1
2
3
4
K
SY
N
ET CA
B
N
E SU ES
G R
FS
US
M B
C
S S
2
SP
x
"
&
'
3
0
x
<
>
4
@
x
L M N O
5
P
x
6
`
x
m n
7
p
x
DE
L
Die ersten beiden Zeilen enthalten die Codes fr einige Steuerzeichen, ihre vollstndige
Beschreibung wrde hier zu weit fhren. Das Zeichen SP steht fr ein Space, also ein
Leerzeichen. BS steht fr Backspace, also ein Zeichen zurck. DEL steht fr Delete, also das
Lschen eines Zeichens. CR steht fr Carriage Return, also wrtlich: der Wagenrcklauf (einer
Schreibmaschine), whrend LFfr Line feed, also einen Zeilenvorschub steht.
Der Assembler kennt diese Codetabelle und ersetzt die Zeile
ldi temp1, 'T'
durch
ldi temp1, $54
was letztendlich auch der Lesbarkeit des Programmes zugute kommt. Funktional besteht kein
Unterschied zwischen den beiden Anweisungen. Beide bewirken, dass das Register temp1 mit
dem Bitmuster 01010100 ( = hexadezimal 54, = dezimal 84 oder eben der ASCII Code fr T)
geladen wird.
Das LCD wiederrum kennt diese Code-Tabelle ebenfalls und wenn es ber den Datenbus die
Codezahl $54 zur Anzeige empfngt, dann schreibt es ein T an die aktuelle Cursorposition.
Genauer gesagt, weiss das LCD nichts von einem T. Es sieht einfach in seinen internen
Tabellen nach, welche Pixel beim Empfang der Codezahl $54 auf schwarz zu setzen sind.
'Zufllig' sind das genau jene Pixel, die fr uns Menschen ein T ergeben.
[Bearbeiten]Welche
Auf dem LCD arbeitet ein Controller vom Typ HD44780. Dieser Kontroller versteht eine Reihe
von Befehlen, die allesamt mittels lcd_command gesendet werden knnen. Ein Kommando ist
dabei nichts anderes als ein Befehlsbyte, in dem die verschiedenen Bits verschiedene
Bedeutungen haben:
Bitwert
Bedeutung
sonstige
Buchstaben
Beispiel: Das Kommando 'ON/OFF Control' soll benutzt werden, um das Display einzuschalten,
der Cursor soll eingeschaltet werden und der Cursor soll blinken. Das Befehlsbyte ist so
aufgebaut:
0b00001dcb
Aus der Befehlsbeschreibung entnimmt man:
Das dafr zu bertragende Befehlsbyte hat also die Gestalt 0b00001111 oder in hexadezimaler
Schreibweise $0F.
[Bearbeiten]Clear display: 0b00000001
Die Anzeige wird gelscht und der Ausgabecursor kehrt an die Home Position (links, erste Zeile)
zurck.
Ausfhrungszeit: 1.64ms
[Bearbeiten]Cursor home: 0b0000001x
Der Cursor kehrt an die Home Position (links, erste Zeile) zurck. Ein verschobenes Display
wird auf die Grundeinstellung zurckgesetzt.
Ausfhrungszeit: 40s bis 1.64ms
[Bearbeiten]Entry mode: 0b000001is
Legt die Cursor Richtung sowie eine mgliche Verschiebung des Displays fest
s = 1, Display wird gescrollt, wenn der Cursor das Ende/Anfang, je nach Einstellung von
i, erreicht hat.
Ausfhrungszeit: 40s
[Bearbeiten]On/off control: 0b00001dcb
Display insgesamt ein/ausschalten; den Cursor ein/ausschalten; den Cursor auf blinken
schalten/blinken aus. Wenn das Display ausgeschaltet wird, geht der Inhalt des Displays nicht
verloren. Der vorher angezeigte Text wird nach wiedereinschalten erneut angezeigt. Ist der
Cursor eingeschaltet, aber Blinken ausgeschaltet, so wird der Cursor als Cursorzeile in
Pixelzeile 8 dargestellt. Ist Blinken eingeschaltet, wird der Cursor als blinkendes ausgeflltes
Rechteck dargestellt, welches abwechselnd mit dem Buchstaben an dieser Stelle angezeigt
wird.
d = 0, Display aus
d = 1, Display ein
c = 0, Cursor aus
c = 1, Cursor ein
Ausfhrungszeit: 40s
[Bearbeiten]Cursor/Scrollen: 0b0001srxx
Bewegt den Cursor oder scrollt das Display um eine Position entweder nach rechts oder nach
links.
s = 1, Display scrollen
s = 0, Cursor bewegen
r = 1, nach rechts
r = 0, nach links
Ausfhrungszeit: 40s
[Bearbeiten]Konfiguration: 0b001dnfxx
Einstellen der Interface Art, Modus, Font
d = 0, 4-Bit Interface
d = 1, 8-Bit Interface
n = 0, 1 zeilig
n = 1, 2 zeilig
f = 0, 5x7 Pixel
f = 1, 5x11 Pixel
Ausfhrungszeit: 40s
[Bearbeiten]Character RAM Address Set: 0b01aaaaaa
Mit diesem Kommando werden maximal 8 selbst definierte Zeichen definiert. Dazu wird der
Character RAM Zeiger auf den Anfang des Character Generator (CG) RAM gesetzt und das
Zeichen durch die Ausgabe von 8 Byte definiert. Der Adresszeiger wird nach Ausgabe jeder
Pixelspalte (8 Bit) vom LCD selbst erhht. Nach Beendigung der Zeichendefinition muss die
Schreibposition explizit mit dem Kommando "Display RAM Address Set" wieder in den DD-RAM
Bereich gesetzt werden.
aaaaaa 6-bit CG RAM Adresse
Ausfhrungszeit: 40s
l = Zeilennummer (0 oder 1)
a = 6-Bit Spaltennummer
Ausfhrungszeit: 40s
[Bearbeiten]Einschub:
Code aufrumen
Es wird Zeit, sich einmal etwas kritisch mit den bisher geschriebenen Funktionen auseinander
zu setzen.
Dabei darf keine einzige Stelle bersehen werden, ansonsten wrden die LCD-Funktionen nicht
oder nicht vollstndig funktionieren.
Eine Mglichkeit, dem vorzubeugen, ist es, diese immer gleichbleibenden Dinge an den Anfang
der LCD-Funktionen vorzuziehen. Anstelle von PORTD wird dann im Code ein anderer Name
benutzt, den man frei vergeben kann. Dem Assembler wird nur noch mitgeteilt, das dieser
Name fr PORTD steht. Muss das LCD an einen anderen Port angeschlossen werden, so wird
nur diese Zurodnung gendert und der Assembler passt dann im restlichen Code alle davon
abhngigen Anweisungen an:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
LCD-Routinen
;;
;;
============
;;
;;
(c)andreas-s@web.de
;;
;;
;;
;; 4bit-Interface
;;
;; DB4-DB7:
PD0-PD3
;;
;; RS:
PD4
;;
;; E:
PD5
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; .equ definiert ein Symbol und dessen Wert
.equ LCD_PORT = PORTD
.equ LCD_DDR = DDRD
.equ PIN_E
= 5
.equ PIN_RS
= 4
;sendet ein Datenbyte an das LCD
lcd_data:
mov temp2, temp1
swap temp1
andi temp1, 0b00001111
sbr temp1, 1<<PIN_RS
out LCD_PORT, temp1
rcall lcd_enable
andi temp2, 0b00001111
sbr temp2, 1<<PIN_RS
out LCD_PORT, temp2
rcall lcd_enable
rcall delay50us
ret
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
"Sicherungskopie" fr
die bertragung des 2.Nibbles
Vertauschen
oberes Nibble auf Null setzen
entspricht 0b00010000
ausgeben
Enable-Routine aufrufen
2. Nibble, kein swap da es schon
an der richtigen stelle ist
obere Hlfte auf Null setzen
entspricht 0b00010000
ausgeben
Enable-Routine aufrufen
Delay-Routine aufrufen
zurck zum Hauptprogramm
ret
; erzeugt den Enable-Puls
lcd_enable:
sbi LCD_PORT, PIN_E
nop
nop
nop
cbi LCD_PORT, PIN_E
ret
; Pause nach jeder bertragung
delay50us:
ldi temp1, $42
delay50us_:dec temp1
brne delay50us_
ret
; Lngere Pause fr manche Befehle
delay5ms:
ldi temp1, $21
WGLOOP0:
ldi temp2, $C9
WGLOOP1:
dec temp2
brne WGLOOP1
dec temp1
brne WGLOOP0
ret
; Enable high
; 3 Taktzyklen warten
; Enable wieder low
; Und wieder zurck
; 50s Pause
; wieder zurck
; 5ms Pause
; wieder zurck
temp3,6
delay5ms
temp3
powerupwait
temp1,
0b00000011
LCD_PORT, temp1
lcd_enable
delay5ms
lcd_enable
delay5ms
lcd_enable
delay5ms
temp1, 0b00000010
LCD_PORT, temp1
lcd_enable
delay5ms
temp1, 0b00101000
lcd_command
temp1, 0b00001100
lcd_command
temp1, 0b00000100
lcd_command
; 4 Bit, 2 Zeilen
; Display on, Cursor off
; endlich fertig
ldi
temp1, 0b00000001
rcall lcd_command
rcall delay5ms
ret
; Sendet den Befehl: Cursor Home
lcd_home:
ldi
temp1, 0b00000010
rcall lcd_command
rcall delay5ms
ret
; Display lschen
; Cursor Home
Mittels .equ werden mit dem Assembler Textersetzungen vereinbart. Der Assembler ersetzt alle
Vorkomnisse des Quelltextes durch den zu ersetzenden Text. Dadurch ist es z. B. mglich, alle
Vorkommnisse von PORTD durch LCD_PORT auszutauschen. Wird das LCD an einen anderen
Port, z. B. PORTB gelegt, dann gengt es, die Zeilen
.equ LCD_PORT = PORTD
.equ LCD_DDR = DDRD
durch
.equ LCD_PORT = PORTB
.equ LCD_DDR = DDRB
zu ersetzen. Der Assembler sorgt dann dafr, dass diese Portnderung an den relevanten
Stellen im Code ber die Textersetzungen einfliet. Selbiges natrlich mit der Pin-Zuordnung.
[Bearbeiten]Registerbenutzung
Bei diesen Funktionen mussten einige Register des Prozessors benutzt werden, um darin
Zwischenergebnisse zu speichern bzw. zu bearbeiten.
Beachtet werden muss dabei natrlich, dass es zu keinen berschneidungen kommt. Solange
nur jede Funktion jeweils fr sich betrachtet wird, ist das kein Problem. In 20 oder 30 CodeZeilen kann man gut verfolgen, welches Register wofr benutzt wird. Schwieriger wird es, wenn
Funktionen wiederum andere Funktionen aufrufen, die ihrerseits wieder Funktionen aufrufen
usw. Jede dieser Funktionen benutzt einige Register und mit zunehmender Programmgre
wird es immer schwieriger, zu verfolgen, welches Register zu welchem Zeitpunkt wofr benutzt
wird.
Speziell bei Basisfunktionen wie diesen LCD-Funktionen, ist es daher oft ratsam, dafr zu
sorgen, dass jede Funktion die Register wieder in dem Zustand hinterlsst, indem sie sie auch
vorgefunden hat. Wir bentigen dazu wieder den Stack, auf dem die Registerinhalte bei
Betreten einer Funktion zwischengespeichert werden und von dem die Register bei Verlassen
einer Funktion wiederhergestellt werden.
Nehmen wir die Funktion
; Sendet den Befehl zur Lschung des Displays
lcd_clear:
ldi
temp1, 0b00000001
; Display lschen
rcall lcd_command
rcall delay5ms
ret
Diese Funktion verndert das Register temp1. Um das Register abzusichern, schreiben wir die
Funktion um:
; Sendet den Befehl zur Lschung des Displays
lcd_clear:
push temp1
; temp1 auf dem Stack sichern
ldi
temp1, 0b00000001
; Display lschen
rcall lcd_command
rcall delay5ms
pop
temp1
; temp1 vom Stack wiederherstellen
ret
Am besten hlt man sich an die Regel: Jede Funktion ist dafr zustndig, die Register zu
sichern und wieder herzustellen, die sie auch selbst verndert. lcd_clear ruft die
Funktionen lcd_commandund delay5ms auf. Wenn diese Funktionen selbst wieder Register
verndern (und das tun sie), so ist es die Aufgabe dieser Funktionen, sich um die Sicherung
und das Wiederherstellen der entsprechenden Register zu kmmern. lcd_clear sollte sich nicht
darum kmmern mssen. Auf diese Weise ist das Schlimmste, das einem passieren kann, dass
ein paar Register unntz gesichert und wiederhergestellt werden. Das kostet zwar etwas
Rechenzeit und etwas Speicherplatz auf dem Stack, ist aber immer noch besser als das andere
Extrem: Nach einem Funktionsaufruf haben einige Register nicht mehr den Wert, den sie haben
sollten, und das Programm rechnet mit falschen Zahlen weiter.
[Bearbeiten]Lass den Assembler rechnen
Betrachtet man den Code genauer, so fallen einige konstante Zahlenwerte auf (Das
vorangestellte $ kennzeichnet die Zahl als Hexadezimalzahl):
delay50us:
ldi
temp1, $42
; 50s Pause
delay50us_:
dec temp1
brne delay50us_
ret
; wieder zurck
Der Code bentigt eine Warteschleife, die mindestens 50s dauert. Die beiden Befehle
innerhalb der Schleife bentigen 3 Takte: 1 Takt fr den dec und der brne bentigt 2 Takte,
wenn die Bedingung zutrifft, der Branch also genommen wird. Bei 4 Mhz werden also 4000000 /
3 * 50 / 1000000 = 66.6 Durchlufe durch die Schleife bentigt, um eine Verzgerungszeit von
50s (0.000050 Sekunden) zu erreichen, hexadezimal ausgedrckt: $42.
Der springende Punkt ist: Bei anderen Taktfrequenzen msste man nun jedesmal diese
Berechnung machen und den entsprechenden Zahlenwert einsetzen. Das kann aber der
Assembler genausogut erledigen. Am Anfang des Codes wird ein Eintrag definiert, der die
Taktfrequenz festlegt. Traditionell heit dieser Eintrag XTAL:
.equ XTAL
= 4000000
...
delay50us:
ldi
; 50s Pause
temp1, ( XTAL * 50 / 3 ) / 1000000
delay50us_:
dec temp1
brne delay50us_
ret
; wieder zurck
; 5ms Pause
; wieder zurck
Was geht hier vor? Die innere Schleife bentigt wieder 3 Takte pro Durchlauf. Bei $C9 = 201
Durchlufen werden also 201 * 3 = 603 Takte verbraucht. In der ueren Schleife werden pro
Durchlauf also 603 + 1 + 2 = 606 Takte verbraucht und einmal 605 Takte (weil der brne nicht
genommen wird). Da die uere Schleife $21 = 33 mal wiederholt wird, werden 32 * 606 + 605
= 19997 Takte verbraucht. Noch 1 Takt mehr fr den allerersten ldi und 4 Takte fr den ret,
macht 20002 Takte. Bei 4Mhz bentigt der Prozessor 20002 / 4000000 = 0.0050005 Sekunden,
also rund 5 ms. Die 7. nachkommastelle kann man an dieser Stelle getrost ignorieren. Vor allen
Dingen auch deshalb, weil auch der Quarz nicht exakt 4000000 Schwingungen in der Sekunde
durchfhren wird. Wird der Wiederholwert fr die innere Schleife bei $C9 belassen, so werden
4000000 / 607 * 5 / 1000 Wiederholungen der usseren Schleife bentigt. (Die Berechnung
wurde hier etwas vereinfacht, die nicht bercksichtigten Takte fallen zeitmssig nicht weiter ins
Gewicht bzw. wurden dadurch bercksichtigt, dass mit 607 anstelle von 606 gerechnet wird).
Auch diese Berechnung kann wieder der Assembler bernehmen:
; Lngere Pause fr manche Befehle
delay5ms:
; 5ms Pause
ldi temp1, ( XTAL * 5 / 607 ) / 1000
WGLOOP0:
ldi temp2, $C9
WGLOOP1:
dec temp2
brne WGLOOP1
dec temp1
brne WGLOOP0
ret
; wieder zurck
Ein kleines Problem kann bei der Verwendung dieses Verfahrens entstehen: Bei hohen
Taktfrequenzen und groen Wartezeiten kann der berechnete Wert grer als 255 werden und
man bekommt die Fehlermeldung "Operand(s) out of range" beim Assemblieren. Dieser Fall tritt
zum Beispiel fr obige Konstruktion bei einer Taktfrequenz von 16 MHz ein (genauer gesagt ab
15,3 MHz), whrend darunter XTAL beliebig gendert werden kann. Als einfachste Lsung
bietet es sich an, die Zahl der Takte pro Schleifendurchlauf durch das Einfgen von nop zu
erhhen und die Berechnungsvorschrift anzupassen.
[Bearbeiten]Ausgabe
Weiter oben wurde schon einmal ein Text ausgegeben. Dies geschah durch Ausgabe von
einzelnen Zeichen. Das knnen wir auch anders machen. Wir knnen den Text im Speicher
ablegen und eine Funktion schreiben, die die einzelnen Zeichen aus dem Speicher liest und aus
gibt. Dabei stellt sich Frage: Woher 'wei' die Funktion eigentlich, wie lang der Text ist? Die
Antwort darauf lautet: Sie kann es nicht wissen. Wir mssen irgendwelche Vereinbarungen
treffen, woran die Funktion erkennen kann, dass der Text zu Ende ist. Im Wesentlichen werden
dazu 2 Methoden benutzt:
Der Text enthlt ein spezielles Zeichen, welches das Ende des Textes markiert
Wir speichern nicht nur den Text selbst, sondern auch die Lnge des Textes
Mit einer der beiden Methoden ist es der Textausgabefunktion dann ein Leichtes, den Text
vollstndig auszugeben.
Wir werden uns im Weiteren dafr entscheiden, ein spezielles Zeichen, eine 0 (den Wert 0,
nicht das Zeichen '0'), dafr zu benutzen. Die Ausgabefunktionen werden dann etwas einfacher,
als wenn bei der Ausgabe die Anzahl der bereits ausgegebenen Zeichen mitgezhlt werden
muss.
Den Text selbst speichern wir im Flash-Speicher, also dort, wo auch das Programm gespeichert
ist:
; Einen konstanten Text aus dem Flash Speicher
; ausgeben. Der Text wird mit einer 0 beendet
lcd_flash_string:
push temp1
push ZH
push ZL
lcd_flash_string_1:
lpm
temp1, Z+
cpi
temp1, 0
breq lcd_flash_string_2
rcall lcd_data
rjmp lcd_flash_string_1
lcd_flash_string_2:
pop
ZL
pop
ZH
pop
temp1
ret
Diese Funktion benutzt den Befehl lpm, um das jeweils nchste Zeichen aus dem Flash
Speicher in ein Register zur Weiterverarbeitung zu laden. Dazu wird der sog. Z-Pointer benutzt.
So nennt man das Registerpaar R30 und R31. Nach jedem Ladevorgang wird dabei durch den
Befehl
lpm
temp1, Z+
dieser Z-Pointer um 1 erhht. Mittels cpi wird das in das Register temp1 geladene Zeichen mit
0 verglichen. cpi vergleicht die beiden Zahlen und merkt sich das Ergebnis in einem speziellen
Register in Form von Status Bits. cpi zieht dabei ganz einfach die beiden Zahlen voneinander
ab. Sind sie gleich, so kommt da als Ergebnis 0 heraus und cpi setzt daher konsequenter
Weise das Zero-Flag, das anzeigt, dass die vorhergegangene Operation eine 0 als Ergebnis
hatte.breq wertet diese Status-Bits aus. Wenn die vorhergegangene Operation ein 0-Ergebnis
hatte, das Zero-Flag also gesetzt ist, dann wird ein Sprung zum angegebenen Label
durchgefhrt. In Summe bewirkt also die Sequenz
cpi
breq
temp1, 0
lcd_flash_string_2
dass das gelesene Zeichen mit 0 verglichen wird und falls das gelesene Zeichen tatschlich 0
war, an der Stelle lcd_flash_string_2 weiter gemacht wird. Im anderen Fall wird die bereits
geschriebene Funktion lcd_data aufgerufen, welche das Zeichen ausgibt. lcd_data erwartet
dabei das Zeichen im Register temp1, genau in dem Register, in welches wir vorher
mittels lpm das Zeichen geladen hatten.
Das verwendende Programm sieht dann so aus:
.include "m8def.inc"
.def temp1 = r16
.def temp2 = r17
.def temp3 = r18
ldi
out
ldi
out
loop:
temp1, LOW(RAMEND)
SPL, temp1
temp1, HIGH(RAMEND)
SPH, temp1
rcall lcd_init
rcall lcd_clear
; Display initialisieren
; Display lschen
rcall lcd_flash_string
rjmp loop
text:
.db "Test",0
.include "lcd-routines.asm"
Genaueres ber die Verwendung unterschiedlicher Speicher findet sich im Kapitel Speicher
[Bearbeiten]Zahlen
ausgeben
Um Zahlen, die beispielsweise in einem Register gespeichert sind, ausgeben zu knnen, ist es
notwendig sich eine Textreprsentierung der Zahl zu generieren. Die Zahl 123 wird also in den
Text "123" umgewandelt welcher dann ausgegeben wird. Aus praktischen Grnden wird
allerdings der Text nicht vollstndig generiert (man msste ihn ja irgendwo zwischenspeichern)
sondern die einzelnen Buchstaben werden sofort ausgegeben, sobald sie bekannt sind.
[Bearbeiten]Dezimal ausgeben
Das Prinzip der Umwandlung ist einfach. Um herauszufinden wieviele Hunderter in der Zahl 123
enthalten sind, gengt es in einer Schleife immer wieder 100 von der Zahl abzuziehen und
mitzuzhlen wie oft dies gelang, bevor das Ergebnis negativ wurde. In diesem Fall lautet die
Antwort: 1 mal, denn 123 - 100 macht 23. Versucht man erneut 100 anzuziehen, so ergibt sich
eine negative Zahl. Also muss eine '1' ausgeben werden. Die verbleibenden 23 werden weiter
behandelt, indem festgestellt wird wieviele Zehner darin enthalten sind. Auch hier wiederum: In
einer Schleife solange 10 abziehen, bis das Ergebnis negativ wurde. Konkret geht das 2 mal
gut, also muss das nchste auszugebende Zeichen ein '2' sein. Damit verbleiben noch die
Einer, welche direkt in das entsprechende Zeichen umgewandelt werden knnen. In Summe hat
man also an das Display die Zeichen '1' '2' '3' ausgegeben.
;**********************************************************************
;
; Eine 8 Bit Zahl ohne Vorzeichen ausgeben
;
; bergabe:
Zahl im Register temp1
; vernderte Register: keine
;
lcd_number:
push temp1
; die Funktion verndert temp1 und temp2,
push temp2
; also sichern wir den Inhalt, um ihn am Ende
; wieder herstellen zu knnen
mov
temp2, temp1
;** Hunderter **
ldi
temp1, '0'-1
lcd_number_1:
inc
temp1
subi
brcc
temp2, 100
lcd_number_1
subi
temp2, -100
rcall lcd_data
**
ldi
lcd_number_2:
inc
;** Zehner
temp1, '0'-1
temp1
;
;
;
;
;
;
;
;
;
subi
brcc
temp2, 10
lcd_number_2
subi
temp2, -10
rcall lcd_data
;** Einer **
ldi
temp1, '0'
add
temp1, temp2
rcall lcd_data
pop
pop
ret
temp2
temp1
;
;
;
;
Beachte: Diese Funktion benutzt wiederrum die Funktion lcd_data. Anders als bei den
bisherigen Aufrufen ist lcd_number aber darauf angewiesen, dass lcd_data das
Register temp2 unangetastet lsst. Falls sie es noch nicht getan haben, dann ist das jetzt die
perfekte Gelegenheit, lcd_data mit den entsprechenden push und pop Befehlen zu versehen.
Sie sollten dies unbedingt zur bung selbst machen. Am Ende mu die Funktion dann wie
diese hier aussehen:
;sendet ein Datenbyte an das LCD
lcd_data:
push temp2
mov
temp2, temp1
swap
andi
sbr
out
rcall
temp1
temp1, 0b00001111
temp1, 1<<PIN_RS
LCD_PORT, temp1
lcd_enable
andi
sbr
out
rcall
rcall
pop
ret
temp2, 0b00001111
temp2, 1<<PIN_RS
LCD_PORT, temp2
lcd_enable
delay50us
temp2
;
;
;
;
;
;
;
;
;
;
;
;
;
;
"Sicherungskopie" fr
die bertragung des 2.Nibbles
Vertauschen
oberes Nibble auf Null setzen
entspricht 0b00010000
ausgeben
Enable-Routine aufrufen
2. Nibble, kein swap da es schon
an der richtigen stelle ist
obere Hlfte auf Null setzen
entspricht 0b00010000
ausgeben
Enable-Routine aufrufen
Delay-Routine aufrufen
Kurz zur Funktionsweise der Funktion lcd_number: Die Zahl in einem Register bewegt sich im
Wertebereich 0 bis 255. Um herauszufinden, wie die Hunderterstelle lautet, zieht die Funktion
einfach in einer Schleife immer wieder 100 von der Schleife ab, bis bei der Subtraktion ein
Unterlauf, angezeigt durch das Setzen des Carry-Bits bei der Subtraktion, entsteht. Die Anzahl
wird im Registertemp1 mitgezhlt. Da dieses Register mit dem ASCII Code von '0' initialisiert
wurde, und dieser ASCII Code bei jedem Schleifendurchlauf um 1 erhht wird, knnen wir das
Register temp1 direkt zur Ausgabe des Zeichens fr die Hunderterstelle durch die
Funktion lcd_data benutzen. Vllig analog funktioniert auch die Ausgabe der Zehnerstelle.
[Bearbeiten]Unterdrckung von fhrenden Nullen
Diese Funktion gibt jede Zahl im Register temp1 immer mit 3 Stellen aus. Fhrende Nullen
werden nicht unterdrckt. Mchte man dies ndern, so ist das ganz leicht mglich: Vor Ausgabe
der Hunderterstelle muss lediglich berprft werden, ob die Entsprechende Ausgabe eine '0'
wre. Ist sie das, so wird die Ausgabe bersprungen. Ist es allerdings eine Zahl 1..9, so muss
sie der Zehner Stelle signalisieren, da eine Prfung auf eine '0' nicht stattfinden darf. Und dazu
wird das T-Flag im SREG genutzt. Lediglich in der Einerstelle wird jede Ziffer wie errechnet
ausgegeben.
...
clt
cpi
temp1, '0'
breq lcd_number_1a
rcall lcd_data
set
lcd_number_1a:
...
...
brts
lcd_number_2a
cpi
temp1, '0'
breq lcd_number_2b
lcd_number_2a:
rcall lcd_data
lcd_number_2b:
...
;
;
;
;
Das Verfahren, die einzelnen Stellen durch Subtraktion zu bestimmen, ist bei kleinen Zahlen
eine durchaus gngige Alternative. Vor allem dann, wenn keine hardwaremige Untersttzung
fr Multiplikation und Division zur Verfgung steht. Ansonsten knnte man die die einzelnen
Ziffern auch durch Division bestimmen. Das Prinzip ist folgendes (beispielhaft an der Zahl
52783 gezeigt)
52783 / 10
52783 - 5278 * 10
-> 5278
->
5278 / 10
5278 - 527 * 10
-> 527
->
527 / 10
-> 52
527 - 52 * 10
->
52 / 10
52 - 5 * 10
-> 5
->
5 / 10
5 - 0 * 10
-> 0
->
Das Prinzip ist also die Restbildung bei einer fortgesetzten Division durch 10, wobei die
einzelnen Ziffern in umgekehrter Reihenfolge ihrer Wertigkeit entstehen. Dadurch hat man aber
ein Problem: Damit die Zeichen in der richtigen Reihenfolge ausgegeben werden knnen, mu
man sie meistens zwischenspeichern um sie in der richtigen Reihenfole ausgeben zu knnen.
Wird die Zahl in einem Feld von immer gleicher Gre ausgegeben, dann kann man auch die
Zahl von rechts nach links ausgeben (bei einem LCD ist das mglich).
[Bearbeiten]Hexadezimal ausgeben
Zu guter letzt hier noch eine Funktion, die eine Zahl aus dem Register temp1 in hexadezimaler
Form ausgibt. Die Funktion weist keine Besonderheiten auf und sollte unmittelbar verstndlich
sein.
;**********************************************************************
;
; Eine 8 Bit Zahl ohne Vorzeichen hexadezimal ausgeben
;
; bergabe:
Zahl im Register temp1
; vernderte Register: keine
;
lcd_number_hex:
swap temp1
rcall lcd_number_hex_digit
swap temp1
lcd_number_hex_digit:
push temp1
andi
cpi
brlt
subi
temp1, $0F
temp1, 10
lcd_number_hex_digit_1
temp1, -( 'A' - '9' - 1 ) ;
;
;
lcd_number_hex_digit_1:
subi temp1, -'0'
;
rcall lcd_data
pop
ret
temp1
[Bearbeiten]Binr ausgeben
Um die Sache komplett zu machen; Hier eine Routine mit der man eine 8 Bit-Zahl binr auf das
LC-Display ausgeben kann:
;**********************************************************************
;
; Eine 8 Bit Zahl ohne Vorzeichen binr ausgeben
;
; bergabe:
Zahl im Register temp1
; vernderte Register: keine
; eine Zahl aus
lcd_number_bit:
push
push
push
; temp1 gesichert
push
temp3
; ** Zehntausender **
ldi
temp1, '0'-1
lcd_number1:
inc
temp1
subi temp2, low(10000)
sbci temp3, high(10000)
brcc lcd_number1
subi temp2, low(-10000)
sbci temp3, high(-10000)
rcall lcd_data
; ** Tausender **
ldi
temp1, '0'-1
lcd_number2:
inc
temp1
subi temp2, low(1000)
sbci temp3, high(1000)
brcc lcd_number2
subi temp2, low(-1000)
sbci temp3, high(-1000)
rcall lcd_data
; ** Hunderter **
ldi
temp1, '0'-1
lcd_number3:
inc
temp1
subi temp2, low(100)
sbci temp3, high(100)
brcc lcd_number3
subi temp2, -100
rcall lcd_data
; ** Zehner **
ldi
lcd_number4:
inc
subi
brcc
subi
rcall
temp1, '0'-1
temp1
temp2, 10
lcd_number4
temp2, -10
lcd_data
; ** Einer **
ldi
temp1, '0'
add
temp1, temp2
rcall lcd_data
; ** Stack aufrumen **
pop
temp3
pop
temp2
pop
temp1
ret
[Bearbeiten]Eine BCD Zahl ausgeben
;**********************************************************************
;
; bergabe:
BCD Zahl in temp1
temp2, temp1
temp1
temp1, 0b00001111
temp1, -0x30
lcd_data
temp1, temp2
temp1, 0b00001111
temp1, -0x30
lcd_data
temp1, temp2
pop
ret
temp2
[Bearbeiten]Benutzerdefinierte
;
;
;
;
;
;
temp1 sichern
oberes mit unterem Nibble tauschen
und "oberes" ausmaskieren
in ASCII umrechnen
und ausgeben
... danach unteres
; temp1 rekonstruieren
Zeichen
Zeichenraster fr 1 Zeichen
Das LCD erlaubt fr spezielle Zeichen, welche sich nicht im Zeichensatz finden, eigene Zeichen
zu definieren. Dazu werden die ersten 8 ASCII Codes reserviert, auf denen sich laut ASCII
Tabelle spezielle Steuerzeichen befinden, die normalerweise keine sichtbare Anzeige
hervorrufen sondern zur Steuerung von angeschlossenen Gerten dienen. Da diese Zeichen
auf einem LCD keine Rolle spielen, knnen diese Zeichen benutzt werden um sich selbst
Sonderzeichen zu erzeugen, die fr die jeweilige Anwendung massgeschneidert sind.
Das LCD stellt fr jedes Zeichen eine 8*5 Matrix zur Verfgung. Um sich selbst
massgeschneiderte Zeichen zu erstellen, ist es am einfachsten sich zunchst auf einem Stck
karriertem Papier zu erstellen.
In diesem Raster markiert man sich dann diejenigen Pixel, die im fertigen Zeichen dunkel
erscheinen sollen. Als Beispiel sei hier ein Glockensymbol gezeichnet, welches in einer
Telefonapplikation zb als Kennzeichnung fr einen Anruf dienen knnte.
Eine Zeile in diesem Zeichen reprsentiert ein an das LCD zu bergebendes Byte, wobei nur
die Bits 0 bis 4 relevant sind. Gesetzte Pixel stellen ein 1 Bit dar, nicht gesetzte Pixel sind ein 0Bit. Das niederwertigste Bit einer Zeile befindet sich rechts. Auf diese Art wird jede Zeile in eine
Binrzahl bersetzt, und 8 Bytes reprsentieren ein komplettes Zeichen. Am Beispiel des
Glockensymboles: Die 8 Bytes, welches das Symbol reprsentiern, lauten: 0x00, 0x04, 0x0A,
0x0A, 0x0A, 0x1F, 0x04, 0x00,
Dem LCD wird die neue Definition bertragen, indem man dem LCD die 'Schreibposition' mittels
des Kommandos Character RAM Address Set in den Zeichensatzgenerator verschiebt. Danach
werden die 8 Bytes ganz normal als Daten ausgegeben, die das LCD damit in seine
Zeichensatztabelle schreibt.
Durch die Wahl der Speicheradresse definiert man, welches Zeichen (0 bis 7) man eigentlich
durch eine eigene Definition ersetzen will.
ASCII Code
Zeichensatzadresse
0x00
0x08
0x10
0x18
0x20
0x28
0x30
0x38
Nach erfolgter Definition des Zeichens, muss die Schreibposition wieder explizit in den DDRAMBereich gesetzt werden. Danach kann ein entsprechendes Zeichen mit dem definierten ASCII
Code ausgegeben werden, wobei das LCD die von uns definierte Pixelform zur Anzeige
benutzt.
Zuerst mssen natrlich erstmal die Zeichen definiert werden. Dieses geschieht einmalig durch
den Aufruf der Routine "lcd_load_user_chars" unmittelbar nach der Initialisierung des LCDDisplays.
.
.
rcall lcd_init
rcall lcd_load_user_chars
rcall lcd_clear
.
.
; Display initialisieren
; User Zeichen in das Display laden
; Display lschen
Durch diesen Aufruf werden die im Flash definierten Zeichen in den GC-Ram bertragen. Diese
Zeichen werden ab Adresse 0 im GC-Ram gespeichert und sind danach wie jedes andere
Zeichen nutzbar.
.
.
ldi
rcall
ldi
rcall
ldi
rcall
ldi
rcall
ldi
rcall
ldi
rcall
ldi
rcall
ldi
rcall
.
.
temp1, 0
lcd_data
temp1, 6
lcd_data
temp1, 5
lcd_data
temp1, 4
lcd_data
temp1, 3
lcd_data
temp1, 2
lcd_data
temp1, 1
lcd_data
temp1, 0
lcd_data
Jetzt sollte der Schriftzug "AVR-MEGA" verkehrt herum (180 Grad gedreht) erscheinen.
Es fehlt natrlich noch die Laderoutine:
;**********************************************************************
;
; Ldt User Zeichen in den GC-Ram des LCD bis Tabellenende (0xFF)
; gelesen wird. (max. 8 Zeichen knnen geladen werden)
;
; bergabe:
; vernderte Register: temp1, temp2, temp3, zh, zl
; Bemerkung:
ist einmalig nach lcd_init aufzurufen
;
lcd_load_user_chars:
ldi
zl, LOW (ldc_user_char * 2) ; Adresse der Zeichentabelle
ldi
zh, HIGH(ldc_user_char * 2) ; in den Z-Pointer laden
clr
temp3
; aktuelles Zeichen = 0
lcd_load_user_chars_2:
clr
temp2
; Linienzhler = 0
lcd_load_user_chars_1:
ldi
temp1, 0b01000000
add
temp1, temp3
add
temp1, temp2
rcall lcd_command
;
;
;
;
Kommando:
0b01aaalll
+ akt. Zeichen (aaa)
+ akt. Linie
(lll)
Kommando schreiben
lpm
rcall
temp1, Z+
lcd_data
; Zeichenline laden
; ... und ausgeben
ldi
add
add
rcall
temp1, 0b01001000
temp1, temp3
temp1, temp2
lcd_command
; Kommando:
0b01aa1lll
; + akt. Zeichen (aaa)
; + akt. Linie
(lll)
lpm
rcall
temp1, Z+
lcd_data
; Zeichenline laden
; ... und ausgeben
inc
cpi
brne
temp2
temp2, 8
lcd_load_user_chars_1
; Linienzhler + 1
; 8 Linien fertig?
; nein, dann nchste Linie
subi
lpm
cpi
brne
temp3, -0x10
temp1, Z
temp1, 0xFF
lcd_load_user_chars_2
;
;
;
;
;
ret
... und die Zeichendefinition:
ldc_user_char:
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
0b10001,
0b10001,
0b11111,
0b10001,
0b10001,
0b10001,
0b01110,
0b00000,
0b10001,
0b01001,
0b00101,
0b11111,
0b10001,
0b10001,
0b01111,
0b00000,
0b00100
0b01010
0b10001
0b10001
0b10001
0b10001
0b10001
0b00000
;
;
;
;
;
;
;
;
;
;
0b00000
0b00000
0b00000
0b11111
0b00000
0b00000
0b00000
0b00000
;
;
;
;
;
;
;
;
;
;
Zeichen
0
@
@
@
@
@@@@@
@
@
@
@
@
@
@@@
,
,
,
,
,
,
,
,
1
@
@ @
@
@
@
@
@
Zeichen
2
@
@
@ @
@ @
@@@@@
@
@
@
@
@@@@
@
@
@
@
@
3
,
,
,
, @@@@@
,
,
,
,
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
0b10001,
0b10001,
0b10001,
0b10001,
0b10101,
0b11011,
0b10001,
0b00000,
0b11110,
0b10001,
0b10001,
0b11101,
0b00001,
0b10001,
0b01110,
0b00000,
0b11111
0b00001
0b00001
0b01111
0b00001
0b00001
0b11111
0b00000
;
;
;
;
;
;
;
;
;
;
0b11111
0b01010
0b00100
0b01110
0b00100
0b01010
0b11111
0b00000
;
;
;
;
;
;
;
;
;
;
Zeichen
4
@
@
@
@
@
@
@
@
@ @ @
@@ @@
@
@
5
, @@@@@
,
@
,
@
, @@@@
,
@
,
@
, @@@@@
,
Zeichen
6
7
@@@@ , @@@@@
@
@ , @ @
@
@ ,
@
@@@ @ , @@@
@ ,
@
@
@ , @ @
@@@ , @@@@@
,
; End of Tab
.db 0xFF, 0xFF
[Bearbeiten]Der
lcd_init
lcd_clear
lcd_home
lcd_data
lcd_command
lcd_flash_string
lcd_number
lcd_number_hex
sind so ausgefhrt, dass sie kein Register (ausser dem Statusregister SREG) verndern. Die
bei manchen Funktionen notwendige Argumente werden immer im Register temp1 bergeben,
wobeitemp1 vom Usercode definiert werden muss.
Download lcd-routines.asm
AVR-Tutorial: Interrupts
Inhaltsverzeichnis
[Verbergen]
1 Definition
2 Mgliche Auslser
3 INT0, INT1 und die zugehrigen Register
4 Interrupts generell zulassen
5 Die Interruptvektoren
6 Beenden eines Interrupthandlers
7 Aufbau der Interruptvektortabelle
8 Beispiel
9 Besonderheiten des Interrupthandlers
10 Siehe auch
[Bearbeiten]Definition
Bei bestimmten Ereignissen in Prozessoren wird ein sogenannter Interrupt ausgelst.
Interrupts machen es mglich, beim Eintreten eines Ereignisses sofort informiert zu werden,
ohne permanent irgendeinen Status abzufragen, was teure Rechenzeit kosten wrde. Dabei
wird das Programm unterbrochen und ein Unterprogramm aufgerufen. Wenn dieses beendet ist,
luft das Hauptprogramm ganz normal weiter.
[Bearbeiten]Mgliche
Auslser
sich der an einem bestimmten Eingangs-Pin anliegende Wert von High auf Low ndert
(oder umgekehrt)
...
Wir wollen uns hier erst mal die beiden Interrupts INT0 und INT1 anschauen. INT0 wird
ausgelst, wenn sich der an PD2 anliegende Wert ndert, INT1 reagiert auf nderungen an
PD3.
Als erstes mssen wir die beiden Interrupts konfigurieren. Im Register MCUCR wird eingestellt,
ob die Interrupts bei einer steigenden Flanke (low nach high) oder bei einer fallenden Flanke
(high nach low) ausgelst werden. Dafr gibt es in diesem Register die
Bits ISC00, ISC01 (betreffen INT0) und ISC10 und ISC11 (betreffen INT1).
Hier eine bersicht ber die mglichen Einstellungen und was sie bewirken:
ISC11 o. ISC01
ISC10 o. ISC00
Beschreibung
Danach mssen diese beiden Interrupts aktiviert werden, indem die Bits INT0 und INT1 im
Register GICR auf 1 gesetzt werden.
Die Register MCUCR und GICR gehren zwar zu den IO-Registern, knnen aber nicht wie
andere mit den Befehlen cbi und sbi verwendet werden. Diese Befehle wirken nur auf die IORegister bis zur Adresse 0x1F (welches Register sich an welcher IO-Adresse befindet, steht in
der Include-Datei, hier "m8def.inc", und im Datenblatt des Controllers). Somit bleiben zum
Zugriff auf diese Register nur die Befehle in und out brig.
[Bearbeiten]Interrupts
generell zulassen
Schlielich muss man noch das Ausfhren von Interrupts allgemein aktivieren, was man durch
einfaches Aufrufen des Assemblerbefehls sei bewerkstelligt.
[Bearbeiten]Die
Interruptvektoren
Woher wei der Controller jetzt, welche Routine aufgerufen werden muss wenn ein Interrupt
ausgelst wird?
Wenn ein Interrupt auftritt, dann springt die Programmausfhrung an eine bestimmte Stelle im
Programmspeicher. Diese Stellen sind festgelegt und knnen nicht gendert werden:
Nr. Adresse
Interruptname
Beschreibung
0x000
RESET
0x001
INT0
Externer Interrupt 0
0x002
INT1
Externer Interrupt 1
0x003
TIMER2_COMP
0x004
TIMER2_OVF
Timer/Counter2 Overflow
0x005
TIMER1_CAPT
0x006
0x007
0x008
TIMER1_OVF
Timer/Counter1 Overflow
10
0x009
TIMER0_OVF
Timer/Counter0 Overflow
11
0x00A
SPI_STC
SPI-bertragung abgeschlossen
12
0x00B
USART_RX
USART-Empfang abgeschlossen
13
0x00C
USART_UDRE
USART-Datenregister leer
14
0x00D
USART_TX
15
0x00E
ADC
16
0x00F
EE_RDY
17
0x010
ANA_COMP
Analogkomparator
18
0x011
TWI
Two-Wire Interface
19
0x012
SPM_RDY
USART-Sendung abgeschlossen
AD-Wandlung abgeschlossen
EEPROM bereit
So, wir wissen jetzt, dass der Controller zu Adresse 0x001 springt, wenn INT0 auftritt. Aber dort
ist ja nur Platz fr einen Befehl, denn die nchste Adresse ist doch fr INT1 reserviert. Wie geht
das? Ganz einfach: Dort kommt ein Sprungbefehl rein, z. B. rjmp interrupt0. Irgendwo anders
im Programm muss in diesem Fall eine Stelle mit interrupt0: gekennzeichnet sein, zu der dann
gesprungen wird. Diese durch den Interrupt aufgerufene Routine nennt
man Interrupthandler (engl. Interrupt Service Routine, ISR).
[Bearbeiten]Beenden
eines Interrupthandlers
Und wie wird die Interruptroutine wieder beendet? Durch den Befehl reti. Wird dieser
aufgerufen, dann wird das Programm ganz normal dort fortgesetzt, wo es durch den Interrupt
unterbrochen wurde. Es ist dabei wichtig, da hier der Befehl reti und nicht ein
normaler ret benutzt wird. Wird ein Interrupt Handler betreten, so sperrt der Mikrocontroller
automatisch alle weiteren Interrupts. Im Unterschied zu ret, hebt ein reti diese Sperre wieder
auf.
[Bearbeiten]Aufbau
der Interruptvektortabelle
Jetzt mssen wir dem Assembler nur noch klarmachen, dass er unser rjmp interrupt0 an die
richtige Stelle im Programmspeicher schreibt, nmlich an den Interruptvektor fr INT0. Dazu
gibt es eine Assemblerdirektive. Durch .org 0x001 sagt man dem Assembler, dass er die
darauffolgenden Befehle ab Adresse 0x001 im Programmspeicher platzieren soll. Diese Stelle
wird von INT0angesprungen.
Damit man nicht alle Interruptvektoren immer nachschlagen muss, sind in der Definitionsdatei
m8def.inc einfach zu merkende Namen fr die Adressen definiert. Statt 0x001 kann man z. B.
einfachINT0addr schreiben. Das hat auerdem den Vorteil, dass man bei Portierung des
Programms auf einen anderen AVR-Mikrocontroller nur die passende Definitionsdatei einbinden
muss, und sich ber evtl. genderte Adressen fr die Interruptvektoren keine Gedanken zu
machen braucht.
Nun gibt es nur noch ein Problem: Beim Reset (bzw. wenn die Spannung eingeschaltet wird)
wird das Programm immer ab der Adresse 0x000 gestartet. Deswegen muss an diese Stelle ein
Sprungbefehl zum Hauptprogramm erfolgen, z. B. rjmp RESET um an die
mit RESET: markierte Stelle zu springen.
Wenn man mehrere Interrupts verwenden mchte, kann man auch, anstatt jeden Interruptvektor
einzeln mit .org an die richtige Stelle zu rcken, die gesamte Sprungtabelle ausschreiben:
.include "m8def.inc"
.org 0x000
rjmp RESET
rjmp
rjmp
rjmp
rjmp
rjmp
rjmp
rjmp
rjmp
rjmp
rjmp
rjmp
rjmp
rjmp
rjmp
rjmp
rjmp
rjmp
rjmp
EXT_INT0
EXT_INT1
TIM2_COMP
TIM2_OVF
TIM1_CAPT
TIM1_COMPA
TIM1_COMPB
TIM1_OVF
TIM0_OVF
SPI_STC
USART_RXC
USART_DRE
USART_TXC
ADC
EE_RDY
ANA_COMP
TWSI
SPM_RDY
RESET:
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
Hier ist es unbedingt ntig, bei unbenutzten Interruptvektoren statt des Sprungbefehls den
Befehl reti (bzw. reti nop, wenn jmp 4 Byte lang ist) reinzuschreiben. Wenn man einen Vektor
einfach weglsst stehen die nachfolgenden Sprungbefehle sonst alle an der falschen Adresse
im Speicher.
Wer auf Nummer sicher gehen mchte kann aber auch alle Vektoren einzeln mit .org
adressieren:
.include "m8def.inc"
.org 0x000
rjmp RESET
.org INT0addr
reti
.org INT1addr
reti
.org OC2addr
reti
.org OVF2addr
reti
.org ICP1addr
reti
.org OC1Aaddr
reti
.org OC1Baddr
reti
.org OVF1addr
reti
.org OVF0addr
reti
.org SPIaddr
reti
.org URXCaddr
reti
.org UDREaddr
reti
.org UTXCaddr
reti
.org ADCCaddr
reti
.org ERDYaddr
reti
.org ACIaddr
reti
.org TWIaddr
reti
.org SPMRaddr
reti
.org INT_VECTORS_SIZE
RESET:
Statt die unbenutzten Interruptvektoren mit reti zu fllen knnte man sie hier auch einfach
weglassen, da die .org-Direktive dafr sorgt dass jeder Vektor in jedem Fall am richtigen Ort im
Speicher landet.
[Bearbeiten]Beispiel
So knnte ein Minimal-Assemblerprogramm aussehen, das die Interrupts INT0 und INT1
verwendet. An die Interrupt Pins knnen zb Taster nach bewhrter Manier angeschlossen
werden. Die Interrupts werden auf fallende Flanke konfiguriert, da ja die Taster so
angeschlossen sind, dass sie im Ruhezustand eine 1 liefern und bei einem Tastendruck nach 0
wechseln.
Download extinttest.asm
.include "m8def.inc"
.def temp = r16
.org 0x000
rjmp main
.org INT0addr
rjmp int0_handler
.org INT1addr
rjmp int1_handler
main:
; Reset Handler
; IRQ0 Handler
; IRQ1 Handler
; hier beginnt das Hauptprogramm
ldi
out
ldi
out
temp, LOW(RAMEND)
SPL, temp
temp, HIGH(RAMEND)
SPH, temp
ldi temp, (1<<ISC01) | (1<<ISC11) ; INT0 und INT1 auf fallende Flanke konfigu
out MCUCR, temp
ldi temp, (1<<INT0) | (1<<INT1) ; INT0 und INT1 aktivieren
out GICR, temp
loop:
sei
rjmp loop
int0_handler:
sbi PORTB, 0
reti
int1_handler:
cbi PORTB, 0
reti
Fr dieses Programm braucht man nichts weiter als eine LED an PB0 und je einen Taster an
PD2 (INT0) und PD3 (INT1). Wie diese angeschlossen werden, steht in Teil 2 des Tutorials.
Die Funktion ist auch nicht schwer zu verstehen: Drckt man eine Taste, wird der dazugehrige
Interrupt aufgerufen und die LED an- oder abgeschaltet. Das ist zwar nicht sonderlich
spektakulr, aber das Prinzip sollte deutlich werden.
Meistens macht es keinen Sinn, Taster direkt an einen Interrupteingang anzuschlieen. Das
kann bisweilen sogar sehr schlecht sein, siehe Entprellung. Hufiger werden Interrupts in
Zusammenhang mit dem UART verwendet, um z. B. auf ein empfangenes Zeichen zu
reagieren. Wie das funktioniert, wird im Kapitel ber den UART beschrieben.
[Bearbeiten]Besonderheiten
des Interrupthandlers
Der Interrupthandler kann ja mehr oder weniger zu jedem beliebigen Zeitpunkt unabhngig vom
restlichen Programm aufgerufen werden. Dabei soll das restliche Programm auf keinen Fall
durch den Interrupthandler negativ beeinflusst werden, das heit das Hauptprogramm soll nach
dem Beenden des Handlers weiterlaufen als wre nichts passiert. Insbesondere muss deshalb
darauf geachtet werden, dass im Interrupthandler Register, die vom Programmierer nicht
ausschlielich nur fr den Interrupthandler reserviert wurden, auf dem Stack gesichert und zum
Schluss wieder hergestellt werden mssen.
Ein Register, das gerne bersehen wird, ist das Status Register. In ihm merkt sich der
Prozessor bestimmte Zustnde von Berechnungen, z. B. ob ein arithmetischer berlauf
stattgefunden hat, ob das letzte Rechenergebnis 0 war, etc. Sobald ein Interrupthandler etwas
komplizierter wird als im obigen Beispiel, tut man gut daran, das SREG Register auf jeden Fall
zu sichern. Ansonsten kann das Hinzufgen von weiterem Code zum Interrupthandler schnell
zum Boomerang werden: Die dann mglicherweise notwendige Sicherung des SREG Registers
wird vergessen. berhaupt empfiehlt es sich, in diesen Dingen bei der Programmierung eines
Interrupthandlers eher vorausschauend, bervorsichtig und konservativ zu programmieren. Wird
dies getan, so vergeudet man hchstens ein bischen Rechenzeit. Im anderen Fall handelt man
sich allerdings einen Super-GAU ein: Man steht dann vor einem Programm, das sporadisch
nicht funktioniert und keiner weiss warum. Solche Fehler sind nur sehr schwer und oft nur mit
einem Quntchen Glck zu finden.
Im Beispiel wre zwar das Sichern und Wiederherstellen der Register temp und SREG nicht
wirklich notwendig, aber hier soll die grundstzliche Vorgehensweise gezeigt werden:
.include "m8def.inc"
.def temp = r16
.org 0x000
rjmp main
.org INT0addr
rjmp int0_handler
.org INT1addr
rjmp int1_handler
main:
; Reset Handler
; IRQ0 Handler
; IRQ1 Handler
temp, LOW(RAMEND)
SPL, temp
temp, HIGH(RAMEND)
SPH, temp
ldi temp, (1<<ISC01) | (1<<ISC11) ; INT0 und INT1 auf fallende Flanke konfigu
out MCUCR, temp
ldi temp, (1<<INT0) | (1<<INT1) ; INT0 und INT1 aktivieren
out GICR, temp
sei
loop:
rjmp loop
int0_handler:
push temp
in
temp, SREG
sbi PORTB, 0
out SREG, temp
pop temp
reti
int1_handler:
push temp
in
temp, SREG
cbi PORTB, 0
out SREG, temp
pop temp
reti
[Bearbeiten]Siehe
auch
Interrupt
Bei bestimmten Ereignissen in Prozessoren wird ein Interrupt (Unterbrechungsanforderung)
registriert.
Bei Mikrocontrollern werden Interrupts z. B. ausgelst wenn:
Die Registrierung eines Interrupts setzt ein passend zum Ereignis benanntes Interruptflag in
Form eines Bits in einem speziellen Statusregister. Bei der Behandlung des Interrupts wird das
Anwendungsprogramm unterbrochen, das auslsende Interruptflag gelscht und ein
Unterprogramm, die sogenannte Interrupt Service Routine (ISR), aufgerufen. Wenn dieses
beendet ist, luft das Anwendungsprogramm ganz normal weiter.
Inhaltsverzeichnis
[Verbergen]
1 Wichtige Eigenschaften von ISRs
o
1.1 Interruptsteuerung
1.6 Zusammenfassung
2 Interruptfeste Programmierung
[Bearbeiten]Wichtige
ISRs reagieren auf ein bestimmtes Ereignis, welches relativ oft oder selten passiert. Prinzipiell
sollte man ISRs mglichst kurz halten und schnell beenden.
Im Mittel muss die Interruptroutine krzer sein als die Periodendauer des Ereignisses,
andernfalls wird es passieren, dass Interrupts "verschluckt" werden, d.h. beim UART gehen
Daten verloren, beim Timer gehen Zhlzyklen verloren, beim AD-Wandler gehen Daten verloren
etc.. Solche verschluckten Interrupts sind bisweilen schwer zu finden, weil es nur sehr wenige in
ganz bestimmten Konstellationen sind. Wenn dann eine per Timer realisierte Uhr in der Stunde
um 1s falsch geht, merkt man das oft nicht. Langwierige Berechnungen, Auswertungen,
Ausgaben oder gar Warteschleifen haben daher in ISRs nichts zu suchen. Auch typische CFunktionen wie printf(), scanf(), lngere Ausgaben auf ein LCD etc. sollte man nicht in ISRs
vornehmen.
Stattdessen kommt bei Interruptbetrieb sinnvollerweise eine andere Programmiertechnik zu
Einsatz, nmlich die bergabe von Parametern bzw. Steuersignalen an das Hauptprogramm.
Hierbei ist wichtig, dass die Steuervariable ("Flag"), welche gemeinsam im InterruptProgrammteil und im Nicht-Interrupt-Programmteil verwendet wird, mit dem C
Schlsselwort volatile deklariert wird. Dadurch wird sichergestellt, dass jeder Zugriff auf die
Variable im Code auch in die entsprechenden Maschinenbefehle umgesetzt wird und nicht
wegoptimiert wird, weil sich die Variable in einem der beiden unabhngigen Programmteile
scheinbar nicht ndert. Auerdem mssen sowohl der Lese- als auch Schreibzugriff auf
Steuervariablen ununterbrechbar (atomar) sein.
[Bearbeiten]Interruptsteuerung
Interrupts mssen wie alle anderen Module und Funktionen eines Mikrocontrollers gesteuert
werden. Dazu wird auf praktisch allen Mikrocontrollern ein zweistufiges System verwendet.
Globale Interruptsteuerung ber ein CPU-Statusbit: Beim AVR ist das das I-Bit
(Interrupt) im Statusregister (SREG). Dieses Bit wirkt wie ein Hauptschalter und kann
global die Ausfhrung aller Interrupts ein - und ausschalten. Das heisst aber nicht, dass
whrend der Zeit der inaktiven Interrupts diese verloren gehen. Vielmehr wird das
jeweilige Interruptbit gesetzt, und wenn die Interrupts wieder freigegeben werden wird
der Interrupt ausgefhrt. Verloren gehen Interrupts erst dann, wenn die Sperrzeit zu
gro ist und whrenddessen mehr als ein Interrupt vom selben Typ eintrifft.
Siehe Beispiel 1 und Beispiel 2.
Dieses System hat eine Reihe von Vorteilen. So knnen sehr schnell und einfach alle Interrupts
kurzzeitig gesperrt werden, wenn beispielsweise atomare Operationen durchgefhrt werden
sollen, oder besonders zeitkritische Ablufe ausgefhrt werden. Danach knnen alle
konfigurierten Interrupts einfach wieder freigeschaltet werden, ohne dass die CPU viele
verschiedene Interruptmaskenbits verwalten msste.
Eine ISR wird demnach nur dann ausgefhrt, wenn
[Bearbeiten]Verschachtelte Interrupts
Einige Mikrocontroller, wie z. B. der AVR kennen nur zwei CPU-Zustnde. Normale
Programmausfhrung und Interruptausfhrung, gesteuert durch das I-Bit der CPU. Die normale
Programmausfhrung kann jederzeit durch Interrupts unterbrochen werden. Die
Interruptausfhrung kann nicht durch neue Interrupts unterbrochen werden. Die ISR wird erst zu
Ende bearbeitet, zurck in die normale Programmausfhrung gesprungen und erst dann
werden neue, wartende (engl. pending) Interrupts bearbeitet.
Etwas komplexere Mikrocontroller oder groe Prozessoren bieten verschiedene Interruptlevel
(Stufen) an . Dabei gilt meist je niedriger die Zahl des Levels, um so hher die Prioritt. Ein
Interrupt mit hherer Prioritt kann einen Interrupt mit niedriger Prioritt unterbrechen. Ein
Interrupt mit gleicher Prioritt wie der gerade bearbeitete Interrupt kann das im allgemeinen
nicht. Das nennt man verschachtelte Interrupts (engl. nested interrupts). Klassische Vertreter
hierfr sind PIC18, 8051, PowerPC, X86 und Motorola 68000.
Auf dem AVR kann man verschachtelte Interrupts sowohl in Assembler als auch in C
nachbilden, allerdings mit einigen Einschrnkungen und Tcken. Das ist jedoch Leuten
vorbehalten, die schon viel Erfahrung auf diesem Gebiet haben. Zu 99,9% braucht man sie
nicht.
[Bearbeiten]Wie lange dauert meine Interruptroutine?
Diese Frage sollte man beantworten knnen, zumindest sollte eine Worst-Case-Abschtzung
gemacht werden. Das geht auf zwei Wegen.
Simulation, dabei muss in einer verzweigten ISR der lngste Pfad simuliert werden.
Dazu mssen alle beteiligten Variablen auf den ensprechenden Wert gesetzt werden.
Messung mit dem Oszilloskop, dabei wird zum Beginn der ISR ein Pin auf HIGH gesetzt
und am Ende auf LOW. Damit kann man in Echtzeit die Dauer der ISR messen. Die
zustzlichen Taktzyklen zum Aufruf und verlassen der ISR sind konstant und im
wesentlichen bekannt. Mit einem modernen Digitaloszilloskop und dem "Infinite
Persistence Mode" kann man eine Worst-Case-Messung vornehmen
Als Hilfsmittel zur Fehlersuche kann man auch am Ende der ISR prfen, ob das jeweilige
Interrupt-Request-Bit schon wieder gesetzt ist. Wenn ja, dann ist die ISR in den meisten Fllen
zu lang. Auch hier kann man einen Ausgang auf HIGH setzen und somit den Fehler anzeigen.
[Bearbeiten]Zeitverhalten eines Timerinterrupts
Ein Timerinterrupt wird im allgemeinen dazu genutzt, in konstanten, periodischen Abstnden
bestimmte Funktionen aufzurufen. Es ist mglich, dass whrend eines Timerinterrupts derselbe
Interrupt wieder aktiv wird, weil die Routine sehr verzweigt ist und dieses Mal sehr lange dauert.
Wenn zum Beispiel der Timerinterrupt mit einer Periodendauer von 100ms aufgerufen wird, er
aber unter bestimmten Umstnden 180ms bentigt, dann wird nach 100ms nach Eintritt in die
ISR der Interrupt wieder aktiv, das Timer Interrupt Flag wird gesetzt. Da aber gerade ein
Interrupt bearbeitet wird, wird er nicht sofort angesprungen, weil whrenddessen die
Interruptfunktion global gesperrt ist (beim AVR ist das I-Bit in der CPU gelscht). Der Interrupt
wird zu Ende bearbeitet, die CPU springt zurck zum Hauptprogramm. Dabei werden die
Interrupts wieder global eingeschaltet. Der zwischenzeitlich eingetroffene und
zwischengespeicherte Interrupt wird nun sofort ausgefhrt, sodass das Hauptprogramm
praktisch gar nicht weiter kommt, bestenfalls einen Maschinenbefehl. Nun sind aber nur noch
20ms bis zum nchsten Timerinterrupt brig. Wenn dieser nun wieder 180 ms bentigt werden
in dieser Zeit aber zwei Interrupts ausgelst, nach 20ms und 120ms. Da diese aber nicht
gezhlt oder andersweitig einzeln gespeichert werden knnen, geht ein Interrupt verloren. Das
ist ein Programmfehler.
[Bearbeiten]Zeitverhalten des UART Empfangsinterrupts
Ein UART Interrupt zum Empfang von Daten per RS232 mit 115200 Baud ist ein recht hufiges
Ereignis (1 Zeichen = 10 Bits = 86,8s). Wenn kontinuierlich Daten empfangen werden, wird
nach jeweils 86,8s ein neuer Interrupt ausgelst. Dabei wird das empfangene Datenbyte vom
UART aus dem Empfangsschiebegregister in einem Puffer kopiert. Whrend das neue Zeichen
Bit fr Bit empfangen wird, wird es zunchst im Schieberegister des UART gespeichert. Die
Daten im Puffer bleiben davon unberhrt. Die CPU muss nun schnell das empfangene
Datenbyte aus dem Empfangsbuffer auslesen. Die maximale Verzgerung, die sich die CPU
erlauben kann von der Aktivierung des Interrupts bis zum tatschlichen Auslesen des
Datenregisters betrgt ziemlich genau die bertragungszeit von einem Zeichen. Wenn bis dahin
nicht das Zeichen von der CPU ausgelesen wurde, wird es vom UART berschrieben und ein
Fehler im Statusregister des UART signalisiert (Overrun, berlauf des Datenpuffers). Die
UARTs in heutigen Mikrocontrollern haben mindestens ein Byte Puffer wie hier beschrieben. Die
neueren AVRs haben sogar effektiv 3 Byte Puffer im UART, praktisch ein kleines FIFO, womit
der Datenempfang besser gepuffert werden kann, wenn die CPU gerade mit anderen sehr
wichtigen Dingen beschftigt ist. D.h. kurzzeitig kann sich die CPU erlauben, die
bertragungszeit von bis zu drei Zeichen zu warten, ehe sie die Daten ausliest. Dann mssen
sie aber sehr schnell hintereinander gelesen werden. Im Mittel hat die CPU aber nur die
bertragungszeit eines Zeichens zur Verfgung, um es abzuholen.
[Bearbeiten]Zusammenfassung
Interruptserviceroutinen:
knnen im Einzelfall nahezu doppelt so lange dauern wie die krzeste Periodendauer
des Ereignisses, ohne dass Interrupts verloren gehen (z. B. Timerinterrupt).
drfen im Mittel maximal solange dauern wie die krzeste Periodendauer des
Ereignisses
drfen maximal solange dauern, wie die krzeste Periodendauer des Ereignisses, wenn
man auf Nummer sicher gehen will, dass keine Interrupts verschluckt werden
Die Interruptzeit versteht sich immer abzglich einer kleinen Reserve fr das
Anspringen und Verlassen des Interrupt minus Panikreserve
[Bearbeiten]Interruptfeste
Programmierung
[Bearbeiten]Atomarer Datenzugriff
Von einem atomaren (engl. atomic) Datenzugriff spricht man, wenn der Zugriff innerhalb einer
nicht-unterbrechbaren Instruktionsfolge abgearbeitet wird.
Alle Variablen, Steuerregister und I/O-Ports, die sowohl im Hauptprogramm als auch in
Interrupts verwendet werden, sind mit viel Sorgfalt zu behandeln.
Beispiel:
port |= 0x03;
bersetzt sich auf AVR-Prozessoren in
IN r16,port
ORI r16,0x03
OUT port,r16
Wenn nun zwischen IN und OUT ein Interrupt auftritt, der beispielsweise Bit 7 verndert, dann
geht mit dem OUT-Befehl diese nderung verloren, da der OUT-Befehl den alten Zustand vor
dem Interrupt wiederherstellt.
Gefhrlich ist das insbesondere deshalb, weil der Fall nur selten auftritt und dieses
Verhalten sehr schlecht reproduzierbar ist.
#0x03,port
und stellt somit kein Problem dar. Im Zweifel hilft nur ein Blick in den erzeugten AssemblerCode. Bei der bernahme fremden Codes ist dies zu beachten. Was beim 8051 kein Problem
war, kann beim AVR zu einem Problem werden, unter Umstnden sogar abhngig vom
verwendeten Port sein.
Ein hnliches Problem entsteht bei Variablen, deren Gre die Wortbreite der Maschine
bersteigt. Bei 8-Bit-Prozessoren wie AVR oder 8051 also bereits bei normalen "int" Variablen.
Diese Variablen werden zwangslufig byteweise verarbeitet. Wenn genau dazwischen ein
Interrupt erfolgt, wird ein falscher Wert gelesen. Wenn beispielsweise eine Interrupt-Routine
einen 16-Bit-Zhler verwendet und von 0x00FF auf 0x0100 hochzhlt, dann kann das
Hauptprogramm auch schon mal versehentlich die Werte 0x01FF oder 0x0000 lesen.
Dies ist auch ein Grund, weshalb fr Programmierung auf derart "niedriger" Ebene Kenntnisse
in Prozessorarchitektur und Assembler-Programmierung sehr hilfreich sind.
Abhilfe: Wenn man sich nicht wirklich ganz sicher ist, sollten um kritische Aktivitten herum
jedesmal die Interrupts abgeschaltet werden.
Beispiel (AVR-GCC):
cli(); // Interrupts abschalten
port |= 0x03;
sei(); // Interrupts wieder einschalten
Wenn man ein globales Einschalten der Interrupts mit sei() vermeiden will, kann man die
folgende Methode benutzen. Hierbei werden die Interrupts nur eingeschaltet, wenn sie vorher
bereits eingeschaltet waren (Hinweis aus der FAQ von avr-libc):
{
// ...
{
uint8_t sreg_local; // Lokale Sicherungskopie von SREG
sreg_local = SREG;
cli();
// hierhin kommt der Code mit atomarem Zugriff
SREG = sreg_local;
}
// ...
}
Je nach Prozessor kann man das Problem manchmal auch ohne Abschalten von Interrupts
durch geeignete Programmierung lsen. So fhrt
port = (port & ~0x0F) | lcd_data;
immer zum beschriebenen Problem,
Variablen, auf die sowohl innerhalb wie auch auerhalb einer Interruptserviceroutine zugegriffen
wird (schreibend oder lesend), mssen (hnlich wie Hardwareregister) mit dem
Schlsselwort volatile(flchtig) versehen werden, damit der C-Compiler bercksichtigen kann,
dass diese Variablen jederzeit (durch das Auftreten des Interrupts) gelesen oder geschrieben
werden knnen. Ansonsten wrde der C-Compiler das regelmige Abfragen oder Beschreiben
dieser Variablen ggf. wegoptimieren, da er nicht damit rechnet, dass auf die Variable auch
"ohne sein Zutun" zugegriffen wird.
Eine ausfhrlichere Erklrung zu "volatile" ist hier zu finden: FAQ: Was hat es mit volatile auf
sich
[Bearbeiten]Interrupts
Wenn der Microcontroller in einen Low Power Mode versetzt wird, wird er durch einen Interrupt
wieder aufgeweckt, z.B. in einem festen Zeitraster per Timer-Interrupt oder vom ADC nach
Beendigung einer AD-Wandlung.
Dabei mu sichergestellt werden, da der Interrupt erst nach dem In-Low-Power-Gehen (z.B.
per Befehl
Sleep();
) kommen kann, da sonst der C nicht bzw. nicht rechtzeitig geweckt wird. Dazu mu eine
Mglichkeit bestehen, da der Interrupt gesperrt wird und erst mit dem Sleep-Befehl
freigegeben wird. Dies scheint auf den ersten Blick unmglich: Man kann nicht gleichzeitig zwei
Befehle ( sei() und sleep() ) ausfhren.
Es gibt spezielle Mechanismen fr diesen Fall. Beim C51 und beim AVR ist es z.B. so, da sei()
erst einen Befehl spter "wirksam" wird. Dadurch wird die Kombination
sei();
sleep();
ununterbrechbar (natrlich sofern zuvor die Interrupts gesperrt wurden).
Andere Microcontroller bieten andere Mechanismen, z.B. sperrt der Assembler-Befehl
DISI
beim PIC24 die Interrupts fr eine bestimmten Anzahl von CPU-Taktzyklen. Die CPU kann dann
eine vorgegebene Anzahl an folgenden Befehlen unter Interruptsperre ausfhren und der
Interrupt wird automatisch wieder freigegeben.
[Bearbeiten](AVR-)
Die Beispiele sind mit WINAVR 20060421 compiliert und getestet worden. Als Mikrocontroller
wird ein AVR vom Typ ATmega32 verwendet. Alle Programme wurden mit Optimierungsstufe
-Os compiliert.
[Bearbeiten]Steuersignale zwischen ISR und Hauptprogramm
=
=
=
=
0xFF;
0xFF;
0xFF;
0xFF;
// Timer2 konfigurieren
TCCR2 = (1<<CS22) | (1<<CS21); // Vorteiler 256 -> ~65ms berlaufperiode
TIMSK |= (1<<TOIE2);
// Timer Overflow Interrupt freischalten
// Interrupts freigeben
sei();
// Endlose Hauptschleife
while(1) {
if (flag == 1) { // Neuer Timerzyklus ?
flag = 0;
// hier steht jetzt in Normalfall ein grosser Programmblock ;-)
PORTD ^= (1 << PD5);
// LED toggeln
}
}
// Timer2 overflow Interrupt
// hier wird der Hauptschleife ein neuer Timerinterrupt signalisiert
ISR( TIMER2_OVF_vect ) {
flag = 1;
}
[Bearbeiten]UART mit Interrupts
Der UART ist ein oft benutztes Modul eines Mikrocontrollers. Anfnger nutzen ihn meist im
sogenannten Polling Betrieb (engl. to poll, abfragen). D.h. wenn ein Zeichen empfangen werden
soll, fragt eine Funktion den UART in einer Schleife ununterbrochen ab, ob Daten empfangen
wurden. In dieser Zeit macht die CPU nichts anderes! Und wenn lange kein Zeichen eintrifft tut
sie sehr lange nichts, sie ist praktisch blockiert! Senden verluft hnlich, nur dass hier die CPU
vor dem Senden prft, ob der UART ein neues Byte aufnehmen kann. D.h. whrend der UART
selbstttig das Zeichen sendet ist die CPU zum Warten verdammt. All diese Nachteile haben
nur einen Vorteil. Die Funktionen und Mechanismen zur UART-Nutzung sind sehr einfach, klein
und leicht anwendbar.
Will man aber die CPU nicht sinnlos warten lassen, was vor allem bei niedrigeren Baudraten
ziemlich lange sein kann, muss man die Interrupts nutzen. Der AVR hat gleich drei davon.
UDRE (UART Data Register Empty): Der Zwischenpuffer des Senders ist leer und kann
ein neues Zeichen aufnehmen. Dieser Zwischenpuffer ist wichtig, um lckenlos auch
bei hohen Baudraten senden zu knnen.
TXC (Transmit Complete): Das aktuelle Zeichen wurde vollstndig inclusive Stopbit
gesendet und es liegt kein neues Datenbyte im Sendepuffer. Dieser Interrupt ist extrem
ntzlich fr eine Halbduplexkommunikation, z. B. auf einem RS485-Bus. Hier kann man
nach dem vollstndigen Senden aller Bytes den Bustranceiver (z. B. MAX485) von
Senden auf Empfangen umschalten, um den Bus freizugeben.
Bei Nutzung der Interrupts kann die CPU andere Dinge bearbeiten und muss nur kurz einen
Interrupt ausfhren, wenn ein Zeichen empfangen oder gesendet wurde.
Die Kommunikation zwischen ISRs und Hauptschleife erfolgt wieder durch Flags und zwei
Pufferarrays (uart_rx_buffer und uart_tx_buffer). Es gibt zwei Funktionen, eine zum Senden von
Strings, eine zum Empfangen. Das Senden sowie Empfangen kann parallel erfolgen und luft
vollkommen unabhngig vom Hauptprogramm. Die Daten werden in spezielle Puffer kopiert,
sodass das Hauptprogramm mit seinen Strings sofort weiterarbeiten kann. Im Beispiel ist die
CPU nicht wirklich mit sinnvollen Dingen beschftigt, zur Demonstration des Prinzips aber
ausreichend.
Um das Programm real zu nutzen braucht man ein Terminalprogramm, z. B. Hyperterminal von
Windows. Dort muss nur die richtige Baudrate eingestellt werden (9600 8N1, 9600 Baud, 8 Bits,
keine Paritt, 1 Stopbit, keine Flusskontrolle). Ausserdem muss man im Menu Datei ->
Eigenschaften -> Einstellungen -> ASCII Konfiguration den Punkt "Eingegebene Zeichen lokal
ausgeben (lokales Echo)" aktivieren. Nun kann man beliebige Texte eintippen. Mit RETURN
wird die Eingabe abgeschlossen und der AVR vermittelt den empfangenen String an das
Hauptprogramm. Diese sendet ihn einfach zurck, parallel dazu wird der String gemorst per
LED angezeigt. Sollte es Probleme bei der Inbetriebnahme des UART geben, so findet man hier
wichtige Hinweise zur Fehlersuche.
/*
*******************************************************************************
*
* UART Interrupt Demo
*
* ATmega32 mit 3,6864 MHz Quarz an XTAL1/XTAL2
*
* LOW Fuse Byte = 0xFF
*
* An PD5 muss eine LED mit 1-kOhm-Vorwiderstand angeschlossen werden
* An PD0/PD1 ist ein MAX232 angeschlosssen, um Daten vom PC zu empfangen/senden
*
*******************************************************************************
*/
// Systemtakt in Hz, das L am Ende ist wichtig, NICHT UL verwenden!
#define F_CPU 3686400L
// "Morsedauer" fr ein Bit in Millisekunden
#define BITZEIT 100
#include
#include
#include
#include
<string.h>
<avr/io.h>
<avr/interrupt.h>
<util/delay.h>
#endif
// globale Variablen fr den UART
// Puffergrsse in Bytes, RX und TX sind gleich gross
#define uart_buffer_size 32
volatile uint8_t uart_rx_flag=0;
volatile uint8_t uart_tx_flag=1;
char uart_rx_buffer[uart_buffer_size];
char uart_tx_buffer[uart_buffer_size];
//
//
//
//
if (uart_rx_flag==1) {
// String kopieren
strcpy(daten, uart_rx_buffer);
// Flag lschen
uart_rx_flag = 0;
}
// LED aus
// Prfe Bit #0
// LED an
else
// LED aus
// nchstes Bit auf Bit #0 schieben
}
// Stopbit, immer 1
PORTD |= (1 << PD5);
long_delay(BITZEIT);
// LED an
}
// Hauptprogramm
int main (void) {
char stringbuffer[64];
uint8_t buffer_full=0;
char * charpointer;
// IO konfigurieren
DDRA
DDRB
DDRC
DDRD
=
=
=
=
0xFF;
0xFF;
0xFF;
0xFF;
// UART konfigurieren
UBRRH = UBRR_VAL >> 8;
UBRRL = UBRR_VAL & 0xFF;
UCSRB = (1<<RXCIE) | (1<<RXEN) | (1<<TXEN);
// Stringpuffer initialisieren
stringbuffer[0] = '\n';
stringbuffer[1] = '\r';
// Interrupts freigeben
sei();
// Endlose Hauptschleife
while(1) {
// "Sinnvolle" CPU Ttigkeit
PORTD &= ~(1<<PD5);
long_delay(300);
PORTD |= (1<<PD5);
long_delay(300);
// Wurde ein kompletter String empfangen
// und der Buffer ist leer?
if (uart_rx_flag==1 && buffer_full==0) {
// ja, dann String lesen,
// die ersten zwei Zeichen
// aber nicht berschreiben
get_string(stringbuffer+2);
buffer_full=1;
}
}
// UART RX complete interrupt
// hier werden Daten vom PC empfangen und in einem String zwischengespeichert
// Wird ein Stringterminator empfangen, wird ein Flag gesetzt, welches dem
// Hauptprogramm den kompletten Empfang signalisiert
ISR(USART_RXC_vect) {
static uint8_t uart_rx_cnt;
uint8_t data;
ISR(TIMER0_OVF_vect)
{
sekunde++;
}
int main(void)
{
DDRB = (1<<PB0); // Pin PB0 Ausgang
setup();
while (1) {
int sekunde_kopie;
ATOMIC_BLOCK(ATOMIC_FORCEON)
{
sekunde_kopie = sekunde; // 16-Bit Zuweisung ist nicht atomar
// deshalb ATOMIC_BLOCK
}
if ( sekunde_kopie >= 25 ) {
ATOMIC_BLOCK(ATOMIC_FORCEON)
{
sekunde = 0; // 16-Bit Zuweisung ist nicht atomar
// deshalb ATOMIC_BLOCK
}
PORTB ^= (1<<PB0); // Toggle PB0
}
}
}
[Bearbeiten]Siehe
auch
ADC
Timer
AVR:
o
AVR-GCC-Tutorial - Interrupts in C
[Bearbeiten]Weblinks
Introduction to the Volatile Keyword von Nigel Jones auf Embedded Systems Design
AVR-Tutorial: Vergleiche
Vergleiche und Entscheidungen sind in jeder Programmiersprache ein zentrales Mittel um den
Programmfluss abhngig von Bedingungen zu kontrollieren. In einem AVR spielen dazu 4
Komponenten zusammen:
Vergleichsbefehle
bedingte Sprungbefehle
andere Befehle, die die Flags im Statusregister beeinflussen, wie zb die meisten
arithmetischen Funktionen
Der Zusammenhang ist dabei folgender: Die Vergleichsbefehle fhren einen Vergleich durch,
zum Beispiel zwischen zwei Registern oder zwischen einem Register und einer Konstante. Das
Ergebnis des Vergleiches wird in den Flags abgelegt. Die bedingten Sprungbefehle werten die
Flags aus und fhren bei einem positiven Ergebnis den Sprung aus. Besonders der erste
Satzteil ist wichtig! Den bedingten Sprungbefehlen ist es nmlich vllig egal, ob die Flags ber
Vergleichsbefehle oder ber sonstige Befehle gesetzt wurden. Die Sprungbefehle werten
einfach nur die Flags aus, wie auch immer diese zu ihrem Zustand kommen.
Inhaltsverzeichnis
[Verbergen]
1 Flags
o
2.1 CP - Compare
4.1 Entscheidungen
4.2 Schleifenkonstrukte
5 Literatur
[Bearbeiten]Flags
Die Flags sind Bits im Statusregister SREG. Ihre Aufgabe ist es, das Auftreten bestimmter
Ereignisse, die whrend Berechnungen eintreten knnen, festzuhalten. Speicherbefehle (LD,
LDI, ST, MOV, ...) haben auf dem AVR grundstzlich keinen Einfluss auf das Statusregister. Will
man den Inhalt eines Registers explizit testen (z. B. nach dem Laden aus dem SRAM), so kann
man hierfr den TST-Befehl verwenden.
Bits im SREG
[Bearbeiten]Carry (C)
Das Carry Flag hlt fest, ob es bei der letzten Berechnung einen ber- oder Unterlauf gab. Aber
Achtung: Nicht alle arithmetischen Befehle verndern tatschlich das Carry Flag. So haben zb
die Inkrementier und Dekrementierbefehle keine Auswirkung auf dieses Flag.
[Bearbeiten]Zero (Z)
Das Zero Flag hlt fest, ob das Ergebnis der letzten 8-Bit Berechnung 0 war oder nicht.
[Bearbeiten]Negative (N)
Spiegelt den Zustand des hchstwertigen Bits (Bit 7) der letzten 8-Bit-Berechnung wieder. In 2Komplement Arithmetik bedeutet ein gesetztes Bit 7 eine negative Zahl, das Bit kann also dazu
genutzt werden um festzustellen ob das Ergebnis einer Berechnung im Sinne einer 2Komplement Arithmetik positiv oder negativ ist.
[Bearbeiten]Overflow (V)
Dieses Bit wird gesetzt, wenn bei einer Berechnung mit 2-Komplement Arithmetik ein berlauf
(Unterlauf) stattgefunden hat. Dies entspricht einem berlauf von Bit 6 ins Bit 7.
Der bertrag, der bei der Addition/Subtraktion von Bit 6 auf Bit 7 auftritt, zeigt daher wenn er
vorhanden ist an, dass es sich hier um einen berlauf (Overflow) des Zahlenbereichs handelt
und das Ergebnis falsch ist. Das ist allerdings nicht der Fall, wenn auch der bertrag von Bit 7
nach Bit 8 (Carry) aufgetreten ist. Daher ist das Overflow-Flag die XOR-Verknpfung aus den
bertrag von bit 6 nach Bit 7 und dem Carry.
Beispiele fr die Anwendung des V-Flags finden sich in saturierter Arithmetik.
[Bearbeiten]Signed (S)
Das Signed-Bit ergibt sich aus der Antivalenz der Flags N und V, also S = N XOR V. Mit Hilfe
des Signed-Flags knnen vorzeichenbehaftete Werte miteinander verglichen werden. Ist nach
einem Vergleich zweier Register S=1, so ist der Wert des ersten Registers kleiner dem zweiten
(in der Signed-Darstellung). Damit entspricht das Signed-Flag gewissermaen dem Carry-Flag
fr Signed-Werte. Es wird hauptschlich fr 'Signed' Tests bentigt. Daher auch der Name.
[Bearbeiten]Half Carry (H)
Das Half Carry Flag hat die gleiche Aufgabe wie das Carry Flag, nur beschftigt es sich mit
einem berlauf von Bit 3 nach Bit 4, also dem bertrag zwischen dem oberen und unteren
Nibble. Wie beim Carry-Flag gilt, dass das Flag nicht durch Inkrementieren bzw.
Dekrementieren ausgelst werden kann. Das Haupteinsatzgebiet ist der Bereich der BCD
Arithmetik, bei der jeweils 4 Bits eine Stelle einer Dezimalzahl reprsentieren.
[Bearbeiten]Transfer (T)
Das T-Flag ist kein Statusbit im eigentlichen Sinne. Es steht dem Programmierer als 1-BitSpeicher zur Verfgung. Der Zugriff erfolgt ber die Befehle Bit Load (BLD), Bit Store (BST), Set
(SET) und Clear (CLT) und wird sonst von keinen anderen Befehlen beeinflusst. Damit knnen
Bits von einer Stelle schnell an eine andere kopiert oder getestet werden.
[Bearbeiten]Interrupt (I)
Das Interrupt Flag fllt hier etwas aus dem Rahmen; es hat nichts mit Berechnungen zu tun,
sondern steuert ob Interrupts im Controller zugelassen sind (siehe AVR-Tutorial: Interrupts).
[Bearbeiten]Vergleiche
Um einen Vergleich durchzufhren, wird intern eine Subtraktion der beiden Operanden
durchgefhrt. Das eigentliche Ergebnis der Subtraktion wird allerdings verworfen, es bleibt nur
die neue Belegung der Flags brig, die in weiterer Folge ausgewertet werden kann
[Bearbeiten]CP - Compare
Vergleicht den Inhalt zweier Register miteinander. Prozessorintern wird dabei eine Subtraktion
der beiden Register durchgefhrt. Das eigentliche Subtraktionsergebnis wird allerdings
verworfen, das Subtraktionsergebnis beeinflusst lediglich die Flags.
[Bearbeiten]CPC - Compare with Carry
Vergleicht den Inhalt zweier Register, wobei das Carry Flag in den Vergleich mit einbezogen
wird. Dieser Befehl wird fr Arithmetik mit groen Variablen (16/32 Bit) bentigt. Siehe AVRTutorial: Arithmetik.
[Bearbeiten]CPI - Compare Immediate
Vergleicht den Inhalt eines Registers mit einer direkt angegebenen Konstanten. Der Befehl ist
nur auf die Register r16..r31 anwendbar.
[Bearbeiten]Bedingte
Sprnge
Die bedingten Sprnge werten immer bestimmte Flags im Statusregister SREG aus. Es spielt
dabei keine Rolle, ob dies nach einem Vergleichsbefehl oder einem sonstigen Befehl gemacht
wird. Entscheidend ist einzig und alleine der Zustand des abgefragten Flags. Die Namen der
Sprungbefehle wurden allerdings so gewhlt, da sich im Befehlsnamen die Beziehung der
Operanden direkt nach einem Compare Befehl wiederspiegelt. Zu beachten ist auch, da die
Flags nicht nur durch Vergleichsbefehle verndert werden, sondern auch durch arithmetische
Operationen, Schiebebefehle und logische Verknpfungen. Da dieses Information wichtig ist, ist
auch in der bei Atmel erhltlichen bersicht ber alle Assemblerbefehle bei jedem Befehl
angegeben, ob und wie er Flags beeinflusst. Ebenso ist dort eine kompakte bersicht aller
bedingten Sprnge zu finden. Beachten muss man jedoch, dass die bedingten Sprnge
maximal 64 Worte weit springen knnen.
[Bearbeiten]Bedingte Sprnge fr vorzeichenlose Zahlen
BRSH - Branch if Same or Higher
Der Sprung wird durchgefhrt, wenn das Carry-Flag (C) nicht gesetzt ist. Wird dieser
Branch direkt nach einer CP, CPI, SUB oder SUBI-Operation eingesetzt, so findet der
Sprung dann statt, wenn der erste Operand grer oder gleich dem zweiten Operanden
ist. BRSH ist identisch mit BRCC (Branch if Carry Cleared).
BRLO - Branch if Lower
Der Sprung wird durchgefhrt, wenn das Carry-Flag (C) gesetzt ist. Wird dieser Branch
direkt nach einer CP, CPI, SUB oder SUBI Operation eingesetzt, so findet der Sprung
dann statt, wenn der erste Operand kleiner dem zweiten Operanden ist. BRLO ist
identisch mit BRCS (Branch if Carry Set).
[Bearbeiten]Bedingte Sprnge fr vorzeichenbehaftete Zahlen
r17, 25
nicht_gleich
r18, 0
rjmp
weiter
nicht_gleich:
ldi
r18,123
weiter:
;
;
;
;
;
In hnlicher Weise knnen die anderen bedingten Sprungbefehle eingesetzt werden, um die
blicherweise vorkommenden Vergleiche auf Gleichheit, Ungleichheit, Grer, Kleiner zu
realisieren.
[Bearbeiten]Schleifenkonstrukte
Ein immer wiederkehrendes Muster in der Programmierung ist eine Schleife. Die einfachste
Form einer Schleife ist die Zhlschleife. Dabei wird ein Register von einem Startwert
ausgehend eine gewisse Anzahl erhht, bis ein Endwert erreicht wird.
ldi
loop:
inc
cpi
brne
r17, 10
;
;
;
;
Sehr oft ist es auch mglich das Konstrukt umzudrehen. Anstatt von einem Startwert aus zu
inkrementieren gengt es die Anzahl der gewnschten Schleifendurchlufe in ein Register zu
laden und dieses Register zu dekrementieren. Dabei kann man von der Eigenschaft der
Dekrementieranweisung gebrauch machen, das Zero Flag (Z) zu beeinflussen. Ist das Ergebnis
des Dekrements 0, so wird das Zero Flag (Z) gesetzt, welches wiederum in der
nachfolgenden BRNE Anweisung fr einen bedingen Sprung benutzt werden kann. Das
vereinfacht die Schleife und spart eine Anweisung sowie einen Takt Ausfhrungzeit.
ldi
loop:
r17, 124
r17
loop
[Bearbeiten]Literatur
AVR Instruction Set
AVR-Tutorial: Mehrfachverzweigung
Inhaltsverzeichnis
[Verbergen]
1 Einleitung
2 Einfacher Ansatz
3 Sprungtabelle
4 Lange Sprungtabelle
5 Z-Pointer leicht verstndlich
[Bearbeiten]Einleitung
Oft ist es in einem Programm notwendig, eine Variable auf mehrere Werte zu prfen und
abhngig vom Ergebnis verschiedene Aktionen auszulsen. Diese Konstruktion nennt man
Mehrfachverzweigung. In einem Struktogramm sieht das so aus.
In Assembler muss man so etwas "zu Fu" programmieren. Die verschiedene Lsungen sollen
hier betrachtet werden.
[Bearbeiten]Einfacher
Ansatz
Im einfachsten Fall verwendet man eine lange Kette von cpi und breq Befehlen. Fr jeden
Zweig bentigt man zwei Befehle.
; Mehrfachverzeigung Version A
; Einfacher Ansatz, mit vielen CPI
start_vergleich:
cpi
brne
r16,1
zweig_0
ende_vergleich
r16,17
zweig_1
ende_vergleich
r16,33
zweig_2
ende_vergleich
r16,9
zweig_3
ende_vergleich
r16,22
kein_Treffer
; hier stehen jetzt alle Anweisungen fr den Fall, dass keiner der Vergleiche erfolgre
ende_vergleich:
rjmp
ende_vergleich
Eigenschaften
Laufzeit: n*3-1, Nicht gefunden: N*3 (N = Anzahl der Zweige, n = Ausgewhlter Zweig)
Vorteile
leicht verstndlich
Nachteile
die Gre der Zweige ist stark begrenzt, weil der Befehl breq maximal 63 Worte weit
springen kann!
die einzelnen Zweige haben unterschiedliche Durchlaufzeiten, der letzte Zweig ist am
langsamsten
[Bearbeiten]Sprungtabelle
Oft liegen die einzelnen Vergleichswerte nebeneinander (z. B. 7..15), z. B. bei der bergabe
von Parametern, Zustandsautomaten, Menueintrgen etc. . In so einem Fall kann man mittels
einerSprungtabelle das Programm verkrzen, beschleunigen und bersichtlicher gestalten.
.include "m8def.inc"
; Mehrfachverzweigung Version B
; Clevere Version mit Sprungtabelle
; minimum und maximum sind auf 0..255 begrenzt!
.equ minimum = 3
.equ maximum = 7
start_vergleich:
subi
cpi
brsh
ldi
ldi
add
ldi
adc
ijmp
r16,minimum
r16,(maximum-minimum+1)
kein_Treffer
ZL,low(Sprungtabelle)
ZH,high(Sprungtabelle)
ZL,r16
r16,0
ZH,r16
;
;
;
;
Nullpunkt verschieben
Index auf Maximum prfen
Index zu gross -> Fehler
Tabellenzeiger laden, 16 Bit
kein_treffer:
; hier stehen jetzt alle Anweisungen fr den Fall, dass keiner der Vergleiche erfolgre
rjmp
ende_vergleich
Sprungtabelle:
rjmp
zweig_0
rjmp
zweig_1
rjmp
zweig_2
rjmp
zweig_3
rjmp
zweig_4
zweig_0:
; hier stehen jetzt alle Anweisungen fr diesen Zweig
rjmp
ende_vergleich
zweig_1:
; hier stehen jetzt alle Awneisungen fr diesen Zweig
rjmp
ende_vergleich
zweig_2:
; hier stehen jetzt alle Anweisungen fr diesen Zweig
rjmp
ende_vergleich
zweig_3:
; hier stehen jetzt alle Anweisungen fr diesen Zweig
rjmp
ende_vergleich
zweig_4:
; hier stehen jetzt alle Anweisungen fr diesen Zweig
rjmp
ende_vergleich
ende_vergleich:
; hier geht das Programm weiter
rjmp
ende_vergleich
Programmbeschreibung
Wie ist dieses Programm nun zu verstehen? Das Prinzip beruht darauf, da in einer
gleichmssigen Tabelle Sprungbefehle auf einzelne Programmzweige abgelegt werden. Das ist
praktisch genauso wie der AVR Interrupts verarbeitet. ber einen Index (0...N) wird ein
Sprungbefehl ausgewhlt und ausgefhrt.Der entscheidende Befehl dazu ist ijmp.
Zunchst muss der Wertebereich, auf welchen die Variable geprft werden soll (minimum bis
maximum), normiert werden (0 bis (Maximum-Minimum)). Dazu wird einfach das Minimum
subtrahiert.
subi
r16,minimum
; Nullpunkt verschieben
Danach muss geprft werden, ob der maximale Index nicht berschritten wird. Denn ein Sprung
auf nichtexistierende Eintrge oberhalb der Sprungtabelle wre fatal!
cpi
brsh
r16,(maximum-minimum+1)
kein_Treffer
Danach muss der indirekte Sprung vorbereitet werden. Dazu wird die Adresse der
Sprungtabelle in das Z-Register geladen, welches ein 16 Bit Register ist und gleichbedeutend
mit r30 und r31.
ldi
ldi
ZL,low(Sprungtabelle)
ZH,high(Sprungtabelle)
Danach muss der Index addiert werden, dies ist eine 16-Bit Addition.
add
ldi
adc
ZL,r16
r16,0
ZH,r16
maximale Gesamtgre der Zweige wird durch den Befehl rjmp begrenzt (+/-4kB). Das
sollte aber nur in sehr wenigen Fllen ein Problem sein (Man wird kaum einen AVR mit
8 kB FLASH mit einer einzigen Mehrfachverzweigung fllen!)
Vorteile
die einzelnen Zweige haben unabhngig von der Grsse der Sprungtabelle eine
konstante und kurze Durchlaufzeit von 12 Takten.
bersichtlicher Quellcode
Nachteile
[Bearbeiten]Lange
Sprungtabelle
Wenn man doch mal eine GIGA-Mehrfachverzweigung braucht, dann hilft die Version C.
.include "m16def.inc"
; Mehrfachverzweigung Version C
; Clevere Version mit langer Sprungtabelle
; funktioniert nur mit AVRs mit mehr als 8KB FLASH
; minimum und maximum sind auf 0..127 begrenzt!
.equ minimum = 3
.equ maximum = 7
start_vergleich:
subi
cpi
brsh
ldi
ldi
lsl
add
ldi
adc
lpm
lpm
mov
ijmp
r16,minimum
r16,(maximum-minimum+1)
kein_Treffer
ZL,low(Sprungtabelle*2)
ZH,high(Sprungtabelle*2)
r16
zl,r16
r16,0
zh,r16
r16,Z+
ZH,Z
ZL,r16
;
;
;
;
Nullpunkt verschieben
Index auf Maximum prfen
Index zu gross -> Fehler
Tabellenzeiger laden, 16 Bit
kein_treffer:
; hier stehen jetzt alle Anweisungen fr den Fall, dass keiner der Vergleiche erfolgre
jmp
ende_vergleich
Sprungtabelle:
.dw zweig_0
.dw zweig_1
.dw zweig_2
.dw zweig_3
.dw zweig_4
zweig_0:
; hier stehen jetzt alle Anweisungen fr diesen Zweig
jmp
ende_vergleich
zweig_1:
; hier stehen jetzt alle Awneisungen fr diesen Zweig
jmp
ende_vergleich
zweig_2:
; hier stehen jetzt alle Anweisungen fr diesen Zweig
jmp
ende_vergleich
zweig_3:
; hier stehen jetzt alle Anweisungen fr diesen Zweig
jmp
ende_vergleich
zweig_4:
; hier stehen jetzt alle Anweisungen fr diesen Zweig
ende_vergleich:
; hier geht das Programm weiter
jmp
ende_vergleich
Programmbeschreibung
Diese Version ist der Version B sehr hnlich. Der Unterschied besteht darin, da in Version B
die Sprungtabelle mit Sprungbefehlen gefllt ist (rjmp) whrend in Version C die Startadressen
der Funktionen ablegt sind. D.H. man kann nicht in die Sprungtabelle springen, sondern muss
sich mit Hilfe des Index die richtige Adresse aus der Sprungtabelle lesen und
mit ijmp anspringen. Klingt sehr hnlich, ist aber dennoch verschieden.
Die ersten drei Befehle sind identisch, es wird der Index normiert und auf das Maximum geprft.
subi
cpi
brsh
r16,minimum
r16,(maximum-minimum+1)
kein_Treffer
; Nullpunkt verschieben
; Index auf Maximum prfen
; Index zu gross -> Fehler
Die nchsten zwei Befehle laden wieder die Anfangsadresse der Sprungtabelle. Doch halt, hier
wird die Adresse der Sprungtabelle mit zwei multipliziert. Des Rtsels Lsung gibt es weiter
unten.
ldi
ldi
ZL,low(Sprungtabelle*2)
ZH,high(Sprungtabelle*2)
r16
zl,r16
r16,0
zh,r16
Nun zeigt unser Z-Zeiger auf den richtigen Tabelleneintrag. Jetzt mssen zwei Bytes aus dem
FLASH geladen werden. Das geschieht mit Hilfe des lpm-Befehls (Load Program Memory).
Hier wird die erweiterte Version des lpm-Befehls verwendet, wie sie nur auf grsseren AVRs
verfgbar ist. Dabei wird ein Byte in Register r16 geladen und gleichzeitig der Z-Pointer um eins
erhht. Damit zeigt er wunderbar auf das nchste Byte, welches auch geladen werden muss.
lpm
r16,Z+
Der zweite lpm-Befehl ist etwas ungewhnlich, denn er berschreibt einen Teil des Z-Pointers!
In den meisten Programmen wre das ein Schuss ins Knie (Programmierfehler!), da wir aber
den Z-Pointer danach sowieso mit neuen Daten laden ist das OK.
lpm
ZH,Z
Das zuerst gelesene Byte wird in den Z-Pointer kopiert. Nun steht die Startadresse des
gewhlten Zweigs im Z-Pointer.
mov
ZL,r16
Zu guter Letzt wird der indirekte Sprung ausgefhrt und bringt uns direkt in den
Programmzweig.
ijmp
Der Zweig fr einen ungltigen Index folgt direkt nach dem ijmp, weil der Befehl brsh nur
maximal 63 Worte weit springen kann.
Eigenschaften
unbegrenzte Sprungweite
Vorteile
die einzelnen Zweige haben unabhngig von der Grsse der Sprungtabelle eine
konstante und kurze Durchlaufzeit von 18 Takten
bersichtlicher Quellcode
Nachteile
[Bearbeiten]Z-Pointer
leicht verstndlich
Auf den ersten Blick scheint es sonderbar, da Version B die Adresse der Sprungtabelle direkt
ldt, whrend Version C sowohl Anfangsadresse als auch Index mit zwei multipliziert. Warum ist
das so?
Version B verwendet nur den Befehl ijmp. Dieser erwartet im Z-Register eine Adresse zur
Programmausfhrung, eine Wort-Adresse. Da der Programmspeicher des AVR 16 Bit breit ist
(=1 Wort = 2 Bytes), werden nur Worte adressiert, nicht jedoch Bytes! Genauso arbeitet der
Assembler. Jedes Label entspricht einer Wort-Adresse. Damit kann man mit einer 12 BitAdresse 4096 Worte adressieren (=8192 Bytes). Wenn man sich die Befehle der einzelnen
AVRs anschaut wird klar, da alle AVRs mit 8KB und weniger FLASH nur die Befehle rjmp und
rcall besitzen. Denn sie brauchen nicht mehr! (Hinweis: Der Atmega8 besitzt die Befehle ijmp
und icall).
Mit 12 Adressbits, welche direkt in einem Wort im Befehl rjmp bzw. rcall kodiert sind, kann der
gesamte Programmspeicher erreicht werden. Grere AVRs besitzen call und jmp, dort ist die
Adresse als 22 bzw. 16 Bit Zahl kodiert, deshalb brauchen diese Befehle auch 2 Worte
Programmspeicher.
Der Befehl lpm dient zum Laden einzelner Bytes aus dem Programmspeicher. Das ist vor allem
fr Tabellen mit konstanten Werten sehr ntzlich (7-Segmentdekoder, Zeichenstze, Kennlinien,
Parameter Texte, etc.) Doch wie kommt man nun in dem wortweise adressierten
Programmspeicher an einzelne Bytes? Ganz einfach. Der AVR "mogelt" hier und erwartet im ZRegister eine Byte-Adresse. Von dieser Adresse bilden die Bits 15..1 die Wortadresse, welche
zur Adressierung des Programmspeichers verwendet wird. Bit 0 entscheidet dann, ob das hochoder niederwertige Byte in das Zielregister kopiert werden soll (0=niederwertiges Byte;
1=hherwertiges Byte).
Darum muss bei Verwendung des Befehls lpm die Anfangsadresse immer mit zwei multipliziert
werden.
ldi
ldi
ZL,low(Sprungtabelle*2)
ZH,high(Sprungtabelle*2)
In Version C muss zustzlich der Index mit zwei multipliziert werden, weil jeder Tabelleneintrag
(Adresse des Programmzweigs) ein Wort breit ist. Damit wird aus einem Index von 0,1,2,3,4 ein
Offset von 0,2,4,6,8.
AVR-Tutorial: UART
Wie viele andere Controller besitzen die meisten AVRs
einen UART (Universal Asynchronous Receiver and Transmitter). Das ist eine serielle
Schnittstelle, die meistens zur Datenbertragung zwischen Mikrocontroller und PC genutzt wird.
Zur bertragung werden zwei Pins am Controller bentigt: TXD und RXD. ber TXD ("Transmit
Data") werden Daten gesendet, RXD ("Receive Data") dient zum Empfang von Daten.
Inhaltsverzeichnis
[Verbergen]
1 Hardware
2 Software
o
[Bearbeiten]Hardware
Um den UART des Mikrocontrollers zu verwenden, muss der Versuchsaufbau um folgende
Bauteile erweitert werden:
UART/MAX232 Standardbeschaltung
Auf dem Board vom Shop sind diese Bauteile bereits enthalten, man muss nur noch die
Verbindungen zwischen MAX232 (IC2) und AVR herstellen wie im Bild zu sehen.
Der MAX232 ist ein Pegelwandler, der die -12V/+12V Signale an der seriellen
Schnittstelle des PCs zu den 5V/0V des AVRs kompatibel macht.
C1 ist ein kleiner Keramikkondensator, wie er immer wieder zur Entkopplung der
Versorgungsspannungen an digitalen ICs verwendet wird.
Die vier Kondensatoren C2..C5 sind Elektrolytkondensatoren (Elkos). Auf die richtige
Polung achten! Minus ist der Strich auf dem Gehuse. Der exakte Wert ist hier relativ
unkritisch, in der Praxis sollte alles von ca. 1F bis 47F mit einer Spannungsfestigkeit
von 16V und hher funktionieren.
Die Verbindung zwischen PC und Mikrocontroller erfolgt ber ein 9-poliges ModemKabel (also ein Verlngerungskabel, keinNullmodem-Kabel!), das an den seriellen
Port des PCs angeschlossen wird. Bei einem Modem-Kabel sind die Pins 2 und 3 des
einen Kabelendes mit den Pins 2 und 3 des anderen Kabelendes durchverbunden. Bei
einem Nullmodem-Kabel sind die Leitungen gekreuzt, sodass Pin 2 von der einen Seite
mit Pin 3 auf der anderen Seite verbunden ist und umgekehrt.
Als Faustregel kann man annehmen: Befinden sich an den beiden Enden des Kabels
die gleiche Art von Anschlssen (Mnnchen = Stecker; Weibchen = Buchse), dann
bentigt man ein gekreuztes, also ein Nullmodem-Kabel. Am PC-Anschluss selbst
befindet sich ein Stecker, also ein Mnnchen, soda am Kabel auf dieser Seite eine
Buchse (also ein Weibchen) sitzen muss. Da am AVR laut obigem Schaltbild eine
Buchse verbaut wird, muss daher an diesem Ende des Kabels ein Stecker sitzen. Das
Kabel hat daher an einem Ende einen Stecker und am anderen Ende eine Buchse und
ist daher ein normales Modem-Kabel ( = nicht gekreuzt).
Kabelbeschaltungen
[Bearbeiten]Software
[Bearbeiten]UART konfigurieren
Als erstes muss die gewnschte Baudrate im Register UBRR festgelegt werden. Der in dieses
Register zu schreibende Wert errechnet sich nach der folgenden Formel:
Beim AT90S4433 kann man den Wert direkt in das Register UBRR laden, beim ATmega8 gibt
es fr UBRR zwei Register: UBRRL (Low-Byte) und UBRRH (High-Byte). Bei Baudraten ber
etwa 3900 Bit/s (gilt nur bei Verwendung eines Takts von 16 MHz) steht in UBRRH 0, da der
berechnete Wert kleiner als 256 ist und somit in UBRRL alleine passt. Beachtet werden muss,
dass das RegisterUBRRH vor dem Register UBRRL beschrieben werden muss. Der
Schreibzugriff auf UBRRL lst das Neusetzen des internen Taktteilers aus.
WICHTIGER HINWEIS 1
Es empfiehlt sich statt der oben genannten Formel, die Formel der Codebeispiele zu
verwenden:
Beispiel: Bei einem ATMega mit 16MHz und 115200 Baud ist der Wert laut Datenblatt
UBBRL=8. Rechnet man mit der Formel UBRRL=(F_CPU / (UART_BAUDRATE* 16L) - 1)
ergibt sich ein Wert von 7,680555 und im UBRRL Register steht somit eine 7 statt einer 8. Die
Verwendung der Formel aus dem Codebeispiel ergibt 8.180555 und im UBRRL Register steht
somit der richtige Wert - nmlich 8
WICHTIGER HINWEIS 2
Auf Grund permanent wiederkehrender Nachfrage sei hier AUSDRCKLICH darauf
hingewiesen, dass bei Verwendung des UART im asynchronen Modus dringend ein Quarz oder
Ouarzoszillator verwendet werden sollte! Der interne RC-Oszillator der AVRs ist recht ungenau!
Damit kann es in Ausnahmefllen funktionieren, muss es aber nicht! Auch ist der interne
Oszillator temperaturempfindlich. Damit hat man dann den schnen Effekt, dass eine UARTSchaltung die im Winter noch funktionierte, im Sommer den Dienst verweigert.
Auerdem muss bei der Berechnung von UBRR geprft werden, ob mit der verwendeten
Taktfrequenz die gewnschte Baudrate mit einem Fehler von <1% generiert werden kann. Das
Datenblatt bietet hier sowohl die Formel als auch Tabellen unter der berschrift des U(S)ART
an.
; Systemtakt in Hz
; Baudrate
; Berechnungen
.equ UBRR_VAL
= ((F_CPU+BAUD*8)/(BAUD*16)-1)
.equ BAUD_REAL = (F_CPU/(16*(UBRR_VAL+1)))
.equ BAUD_ERROR = ((BAUD_REAL*1000)/BAUD-1000)
; clever runden
; Reale Baudrate
; Fehler in Promille
Um den Sendekanal des UART zu aktivieren, muss das Bit TXEN im UART Control
Register UCSRB auf 1 gesetzt werden.
Danach kann das zu sendende Byte in das Register UDR eingeschrieben werden - vorher muss
jedoch sichergestellt werden, dass das Register leer ist, die vorhergehende bertragung also
schon abgeschlossen wurde. Dazu wird getestet, ob das Bit UDRE ("UART Data Register
Empty") im Register UCSRA auf 1 ist.
Genaueres ber die UART-Register findet man im Datenblatt des Controllers.
An dieser Stelle sei noch folgendes angemerkt: Das UDRE-Bit sagt nichts darber aus, ob der
Controller immer noch damit beschftigt ist, Daten zu senden. Da das Senderegister mehrfach
gepuffert ist, wird UDRE bereits gesetzt, obwohl das letzte Zeichen den AVR noch nicht
komplett verlassen hat. Dies kann insbesondere bei der Verwendung von Sleep-Modes ein
Problem werden, wenn der Controller schlafen gelegt wird, bevor das letzte Zeichen versendet
wurde, da dies gezwungenermassen zu einem Frame-Error beim Empfnger fhren wird. Um
sicher zu gehen, dass der UART nicht mehr beschftigt ist, kann das Bit TXC ("UART Transmit
complete") getestet werden. Dieses wird jedoch wirklich erst nach dem Senden eines Zeichens
gesetzt, beinhaltet also auch nach dem Systemstart eine 0, obwohl der Controller nichts sendet.
Der ATmega8 bietet noch viele weitere Optionen zur Konfiguration des UARTs, aber fr die
Datenbertragung zum PC sind im Normalfall keine anderen Einstellungen notwendig.
[Bearbeiten]Senden von Zeichen
Das Beispielprogramm bertrgt die Zeichenkette "Test!" in einer Endlosschleife an den PC.
Hinweis
Wenn man das nachfolgende Programm laufen lsst und Hyperterminal startet, scheint es
problemlos zu funktionieren. Wenn man aber das RS232 Kabel zwischenzeitlich abzieht und
wieder ansteckt wird es oft passieren, dass nur noch wirre Zeichen auf dem PC erscheinen.
Das liegt daran, dass der PC aus einem ununterbrochen Zeichenstrom nicht den Anfang eines
Zeichens erkennen kann. Darum muss in solchen Fllen periodisch eine kleine Pause von der
Lnge mindestens eines Zeichens eingelegt werden, damit der PC sich wieder synchronisieren
kann.
; Systemtakt in Hz
; Baudrate
; Berechnungen
.equ UBRR_VAL
; clever runden
= ((F_CPU+BAUD*8)/(BAUD*16)-1)
; Reale Baudrate
; Fehler in Promille
temp, HIGH(RAMEND)
SPH, temp
temp, LOW(RAMEND)
SPL, temp
; Baudrate einstellen
ldi
out
ldi
out
temp, HIGH(UBRR_VAL)
UBRRH, temp
temp, LOW(UBRR_VAL)
UBRRL, temp
; Frame-Format: 8 Bit
ldi
out
temp, (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0)
UCSRC, temp
sbi
UCSRB,TXEN
loop:
ldi
rcall
ldi
rcall
ldi
rcall
ldi
rcall
ldi
rcall
ldi
rcall
ldi
rcall
rcall
rjmp
zeichen,
serout
zeichen,
serout
zeichen,
serout
zeichen,
serout
zeichen,
serout
zeichen,
serout
zeichen,
serout
sync
loop
serout:
sbis
UCSRA,UDRE
rjmp
out
ret
; TX aktivieren
'T'
'e'
's'
't'
; Unterprogramm aufrufen
; Unterprogramm aufrufen
; ...
'!'
10
13
serout
UDR, zeichen
r16,0
r17,0
dec
brne
dec
brne
ret
r17
sync_loop
r16
sync_1
Der Befehl rcall serout ruft ein kleines Unterprogramm auf, das zuerst wartet bis das
Datenregister UDR von der vorhergehenden bertragung frei ist, und anschlieend das in
zeichen (=r17) gespeicherte Byte an UDR ausgibt.
Bevor serout aufgerufen wird, wird zeichen jedesmal mit dem ASCII-Code des zu
bertragenden Zeichens geladen (so wie in Teil 4 bei der LCD-Ansteuerung). Der Assembler
wandelt Zeichen in einfachen Anfhrungsstrichen automatisch in den entsprechenden ASCIIWert um. Nach dem Wort "Test!" werden noch die Codes 10 (New Line) und 13 (Carriage
Return) gesendet, um dem Terminalprogramm mitzuteilen, dass eine neue Zeile beginnt.
Eine bersicht aller ASCII-Codes gibt es auf www.asciitable.com.
Die Berechnung der Baudrate wird brigens nicht im Controller whrend der
Programmausfhrung durchgefhrt, sondern schon beim Assemblieren, wie man beim
Betrachten der Listingdatei feststellen kann.
Zum Empfang muss auf dem PC ein Terminal-Programm wie z. B. HyperTerminal gestartet
werden. Der folgende Screenshot zeigt, welche Einstellungen im Programm vorgenommen
werden mssen:
Linux-Benutzer knnen das entsprechende Device (z. B. /dev/ttyS0) mit stty konfigurieren und
mit cat die empfangenen Daten anzeigen oder ein Terminalprogramm wie minicom nutzen.
Alternativ kann unter Windows und Linux HTerm genutzt werden. (Freeware)
; Systemtakt in Hz
; Baudrate
; Berechnungen
.equ UBRR_VAL
= ((F_CPU+BAUD*8)/(BAUD*16)-1)
.equ BAUD_REAL = (F_CPU/(16*(UBRR_VAL+1)))
.equ BAUD_ERROR = ((BAUD_REAL*1000)/BAUD-1000)
; clever runden
; Reale Baudrate
; Fehler in Promille
temp, HIGH(RAMEND)
SPH, temp
temp, LOW(RAMEND)
SPL, temp
; Baudrate einstellen
ldi
out
ldi
out
temp, HIGH(UBRR_VAL)
UBRRH, temp
temp, LOW(UBRR_VAL)
UBRRL, temp
; Frame-Format: 8 Bit
ldi
out
temp, (1<<URSEL)|(3<<UCSZ0)
UCSRC, temp
sbi
UCSRB,TXEN
; TX aktivieren
zl,low(my_string*2);
zh,high(my_string*2);
serout_string
loop
; Z Pointer laden
loop:
ldi
ldi
rcall
rjmp
.db "Test!",10,13,0
.include "m8def.inc"
.def temp = R16
.equ F_CPU = 4000000
.equ BAUD = 9600
; Systemtakt in Hz
; Baudrate
; Berechnungen
.equ UBRR_VAL
= ((F_CPU+BAUD*8)/(BAUD*16)-1)
.equ BAUD_REAL = (F_CPU/(16*(UBRR_VAL+1)))
.equ BAUD_ERROR = ((BAUD_REAL*1000)/BAUD-1000)
; clever runden
; Reale Baudrate
; Fehler in Promille
; Stackpointer initialisieren
ldi
out
ldi
out
temp, HIGH(RAMEND)
SPH, temp
temp, LOW(RAMEND)
SPL, temp
; Port D = Ausgang
ldi
out
temp, 0xFF
DDRD, temp
; Baudrate einstellen
ldi
out
ldi
out
temp, HIGH(UBRR_VAL)
UBRRH, temp
temp, LOW(UBRR_VAL)
UBRRL, temp
; Frame-Format: 8 Bit
ldi
out
temp, (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0)
UCSRC, temp
sbi
UCSRB, RXEN
receive_loop:
sbis
UCSRA, RXC
rjmp
receive_loop
in
temp, UDR
out
PORTD, temp
rjmp
receive_loop
; RX (Empfang) aktivieren
; warten bis ein Byte angekommen ist
; empfangenes Byte nach temp kopieren
; und an Port D ausgeben.
; zurck zum Hauptprogramm
.include "m8def.inc"
.def temp = R16
.equ F_CPU = 4000000
.equ BAUD = 9600
; Systemtakt in Hz
; Baudrate
; Berechnungen
.equ UBRR_VAL
= ((F_CPU+BAUD*8)/(BAUD*16)-1)
.equ BAUD_REAL = (F_CPU/(16*(UBRR_VAL+1)))
.equ BAUD_ERROR = ((BAUD_REAL*1000)/BAUD-1000)
; clever runden
; Reale Baudrate
; Fehler in Promille
.org URXCaddr
rjmp int_rxc
; Interruptvektor fr UART-Empfang
; Hauptprogramm
main:
; Stackpointer initialisieren
ldi
out
ldi
out
temp, HIGH(RAMEND)
SPH, temp
temp, LOW(RAMEND)
SPL, temp
; Port D = Ausgang
ldi
out
temp, 0xFF
DDRD, temp
; Baudrate einstellen
ldi
out
ldi
out
temp, HIGH(UBRR_VAL)
UBRRH, temp
temp, LOW(UBRR_VAL)
UBRRL, temp
; Frame-Format: 8 Bit
ldi
out
temp, (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0)
UCSRC, temp
sbi
sbi
UCSRB, RXCIE
UCSRB, RXEN
sei
loop:
rjmp loop
; Endlosschleife
; Interruptroutine: wird ausgefhrt sobald ein Byte ber das UART empfangen wurde
int_rxc:
push
in
out
pop
reti
temp
temp, UDR
PORTD, temp
temp
;
;
;
;
;
;
Diese Methode hat den groen Vorteil, dass das Hauptprogramm (hier nur eine leere
Endlosschleife) andere Dinge erledigen kann, whrend der Controller Daten empfngt. Auf
diese Weise kann man mehrere Aktionen quasi gleichzeitig ausfhren, da das Hauptprogramm
nur kurz unterbrochen wird, um die empfangenen Daten zu verarbeiten.
Probleme knnen allerdings auftreten, wenn in der Interruptroutine die gleichen Register
verwendet werden wie im Hauptprogramm, da dieses ja an beliebigen Stellen durch den
Interrupt unterbrochen werden kann. Damit sich aus der Sicht der Hauptschleife durch den
Interruptaufruf nichts ndert, mssen alle in der Interruptroutine genderten Register am Anfang
der Routine gesichert und am Ende wiederhergestellt werden. Das gilt vor allem fr das CPUStatusregister (SREG)! Sobald ein einziger Befehl im Interrupt ein einziges Bit im SREG
beeinflusst, muss das SREG gesichert werden. Das ist praktisch fast immer der Fall, nur in dem
ganz einfachen Beispiel oben ist es berflssig, weil die verwendeten Befehle das SREG nicht
beeinflussen. In diesem Zusammenhang wird der Stackwieder interessant. Um die Register zu
sichern, kann man sie mit push oben auf den Stapel legen und am Ende wieder in der
umgekehrten Reihenfolge(!) mit pop vom Stapel herunternehmen.
Im folgenden Beispielprogramm werden die empfangenen Daten nun nicht mehr komplett
angezeigt. Stattdessen kann man durch Eingabe einer 1 oder einer 0 im Terminalprogramm
eine LED (an PB0) an- oder ausschalten. Dazu wird das empfangene Byte in der
Interruptroutine mit den entsprechenden ASCII-Codes der Zeichen 1 und 0
(siehe www.asciitable.com) verglichen.
Fr den Vergleich eines Registers mit einer Konstanten gibt es den Befehl cpi register,
konstante. Das Ergebnis dieses Vergleichs kann man mit den Befehlen breq label (springe zu
label, wenn gleich) und brne label (springe zu label, wenn ungleich) auswerten.
.include "m8def.inc"
.def temp = R16
.equ F_CPU = 4000000
.equ BAUD = 9600
; Systemtakt in Hz
; Baudrate
; Berechnungen
.equ UBRR_VAL
= ((F_CPU+BAUD*8)/(BAUD*16)-1)
.equ BAUD_REAL = (F_CPU/(16*(UBRR_VAL+1)))
.equ BAUD_ERROR = ((BAUD_REAL*1000)/BAUD-1000)
; clever runden
; Reale Baudrate
; Fehler in Promille
temp, HIGH(RAMEND)
SPH, temp
temp, LOW(RAMEND)
SPL, temp
; Port B = Ausgang
ldi
out
temp, 0xFF
DDRB, temp
; Baudrate einstellen
ldi
out
ldi
out
temp, HIGH(UBRR_VAL)
UBRRH, temp
temp, LOW(UBRR_VAL)
UBRRL, temp
; Frame-Format: 8 Bit
ldi
out
temp, (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0)
UCSRC, temp
sbi
sbi
UCSRB, RXCIE
UCSRB, RXEN
sei
loop:
rjmp loop
; Endlosschleife
; Interruptroutine: wird ausgefhrt sobald ein Byte ber das UART empfangen wurde
int_rxc:
push
in
push
in
cpi
brne
cbi
rjmp
int_rxc_1:
cpi
brne
sbi
int_rxc_2:
pop
out
pop
reti
temp
temp, sreg
temp
temp, UDR
temp, '1'
int_rxc_1
PORTB, 0
int_rxc_2
;
;
;
;
;
temp, '0'
int_rxc_2
PORTB, 0
temp
sreg, temp
temp
; SREG wiederherstellen
; temp wiederherstellen
[Bearbeiten]Handshake
Werden Daten schnell ber eine serielle Leitung an ein langsames Gert bertragen, dann kann
es passieren, dass die Situation eintritt, dass das empfangende Gert nicht mehr mitkommt.
Das kann z.B. dadurch passieren, dass das empfangende Gert selbst etwas Zeit fr die
Bearbeitung der Daten bentigt. Man denke z.B. an die Situation, dass an ein Modem Daten
bertragen werden. Das Modem muss diese Daten bearbeiten und unter Umstnden ber eine
langsame Telefonleitung absetzen. bertrgt der AVR seine Daten mit voller Geschwindigkeit
an das Modem, so wird auch dem besten Modem irgendwann der interne Speicher ausgehen,
in dem es die Daten zwischenspeichern kann.
Was bentigt wird, ist also eine Mglichkeit, wie die Gegenstelle dem Sender signalisieren
kann: "Bitte jetzt nichts senden, ich bin beschftigt!". Die einfachste Form eines derartigen
Protokolls, nennt sich Handshake. Es gibt bei RS232 2 Arten, wie dieses Handshake
implementiert werden kann: Software-Handshake und Hardware-Handshake.
[Bearbeiten]Hardware Handshake
Hardware Handshake benutzt die beiden Steuerleitungen RTS - Request to Send und CTS Clear to Send um die Flusskontrolle durchzufhren.
Die etwas seltsam anmutenden Namen haben historische Ursache. Ursprnglich war RS232
dazu gedacht ein Modem (ein sog. Data Carrier Equipment oder DCE) an einen Endpunkt (DTE
oder Data Terminal Equipment) anzuschliessen. Wenn das DTE Daten senden wollte, aktivierte
es die Leitung RTS, es fragte praktisch beim DCE an: "Darf ich senden? (engl. Request
sending)". Wenn das DCE bereit war, dann aktivierte es seinerseits die CTS Leitung und
signalisierte damit "Alles ok. Daten marsch! (engl. Clear to send)". Solange das DCE nicht
bereit war, Daten entgegenzunehmen, musste das DTE warten, bis es vom DCE die Freigabe
zum Senden bekam.
Fr das DTE gilt: RTS ist ein Ausgang, CTS ist ein Eingang.
Fr das DCE gilt: RTS ist ein Eingang, CTS ist ein Ausgang.
Das war die ursprngliche Idee. Heutzutage ist es aber normal, dass 2 DTE miteinander ber
eine RS232 Verbindung gekoppelt werden. Wird in so einem Fall Hardware Handshake benutzt,
so muss jedes DTE seiner Gegenstelle eine korrekte Bedienung der RTS/CTS Leitung
vortuschen.
Der Teil, dass CTS nur dann bedient wird, wenn ber RTS die Anfrage nach der Sendefreigabe
erfolgt entfllt dabei. Jeder Gesprchspartner berprft ganz einfach vor dem Sendevorgang
den Zustand der CTS Leitung der Gegenstelle, whrend der eigene RTS Ausgang zur
Signalisierung der Empfangsbereitschaft fr die Gegenstelle dient. Dies ist auch der Grund
warum bei einem Null-Modem-Kabel nicht nur die RX/TX Leitungen, sondern auch die
RTS/CTS Leitungen gekreuzt werden mssen.
Mchte man obige Schaltung um eine Hardware-Flusskontrolle erweitern, so bietet es sich an,
die beiden noch freien Kanle des MAX232 dafr zu verwenden. Die Schaltung sieht dann wie
folgt aus:
Am Mega8 stehen dann die Signale RTS bzw. CTS an den Pins PD4 bzw. PD5 zur Verfgung.
An PD5 kann abgefragt werden, ob die Gegenstelle zum Empfang von Daten bereit ist,
whrend der Mega8 ber PD4 signalisieren kann, dass er im Moment keine Daten ber die
serielle Schnittstelle empfangen kann.
[Bearbeiten]Software Handshake
Software Handshake benutzt die Datenleitung selbst, um die Flukontrolle von
Sender/Empfnger zu erreichen. Dazu wurden im ASCII Code 2 spezielle 'Zeichen' vorgesehen:
XON (mit dem Code 0x11) und XOFF (mit dem Code 0x13).
Bemerkt ein Empfnger, dass er in Krze keine Daten mehr vom Sender aufnehmen kann,
dann sendet er seinerseits ein XOFF, woraufhin der Sender das Senden der Daten unterbricht.
Ist der Empfnger wieder aufnahmebereit, so gibt er die bertragung durch das Senden eines
XON wieder frei.
Der Nachteil des Software-Handshaking besteht also in mehreren Punkten
zum einen knnen nicht mehr alle Datenbytes bertragen werden, da ja die Bytes 0x11
und 0x13 eine spezielle Bedeutung haben. Mchte man Bytes binr bertragen, muss
man daher spezielle Vorkehrungen treffen, damit diese Datenbytes nicht durch das
Software-Handshaking fehlinterpretiert werden.
zum anderen muss jeder Sender whrend er sendet auch gleichzeitig einen mglichen
Empfang von Daten berwachen. Die Gegenstelle knnte ja mittels XOFF eine
kurzfristige Unterbrechung der Sendung anfordern. Auch muss jeder Sender exakt
darber Buch fhren, ob die Leitung zur Zeit im Status XOFF liegt und ob daher
bertragungen berhaupt mglich sind.
das Senden von XOFF muss rechtzeitig erfolgen. Denn meistens bentigt die
Gegenstelle etwas Zeit um das Senden einzustellen. Es kann durchaus sein, dass nach
einem XOFF noch ein paar Zeichen von der Gegenstelle eintreffen
es besteht die Gefahr eines Deadlocks, indem sich beide Seiten gegenseitig mit einem
XOFF blockieren, aus dem sie nicht mehr herauskommen.
[Bearbeiten]Weblinks
AVR-Tutorial: Speicher
Inhaltsverzeichnis
[Verbergen]
1 Speichertypen
o
1.1 Flash-ROM
1.2 EEPROM
1.3 RAM
2 Anwendung
2.1 Flash-ROM
2.2 EEPROM
2.2.1 Lesen
2.2.2 Schreiben
2.3 SRAM
3 Siehe auch
[Bearbeiten]Speichertypen
Die AVR-Mikrocontroller besitzen 3 verschiedene Arten von Speicher:
has
Flash
EEPROM
RAM
Schreibzyklen
>10.000
>100.000
unbegrenzt
Lesezyklen
unbegrenzt
unbegrenzt
unbegrenzt
flchtig
nein
nein
ja
2 KB
128 Byte
128 Byte
8 KB
512 Byte
1 KB
32 KB
1 KB
2 KB
[Bearbeiten]Flash-ROM
Das Flash-ROM der AVRs dient als Programmspeicher. ber den Programmieradapter werden
die kompilierten Programme vom PC an den Controller bertragen und im Flash-ROM abgelegt.
Bei der Programmausfhrung wird das ROM Wort fr Wort ausgelesen und ausgefhrt. Es lsst
sich aber auch zur Speicherung von Daten nutzen (z. B. Texte fr ein LCD). Vom laufenden
Programm aus kann man das ROM normalerweise nur lesen, nicht beschreiben. Es kann
beliebig oft ausgelesen werden, aber theoretisch nur ~10.000 mal beschrieben werden.
[Bearbeiten]EEPROM
Das EEPROM ist wie das Flash ein nichtflchtiger Speicher, die Daten bleiben also auch nach
dem Ausschalten der Betriebsspannung erhalten. Es kann beliebig oft gelesen und mindestens
100.000 mal beschrieben werden. Bei den AVRs kann man es z. B. als Speicher fr Messwerte
oder Einstellungen benutzen.
[Bearbeiten]RAM
Das RAM ist ein flchtiger Speicher, d.h. die Daten gehen nach dem Ausschalten verloren. Es
kann beliebig oft gelesen und beschrieben werden, weshalb es sich zur Speicherung von
Variablen eignet fr die die Register R0-R31 nicht ausreichen. Daneben dient es als Speicherort
fr den Stack, auf dem z. B. bei Unterprogrammaufrufen (rcall) die Rcksprungadresse
gespeichert wird (sieheAVR-Tutorial: SRAM).
[Bearbeiten]Anwendung
[Bearbeiten]Flash-ROM
Die erste und wichtigste Anwendung des Flash-ROMs kennen wir bereits: Das Speichern von
Programmen, die wir nach dem Assemblieren dort hineingeladen haben. Nun sollen aber auch
vom laufenden Programm aus Daten ausgelesen werden.
Um die Daten wieder auszulesen, muss man die Adresse, auf die zugegriffen werden soll, in
den Z-Pointer laden. Der Z-Pointer besteht aus den Registern R30 (Low-Byte) und R31 (HighByte), daher kann man das Laden einer Konstante wie gewohnt mit dem Befehl ldi durchfhren.
Statt R30 und R31 kann man brigens einfach ZL und ZH schreiben, da diese Synonyme
bereits in der include-Datei m8def.inc definiert sind.
Wenn die richtige Adresse erstmal im Z-Pointer steht, geht das eigentliche Laden der Daten
ganz einfach mit dem Befehl lpm. Dieser Befehl, der im Gegensatz zu out, ldi usw. keine
Operanden hat, veranlasst das Laden des durch den Z-Pointer addressierte Byte aus dem
Programmspeicher in das Register R0, von wo aus man es weiterverarbeiten kann.
Jetzt muss man nur noch wissen, wie man dem Assembler berhaupt beibringt, dass er die von
uns festgelegten Daten im ROM platzieren soll und wie man dann an die Adresse kommt an der
sich diese Daten befinden. Um den Programmspeicher mit Daten zu fllen, gibt es die
Direktiven .db und .dw. In der Regel bentigt man nur .db, was folgendermaen funktioniert:
daten:
.db 12, 20, 255, 0xFF, 0b10010000
Direktiven wie .db sind Anweisungen an den Assembler, keine Prozessorbefehle. Von denen
kann man sie durch den vorangestellten Punkt unterscheiden. In diesem Fall sagen wir dem
Assembler, dass er die angegebenen Bytes nacheinander im Speicher platzieren soll; wenn
man die Zeile also assembliert, erhlt man eine Hex-Datei, die nur diese Daten enthlt.
Aber was soll das daten: am Anfang der Zeile? Bis jetzt haben wir Labels nur als
Sprungmarken verwendet, um den Befehlen rcall und rjmp zu sagen, an welche Stelle im
Programm gesprungen werden soll. Wrden wir in diesem Fall rjmp daten im Programm
stehen haben, dann wrde die Programmausfhrung zur Stelle daten: springen, und versuchen
die sinnlosen Daten als Befehle zu interpretieren - was mit Sicherheit dazu fhrt, dass der
Controller Amok luft.
Statt nach daten: zu springen, sollten wir die Adresse besser in den Z-Pointer laden. Da der ZPointer aus zwei Bytes besteht, brauchen wir dazu zweimal den Befehl ldi:
ldi ZL, LOW(daten*2)
ldi ZH, HIGH(daten*2)
Wie man sieht, ist das Ganze sehr einfach: Man kann die Labels im Assembler direkt wie
Konstanten verwenden. ber die Multiplikation der Adresse mit zwei sollte man sich erst mal
keine Gedanken machen: "Das ist einfach so." Wer es genauer wissen will schaut hier nach.
Um zu zeigen wie das alles konkret funktioniert, ist das folgende Beispiel ntzlich:
.include "m8def.inc"
ldi
out
R16, 0xFF
DDRB, R16
; Port B: Ausgang
ldi
ldi
ZL, LOW(daten*2)
ZH, HIGH(daten*2)
PORTB, R0
lpm
out
ende:
rjmp ende
; Endlosschleife
daten:
.db 0b10101010
Wenn man dieses Programm assembliert und in den Controller bertrgt, dann kann man auf
den an Port B angeschlossenen LEDs das mit .db 0b10101010 im Programmspeicher
abgelegte Bitmuster sehen.
Eine hufige Anwendung von lpm ist das Auslesen von Zeichenketten ("Strings") aus dem
Flash-ROM und die Ausgabe an den seriellen Port oder ein LCD. Das folgende Programm gibt
in einer Endlosschleife den Text "AVR-Assembler ist ganz einfach", gefolgt von einem
Zeilenumbruch, an den UART aus.
.include "m8def.inc"
.def temp = r16
.def temp1 = r17
.equ CLOCK = 4000000
.equ BAUD = 9600
.equ UBRRVAL = CLOCK/(BAUD*16)-1
r16,
SPL,
r16,
SPH,
low(RAMEND)
r16
high(RAMEND)
r16
; Stackpointer initialisieren
ldi
out
ldi
out
temp, LOW(UBRRVAL)
UBRRL, temp
temp, HIGH(UBRRVAL)
UBRRH, temp
ldi
out
sbi
loop:
ldi
ldi
rcall
rcall
rjmp
ZL, LOW(text*2)
ZH, HIGH(text*2)
print
wait
loop
; Baudrate einstellen
;
;
;
;
;
; kleine Pause
wait:
ldi
temp,0
wait_1:
ldi
temp1,0
wait_2:
dec
temp1
brne
wait_2
dec
temp
brne
wait_1
ret
; print: sendet die durch den Z-Pointer adressierte Zeichenkette
print:
lpm
tst
breq
mov
rcall
adiw
R0
print_end
r16, r0
sendbyte
ZL, 1
;
;
;
;
;
;
rjmp
print_end:
ret
UCSRA, UDRE
sendbyte
UDR, r16
r16, Z+
Dieser Befehl liest ein Byte aus dem Flash und speichert es in einem beliebigen Register, hier
r16. Danach wird der Zeiger Z um eins erhht. Fr die neuen Controller, wie ATmegas kann das
Codebeispiel also so abgendert werden:
; print: sendet die durch den Z-Pointer adressierte Zeichenkette
print:
lpm
r16, Z+
; Erstes Byte des Strings nach r16 lesen
tst
r16
; r16 auf 0 testen
breq
print_end
; wenn 0, dann zu print_end
rcall
sendbyte
; UART-Sendefunktion aufrufen
rjmp
print
; wieder zum Anfang springen
print_end:
ret
Wenn man bei .db einen Text in doppelten Anfhrungszeichen angibt, werden die Zeichen
automatisch in die entsprechenden ASCII-Codes umgerechnet:
.db
"Test", 0
; ist quivalent zu
.db
84, 101, 115, 116, 0
Damit das Programm das Ende der Zeichenkette erkennen kann, wird eine 0 an den Text
angehngt.
Das ist doch schonmal sehr viel praktischer, als jeden Buchstaben einzeln in ein Register zu
laden und abzuschicken. Und wenn man statt sendbyte einfach die Routine lcd_data aus dem
4. Teil des Tutorials aufruft, dann funktioniert das gleiche sogar mit dem LCD!
[Bearbeiten]Neue
Assemblerbefehle
lpm
lpm
lpm
[Register], Z+
tst
[Register]
breq
[Label]
adiw
[Register], [Konstante] ;
;
;
;
;
;
[Bearbeiten]EEPROM
Den Flash Speicher kann man also benutzen, um dort Daten abzulegen, die sich whrend eines
Programmlaufs nicht verndern. Irgendwelche Tabellen oder konstante Texte. Aber des fteren
mchte man auch die Mglichkeit haben, Daten zu speichern und wieder zu lesen, die sich
whrend des Programmlaufs ndern. Ganz besonders mchte man eine Speicherflche zur
Verfgung haben, die ihren Inhalt auch dann behlt, wenn dem C die Versorgungsspannung
abgedreht wird. Man denke z.B. an irgendwelche Konfigurationsdaten oder an Informationen,
wie weit der C in der Bearbeitung von Daten vorangekommen ist oder irgendwelche
Statistikdaten, die auch nach einem Stromausfall noch verfgbar sein sollen. In solchen Fllen
kommt das EEPROM zum Einsatz
[Bearbeiten]Lesen
Als erstes muss geprft werden, ob ein vorheriger Schreibzugriff schon abgeschlossen ist.
Danach wird die EEPROM-Adresse von der gelesen werden soll in das IORegisterpaar EEARH/EEARL(EEPROM Address Register) geladen. Da der ATmega8 mehr als
256 Byte EEPROM hat, passt die Adresse nicht in ein einziges 8-Bit-Register, sondern muss in
zwei Register aufgeteilt werden: EEARH bekommt das obere Byte der Adresse, EEARL das
untere Byte. Dann lst man den Lesevorgang durch das Setzen des Bits EERE (EEPROM
Read Enable) im IO-Register EECR (EEPROM Control Register) aus. Das gelesene Byte kann
sofort aus dem IO-Register EEDR (EEPROM Data Register) in ein normales CPU-Register
kopiert und dort weiterverarbeitet werden.
Wie auch das Flash-ROM kann man das EEPROM ber den ISP-Programmer programmieren.
Die Daten, die im EEPROM abgelegt werden sollen, werden wie gewohnt mit .db angegeben;
allerdings muss man dem Assembler natrlich sagen, dass es sich hier um Daten fr das
EEPROM handelt. Das macht man durch die Direktive .eseg, woran der Assembler erkennt,
dass alle nun folgenden Daten fr das EEPROM bestimmt sind.
Damit man die Bytes nicht von Hand abzhlen muss um die Adresse herauszufinden, kann man
auch im EEPROM-Segment wieder Labels einsetzen und diese im Assemblerprogramm wie
Konstanten verwenden.
.include "m8def.inc"
; hier geht die Programmsektion los
.cseg
ldi
r16, low(RAMEND)
; Stackpointer initialisieren
out
ldi
out
SPL, r16
r16, high(RAMEND)
SPH, r16
ldi
out
r16, 0xFF
DDRB, r16
ldi
ldi
rcall
out
ZL,low(daten)
ZH,high(daten)
EEPROM_read
PORTB, r16
; Port B Ausgang
; Z-Zeiger laden
; Daten aus EEPROM lesen
loop:
rjmp loop
EEPROM_read:
sbic
EECR,EEWE
rjmp
EEPROM_read
out
out
sbi
in
ret
EEARH, ZH
EEARL, ZL
EECR, EERE
r16, EEDR
; Baudrate
; Baudratenteiler
; Stackpointer initialisieren
ldi
out
ldi
out
temp, LOW(UBRRVAL)
UBRRL, temp
temp, HIGH(UBRRVAL)
UBRRH, temp
ldi
out
sbi
ldi
ldi
rcall
ZL, low(text1)
ZH, high(text1)
EEPROM_print
ldi
ldi
rcall
ZL, low(text2)
ZH, high(text2)
EEPROM_print
loop
; Endlosschleife
loop:
rjmp
; Baudrate einstellen
EEPROM_print
out
out
EEARH, ZH
EEARL, ZL
sbi
EECR, EERE
in
temp, EEDR
tst
temp
breq
eep_print_end
rcall
sendbyte
adiw
ZL,1
rjmp
EEPROM_print
eep_print_end:
ret
; Adresse laden
;
;
;
;
;
;
;
Lesevorgang aktivieren
Daten in CPU Register kopieren
auf 0 testen (=Stringende)
falls 0, Funktion beenden
ansonsten Byte senden...
Adresse um 1 erhhen...
und zum Anfang der Funktion
UCSRA, UDRE
sendbyte
UDR, temp
[Bearbeiten]Schreiben
Als erstes muss geprft werden, ob ein vorheriger Schreibzugriff schon abgeschlossen ist.
Danach wird die EEPROM-Adresse, auf die geschrieben wird, in das IORegister EEAR (EEPROM AddressRegister) geladen. Dann schreibt man die Daten, welche
man auf der im Adressregister abgespeicherten Position ablegen will ins
Register EEDR (EEPROM Data Register). Als nchstes setzt man das EEMWE Bit im
EEPROM-Kontrollregister EECR (EEPROM Control Register) um den Schreibvorgang
vorzubereiten. Nun wird es zeitkritisch - es darf nun keinesfalls ein Interrupt dazwischenfahren denn man muss innerhalb von 4 Taktzyklen das EEWE Bit setzen um den Schreibvorgang
auszulsen. Um das unter allen Bedingungen sicherzustellen werden die Interrupts kurz
gesperrt. Danach startet der Schreibvorgang und luft automatisch ab. Wenn er beendet ist,
wird von der Hardware das EEWE Bit im Register EECR wieder gelscht.
In diesem Beispiel werden Zeichen per UART und Interrupt empfangen und nacheinander im
EEPROM gespeichert. Per Terminalprogramm kann man nun bis zu 512 Zeichen in den
EEPROM schreiben. Per Programmieradapter kann man denn EEPROM wieder auslesen und
seine gespeicherten Daten anschauen.
.include "m8def.inc"
.def temp
= r16
.def sreg_save = r17
.equ CLOCK = 4000000
.equ BAUD = 9600
.equ UBRRVAL = CLOCK/(BAUD*16)-1
; hier geht das Programmsegment los
.CSEG
.org 0x00
rjmp
main
.org URXCaddr
rjmp
int_rxc
; Hauptprogramm
main:
ldi
temp, LOW(RAMEND)
out
SPL, temp
ldi
temp, HIGH(RAMEND)
out
SPH, temp
; Stackpointer initialisieren
ldi
out
ldi
out
temp, LOW(UBRRVAL)
UBRRL, temp
temp, HIGH(UBRRVAL)
UBRRH, temp
; Baudrate einstellen
ldi
out
sbi
sbi
UCSRB, RXCIE
UCSRB, RXEN
ldi
ZL,low(daten)
ldi
ZH,high(daten)
sei
loop:
rjmp
loop
temp
temp,sreg
temp
temp, UDR
EEPROM_write
ZL,1
ZL,low(EEPROMEND+1)
temp,high(EEPROMEND+1)
ZH,temp
int_rxc_1
ZL,low(Daten)
ZH,high(Daten)
temp
sreg,temp
temp
;
;
;
;
;
; temp wiederherstellen
EEARH, ZH
EEARL, ZL
EEDR,temp
sreg_save,sreg
sbi
sbi
out
ret
EECR,EEMWE
EECR,EEWE
sreg, sreg_save
Adresse schreiben
Daten schreiben
SREG sichern
Interrupts sperren, die nchsten
zwei Befehle drfen NICHT
unterbrochen werden
Schreiben vorbereiten
Und los !
SREG wieder herstellen
[Bearbeiten]SRAM
Die Verwendung des SRAM wird in einem anderen Kapitel erklrt: AVR-Tutorial: SRAM
[Bearbeiten]Siehe
Adressierung
auch
Adressierung
Mikrocontroller und -prozessoren bieten in der Regel mehrere Mglichkeiten an, um auf Daten
zuzugreifen. An dieser Stelle sollen die grundlegenden Adressierungsarten der AVR-Controller
mit internem SRAM behandelt werden.
Inhaltsverzeichnis
[Verbergen]
1 Immediate-Werte
2 Direkte Adressierung
3 Indirekte Adressierung
o
3.1 Postinkrement
3.2 Predekrement
[Bearbeiten]Immediate-Werte
Eigentlich keine Adressierungsart, aber dennoch sehr wichtig, ist die Mglichkeit direkt
konstante Werte in ein Register zu schreiben. Dabei ist schon zur Entwicklungszeit bekannt,
welcher Wert in welches Register geladen werden soll.
ldi
r16, 0xA0
ldi steht hierbei fr load immediate. Bei AVR-Mikrocontrollern ist das direkte Laden von Werten
nur mit den Registern r16 bis r31 mglich.
[Bearbeiten]Direkte
Adressierung
Um auf Daten im Speicher zuzugreifen, muss man selbstverstndlich wissen, wo sich diese
Daten befinden. Will man z. B. den Inhalt eines Registers in eine Speicherzelle schreiben, so
muss das Mikroprogramm die Adresse der gewnschten Speicherzelle kennen. Eine einfache
Mglichkeit der Adressierung ist es, dem Befehl die Adresse direkt mitzuteilen.
.dseg
variable:
.cseg
ldi
sts
.byte 1
r16, 25
variable, r16
;
;
;
;
;
;
;
;
Die Adresse der Speicherzelle wird also schon zur Entwicklungszeit im Assembler-Befehl
eingetragen, was nach sich zieht, dass so ein Befehl nur auf Speicherzellen zugreifen kann,
deren Adressen schon im Vorhinein bekannt sind. Da variable in obigem Beispiel eine Adresse
und somit nur eine Zahl darstellt, kann man zur Entwicklungszeit auch Konstanten addieren:
.dseg
variable2: .byte 2
.cseg
ldi
sts
inc
sts
r16, 17
variable2, r16
r16
variable2+1, r16
;
;
;
;
Nun steht in diesem Beispiel im ersten Byte die Zahl 17 und im zweiten Byte die Zahl 18. Dabei
muss man beachten, dass die Addition im sts-Befehl bereits whrend der Assemblierung und
nicht vom Mikrocontroller durchgefhrt wird. Das ist der Fall, weil die Adresse der reservierten
Speicherzelle schon zu dieser Zeit berechnet worden ist. Somit ist natrlich auch die Adresse +
1 bekannt.
[Bearbeiten]Indirekte
Adressierung
Wenn wir nur die direkte Adressierung zur Verfgung haben, stoen wir schnell an Grenzen.
Betrachten wir folgendes Beispiel:
Wir sollen Code schreiben, welcher eine variable Anzahl an Zahlen addieren soll. Die Zahlen
stehen bereits hintereinander im Speicher, beginnend mit der Adresse zahlen_start, und im
Register r16 steht, wie viele Zahlen es sind. Man merkt leicht, dass dies mit direkter
Adressierung nur schwer mglich ist, denn es ist zur Entwicklungszeit noch nicht bekannt, wie
viele Zahlen es sind.
Wir lsen diese Aufgabe, indem wir eine Schleife programmieren, die die Zahlen liest und
aufaddiert, und das ganze so oft, wie im Register r16 steht. Da wir hier von einer Schleife reden,
mssen wir bei jedem Lesen aus dem Speicher mit demselben Befehl auf eine andere
Speicherzelle zugreifen. Wir brauchen also die Mglichkeit die Adresse dynamisch im
Programmablauf zu ndern. Dieses bietet uns die indirekte Adressierung, bei der die Adresse
der gewnschten Speicherstelle in einem Register steht.
Bei AVR-Mikrocontrollern gibt es dafr drei 16 Bit breite Register, die jeweils aus zwei 8-BitRegistern bestehen. Dies rhrt daher, dass ein 8-Bit-Register nur maximal 256 verschiedene
Speicherzellen adressieren knnte, was fr Mikrocontroller mit mehr Speicher nicht ausreicht.
Die Register (r26, r27) und (r28, r29) und (r30, r31) bilden die besagten drei 16 Bit breiten
Register zur indirekten Adressierung. Da diese Register auf Daten zeigen, nennt man sie
logischerweise Zeigerregister (engl. Pointer). Sie tragen die Namen X, Y und Z, wobei die
einzelnen 8-Bit-Register neben ihren rxx-Namen auch
mit XL, XH, YL, YH, ZL und ZH angesprochen werden knnen. L(low) und H(high) bedeutet
hierbei dass die unteren respektive die oberen 8 Bits der 16-Bit-Adresse gemeint ist.
Register
alternativer Name
r26
XL
16-Bit Zeigerregister
X
r27
XH
r28
YL
Y
r29
YH
r30
ZL
Z
r31
ZH
Wir werden beispielhalber das Z-Register fr unser Problem verwenden. Dazu mssen wir
zunchst die Adresse der ersten Zahl in dieses laden. Da das Z-Register 16 Bit breit ist, mssen
wir ZH undZL in zwei einzelnen ldi Operationen beschreiben. Der AVR-Assembler bietet uns
hier zwei praktische Funktionen: Mit LOW(...) und HIGH(...) bekommt man die unteren
respektive die oberen 8 Bit einer Speicheradresse. Das kommt uns gerade recht, da wir gerade
die unteren/oberen 8 Bit der Adresse in die Register ZL/ZH schreiben wollen.
Dann knnen wir mit dem ld-Befehl die Zahl von der Speicherstelle lesen, auf die das ZRegister verweist. Wir schreiben den Wert in das Register r17. Zum Aufsummieren wollen wir
das Register r18 verwenden, welches ganz zu Anfang mit clr auf 0 gesetzt wird.
.dseg
zahlen_start: .byte 20
.cseg
; Irgendwo vorher werden die Zahlen geschrieben, das interessiert
; erstmal nicht weiter, wie das geschieht. Wir gehen jetzt davon aus,
; dass beginnend bei der Speicheradresse zahlen_start so viele Zahlen
; im Speicher stehen, wie im Register r16 steht.
ldi
ldi
clr
schleife:
ld
ZL, LOW(zahlen_start)
ZH, HIGH(zahlen_start)
r18
r17, Z
adiw
ZH:ZL, 1
add
dec
brne
r18, r17
r16
schleife
;
;
;
;
;
; An dieser Stelle ist die Schleife fertig und in r18 steht das Ergebnis.
Das Programm funktioniert zwar schon, aber eine Sache ist unpraktisch: Das Z-Register muss
jedes mal manuell inkrementiert werden, um im nchsten Schleifendurchlauf die nchste Zahl
zu lesen. Da das sequenzielle Lesen oder Schreiben von Daten aus dem bzw. in das SRAM
sehr oft in Programmen vorkommt, gibt es folgende Mglichkeiten:
[Bearbeiten]Postinkrement
Die beiden Zeilen
ld
adiw
r17, Z
ZH:ZL, 1
r17, Z+
Das spart Ausfhrungszeit und macht den Code krzer. Zu beachten ist, dass die
Inkrementierung erst nach der Ausfhrung des eigentlichen Befehls durchgefhrt wird.
[Bearbeiten]Predekrement
quivalent zum Postinkrement gibt es auch die Mglichkeit des Dekrementierens. Hierbei wird
der Wert jedoch vor der Ausfhrung des Befehls dekrementiert. Das Predekrement eignet sich,
umrckwrts durch linear angeordnete Daten zu gehen.
ld
r17, -Z
AVR-Tutorial: Timer
Timer sind eines der Hauptarbeitspferde in unserem Mikrocontroller. Mit ihrer Hilfe ist es
mglich, in regelmigen Zeitabstnden Aktionen zu veranlassen. Aber Timer knnen noch
mehr!
Timer knnen aber auch vllig selbststndig Signale an einem Ausgabepin erzeugen
...
Aber der Reihe nach und kurz zur Erinnerung: Die Beispiele passen zu einem ATmega8 und
ggf. einer Reihe anderer AVR, knnen bei einigen Typen aber abweichen.
Inhaltsverzeichnis
[Verbergen]
1 Was ist ein Timer?
2 Der Vorteiler (Prescaler)
3 Erste Tests
4 Simulation im AVR-Studio
5 Wie schnell schaltet denn jetzt der Port?
6 Timer 0
6.1 TCCR0
7 Timer 1
7.1 TCCR1B
7.2 TCCR1A
7.3 OCR1A
7.4 OCR1B
7.5 ICR1
7.6 TIMSK
8.2 OCR2
8.3 TIMSK
[Bearbeiten]Was
Ein Timer ist im Grunde nichts anderes als ein bestimmtes Register im Mikrocontroller, das
hardwaregesteuert fortlaufend um 1 erhht (oder erniedrigt) wird (statt um 1 erhhen sagt man
auchinkrementieren, und das Gegenstck, dekrementieren, bedeutet um 1 verringern).
Anstatt also Befehle im Programm vorzusehen, die regelmig ausgefhrt werden und ein
Register inkrementieren, erledigt dies der Mikrocontroller ganz von alleine. Dazu ist es mglich,
den Timer mit dem Systemtakt zu verbinden und so die Genauigkeit des Quarzes auszunutzen,
um ein Register regelmig und vor allen Dingen unabhngig vom restlichen Programmfluss (!)
hochzhlen zu lassen.
Davon alleine htte man aber noch keinen groen Gewinn. Ntzlich wird das Ganze erst dann,
wenn man bei bestimmten Zhlerstnden eine Aktion ausfhren lassen kann. Einer der
'bestimmten Zhlerstnde' ist zum Beispiel der Overflow. Das Zhlregister eines Timers kann
natrlich nicht beliebig lange inkrementiert werden z. B. ist der hchste Zhlerstand, den ein
8-Bit-Timer erreichen kann, 28 1 = 255. Beim nchsten Inkrementierschritt tritt ein berlauf
(engl. Overflow) auf, der den Timerstand wieder zu 0 werden lsst. Und hier liegt der
springende Punkt. Wir knnen uns nmlich an diesen Overflow "anhngen" und den Controller
so konfigurieren, dass beim Auftreten des Timer-Overflows ein Interrupt ausgelst wird.
[Bearbeiten]Der
Vorteiler (Prescaler)
Wenn also der Quarzoszillator mit 4 MHz schwingt, dann wrde auch der Timer 4 Millionen mal
in der Sekunde erhht werden. Da der Timer jedes Mal von 0 bis 255 zhlt, bevor ein Overflow
auftritt, heit das auch, dass in einer Sekunde 4000000 / 256 = 15625 Overflows vorkommen.
Ganz schn schnell! Nur: Oft ist das nicht sinnvoll. Um diese Raten zu verzgern, gibt es den
Vorteiler, oder auf Englisch, Prescaler. Er kann z.B. auf die Werte 1, 8, 64, 256 oder 1024
eingestellt werden, je nach Timer (Bitte Datenblatt konsultieren!). Seine Aufgabe ist es, den
Systemtakt um den angegebenen Faktor zu teilen. Steht der Vorteiler also auf 1024, so wird nur
bei jedem 1024-ten Impuls vom Systemtakt das Timerregister um 1 erhht. Entsprechend
weniger hufig kommen dann natrlich die Overflows. Der Systemtakt sei wieder 4000000.
Dann wird der Timer in 1 Sekunde 4000000 / 1024 = 3906.25 mal erhht. Da der Timer wieder
jedesmal bis 255 zhlen muss bis ein Overflow auftritt, bedeutet dies, dass in 1 Sekunde
3906.25 / 256 = 15.25 Overflows auftreten.
Systemtakt: 4Mhz
Vorteiler
Overflows/Sekunde
1
8
64
256
1024
15625
1953.125
244.1406
61.0351
15.2587
Zeit zwischen
2 Overflows [s]
0.000064
0.000512
0.004096
0.016384
0.065536
berechnen:
[Bearbeiten]Erste
Tests
Ein Programm, das einen Timer Overflow in Aktion zeigt, knnte z. B. so aussehen:
.include "m8def.inc"
.def temp = r16
.def leds = r17
.org 0x0000
rjmp
.org OVF0addr
rjmp
main
; Reset Handler
timer0_overflow
main:
; Stackpointer initialisieren
ldi
temp, HIGH(RAMEND)
out
SPH, temp
ldi
temp, LOW(RAMEND)
out
SPL, temp
ldi
out
temp, 0xFF
DDRB, temp
ldi
leds, 0xFF
ldi
out
temp, (1<<CS00)
TCCR0, temp
ldi
out
temp, (1<<TOIE0)
TIMSK, temp
sei
loop:
rjmp
loop
timer0_overflow:
out
PORTB, leds
com
leds
reti
Tritt nun ein Overflow am Timer auf, so wird ber den Umweg ber die Interrupt Vektor Tabelle
der Programmteil timer0_overflow angesprungen. Dieser gibt einfach nur den Inhalt des
Registers leds am Port B aus. Danach wird das leds Register mit einer com Operation negiert,
so dass aus allen 0 Bits eine 1 wird und umgekehrt. Die Overflow Behandlung ist damit beendet
und mittels reti wird der Interrupt Handler wieder verlassen.
[Bearbeiten]Simulation
im AVR-Studio
Es lohnt sich, den ganzen Vorgang im AVR-Studio simulieren zu lassen. Dazu am besten in der
linken I/O View Ansicht die Eintrge fr PORTB und TIMER_COUNTER_0 ffnen. Wird mittels
F11 durch das Programm gegangen, so sieht man, dass ab dem Moment, ab dem der Vorteiler
auf 1 gesetzt wird, der Timer 0 im TCNT0 Register zu zhlen anfngt. Mit jedem Druck auf F11
erhht sich der Zhlerstand. Irgendwann ist dann die Endlosschleife loop erreicht. Drcken Sie
weiterhin F11 und beobachten sie, wie TCNT0 immer hher zhlt, bis der Overflow erreicht wird.
In dem Moment, in dem der Overflow erreicht wird, wird der Interrupt ausgelst. Mit dem
nchsten F11 landen sie in der Interrupt Vektor Tabelle und von dort geht es weiter zu
timer_0_overflow. Weitere Tastendrcke von F11 erledigen dann die Ausgabe auf den Port B,
das Invertieren des Registers r17 und der Interrupt ist damit behandelt. Nach dem reti macht
der Microcontroller genau an der Stelle weiter, an der er vom Interrupt unterbrochen wurde. Und
der Timer 0 hat in der Zwischenzeit weitergezhlt! Nach exakt weiteren 256 Schritten, vom
Auftreten des ersten Overflows an gerechnet, wird der nchste Overflow ausgelst.
[Bearbeiten]Wie
Eine berechtigte Frage. Dazu mssen wir etwas rechnen. Keine Angst, es ist nicht schwer, und
wer das Prinzip bisher verstanden hat, der sollte keine Schwierigkeiten haben, die Berechnung
nachzuvollziehen.
Der Quarzoszillator schwingt mit 4 MHz. Das heit, in 1 Sekunde werden 4000000 Taktzyklen
generiert. Durch die Wahl des Vorteilers von 1 bedeutet das auch, dass der Timer 4000000 mal
in der Sekunde erhht wird. Von einem Overflow zum nchsten muss der Timer 256
Zhlvorgnge ausfhren. Also werden in 1 Sekunde 4000000 / 256 = 15625 Overflows
generiert. Bei jedem Overflow schalten wir die LEDs jeweils in den anderen Zustand. D.h die
LEDs blinken mit einer Frequenz von 7812.5 Hz. Das ist zuviel als dass wir es noch sehen
knnten.
Was knnen wir also tun, um diese Blinkfrequenz zu verringern? Im Moment ist unsere einzige
Einflussgre der Vorteiler. Wie sieht die Rechnung aus, wenn wir einen Vorteiler von 1024
whlen?
Wiederrum: Der Systemtakt sei 4 Mhz. Durch den Vorteiler von 1024 werden daraus 4000000 /
1024 = 3906.25 Pulse pro Sekunde fr den Timer. Der zhlt wiederum 256 Zustnde von einem
Overflow zum nchsten. 3906.25 / 256 = 15.2587. Und wiederum: Im Overflow werden die
LEDs ja abwechselnd ein und ausgeschaltet, also dividieren wir noch durch 2: 15.2587 / 2 =
7.629. Also knapp 7 Hz. Diese Frequenz msste man schon mit freiem Auge sehen. Die LEDs
werden ziemlich schnell vor sich hin blinken.
Reicht diese Verzgerung noch immer nicht, dann haben wir 2 Mglichkeiten:
Entweder wir benutzen einen anderen Timer. Timer 1 beispielsweise ist ein 16 Bit Timer.
Der Timer zhlt also nicht von 0 bis 255 sondern von 0 bis 65535. Bei entsprechender
Umarbeitung des Programms und einem Vorteiler von 1024 bedeutet das, dass die
LEDs einen Ein/Aus Zyklus in 33 Sekunden absolvieren.
Oder wir schalten die LEDs nicht bei jedem Timer Overflow um. Man knnte zum
Beispiel in einem Register bis 7 zhlen und nur dann, wenn dieses Register 7 erreicht
hat, wird
o
[Bearbeiten]Timer
Overflow Interrupt
[Bearbeiten]TCCR0
[Bearbeiten]TCCR
CS CS CS
02 01 00
[Bearbeiten]CS02/CS00
- Clock Select
CS0
2
CS0
1
CS0
0
Vorteiler: 1
Vorteiler: 8
Vorteiler: 64
Bedeutung
Vorteiler: 256
Vorteiler: 1024
[Bearbeiten]TIMSK
TIMSK
TOI
E0
[Bearbeiten]TOIE0
Ist dieses Bit gesetzt, so wird beim Auftreten eines Overflows am Timer ein Interrupt ausgelst.
Anstatt der Schreibweise
ldi
out
temp, 0b00000001
TIMSK, temp
zu whlen, da hier unmittelbar aus dem Ladekommando hervorgeht, welche Bedeutung das
gesetzte Bit hat. Die vorher inkludierte m8def.inc definiert dazu alles Notwendige.
[Bearbeiten]Timer
Overflow Interrupt
Input Capture
2 Compare Einheiten
[Bearbeiten]TCCR1B
TCCR1B
ICN ICES
C1
1
[Bearbeiten]CS12/CS10
- Clock Select
CS1
2
CS1
1
CS1
0
Vorteiler: 1
Vorteiler: 8
Vorteiler: 64
Vorteiler: 256
Vorteiler: 1024
[Bearbeiten]ICES1
[Bearbeiten]ICNC1
Bedeutung
TCCR1A
[Bearbeiten]OCR1A
[Bearbeiten]OCR1B
[Bearbeiten]ICR1
[Bearbeiten]TIMSK
TIMSK
[Bearbeiten]TICIE1
[Bearbeiten]OCIE1A
[Bearbeiten]OCIE1B
[Bearbeiten]TOIE1
[Bearbeiten]Timer
Overflow Interrupt
[Bearbeiten]TCCR2
TCCR2
[Bearbeiten]CS22/CS20
- Clock Select
CS22
CS21
CS20
Bedeutung
Vorteiler: 1
Vorteiler: 8
Vorteiler: 32
Vorteiler: 64
Vorteiler: 128
Vorteiler: 256
Vorteiler: 1024
[Bearbeiten]OCR2
[Bearbeiten]TIMSK
TIMSK
OCI TOI
E2 E2
[Bearbeiten]OCIE2
[Bearbeiten]TOIE2
[Bearbeiten]Was
Ein paar der Timermodule lassen sich auch als Counter verwenden. Damit kann man z. B. die
Anzahl externer Ereignisse wie Schaltvorgnge eines Inkrementalgebers bestimmen.
Andere bieten die Mglichkeit, ber einen externen Uhrenquarz getaktet zu werden
(Anwendung z. B. eine "Echtzeituhr" oder als "Weckfunktion" aus einem
Standby/Powerdownmodus).
Durch geschickte Umprogrammierung in Echtzeit, lsst sich mit einem Timer
eine PLL aufbauen, die sich fortwhrend auf einen Eingangstakt synchronisiert. Damit wird
vermieden, dass der Controller ein Signal stndig abpollen muss, sondern das Signale wird per
Timer-Interrupt verarbeitet. Massgeblich ist die Messung der aktuellen- und Schtzung der
kommenden Flanke mithilfe eines einstellbaren Taktteiler-verhltnisses.
[Bearbeiten]Weblinks
AVR-Tutorial: Uhr
Eine beliebte bung fr jeden Programmierer ist die Implementierung einer Uhr. Die meisten
Uhren bestehen aus einem Taktgeber und einer Auswerte- und Anzeigevorrichtung. Wir wollen
hier beides mittels eines Programmes in einem Mikrocontroller realisieren. Voraussetzung fr
diese Fallstudie ist das Verstndnis der Kapitel ber
Timer
Inhaltsverzeichnis
[Verbergen]
1 Aufbau und Funktion
2 Das erste Programm
3 Ganggenauigkeit
4 Der CTC-Modus des Timers
[Bearbeiten]Aufbau
und Funktion
Die Aufgabe des Taktgebers, der uns einen mglichst konstanten und genauen Takt liefert,
bernimmt ein Timer. Der Timer ermglicht einen einfachen Zugang zum Takt, den der AVR vom
Quarz abgreift. Wie schon im Einfhrungskapitel ber den Timer beschrieben, wird dazu ein
Timer Overflow Interrupt installiert, und in diesem Interrupt wird die eigentliche Uhr hochgezhlt.
Die Uhr besteht aus vier Registern. Drei davon reprsentieren die Sekunden, Minuten und
Stunden unserer Uhr. Nach jeweils einer Sekunde wird das Sekundenregister um eins erhht.
Sind 60 Sekunden vergangen, wird das Sekundenregister wieder auf Null gesetzt und
gleichzeitig das Minutenregister um eins erhht. Dies ist ein berlauf. Nach 60 Minuten werden
die Minuten wieder auf Null gesetzt und fr diese vergangenen 60 Minuten eine Stunde
aufaddiert. Nach 24 Stunden schliesslich werden die Stunden wieder auf Null gesetzt, ein
ganzer Tag ist vergangen.
Aber wozu das vierte Register?
Der Mikrocontroller wird mit 4 MHz betrieben. Bei einem Teiler von 1024 zhlt der Timer also mit
4000000 / 1024 = 3906,25 Pulsen pro Sekunde. Der Timer muss einmal bis 256 zhlen, bis er
einen berlauf auslst. Es ereignen sich also 3906,25 / 256 = 15,2587 berlufe pro Sekunde.
Die Aufgabe des vierten Registers ist es nun, diese 15 berlufe zu zhlen. Bei Auftreten des
15. ist eine Sekunde vergangen. Dies stimmt jedoch nicht exakt, denn die Division weist ja auch
Nachkommastellen auf, hat einen Rest, der hier im Moment der Einfachheit halber ignoriert
wird. In einem spteren Abschnitt wird darauf noch eingegangen werden.
Im berlauf-Interrupt wird also diese Kette von Zhlvorgngen auf den Sekunden, Minuten und
Stunden durchgefhrt und anschliessend zur Anzeige gebracht. Dazu werden die in einem
vorhergehenden Kapitel entwickelten LCD Funktionen benutzt.
[Bearbeiten]Das
erste Programm
.include "m8def.inc"
.def
.def
.def
.def
temp1
temp2
temp3
flag
=
=
=
=
.def
.def
.def
.def
SubCount
Sekunden
Minuten
Stunden
.org 0x0000
rjmp
.org OVF0addr
rjmp
r16
r17
r18
r19
=
=
=
=
r21
r22
r23
r24
main
; Reset Handler
timer0_overflow
.include "lcd-routines.asm"
main:
ldi
out
ldi
out
temp1, HIGH(RAMEND)
SPH, temp1
temp1, LOW(RAMEND) ; Stackpointer initialisieren
SPL, temp1
rcall
rcall
lcd_init
lcd_clear
ldi
out
ldi
out
temp1, 1<<TOIE0
TIMSK, temp1
clr
clr
clr
clr
clr
Minuten
Sekunden
Stunden
SubCount
Flag
; Teiler 1024
; Merker lschen
sei
loop:
cpi
breq
ldi
flag,0
loop
flag,0
rcall
mov
rcall
ldi
lcd_clear
temp1, Stunden
lcd_number
temp1, ':'
rcall
mov
rcall
ldi
rcall
mov
rcall
lcd_data
temp1, Minuten
lcd_number
temp1, ':'
lcd_data
temp1, Sekunden
lcd_number
rjmp
loop
timer0_overflow:
push
in
push
temp1
temp1,sreg
temp1
; temp 1 sichern
; SREG sichern
inc
cpi
brne
SubCount
SubCount, 15
end_isr
clr
inc
cpi
brne
SubCount
Sekunden
Sekunden, 60
Ausgabe
clr
inc
cpi
brne
;
;
;
;
;
;
berlauf
SubCount rcksetzen
plus 1 Sekunde
sind 60 Sekunden vergangen?
wenn nicht kann die Ausgabe schon
gemacht werden
Sekunden
Minuten
Minuten, 60
Ausgabe
;
;
;
;
;
berlauf
Sekunden wieder auf 0 und dafr
plus 1 Minute
sind 60 Minuten vergangen ?
wenn nicht, -> Ausgabe
clr
inc
cpi
brne
Minuten
Stunden
Stunden, 24
Ausgabe
;
;
;
;
;
berlauf
Minuten zurcksetzen und dafr
plus 1 Stunde
nach 24 Stunden, die Stundenanzeige
wieder zurcksetzen
clr
Stunden
; berlauf
; Stunden rcksetzen
flag,1
temp1
sreg,temp1
temp1
Ausgabe:
ldi
end_isr:
pop
out
pop
reti
temp2
; register sichern,
; wird fr Zwsichenergebnisse gebraucht
temp2, '0'
temp1, 10
lcd_number_1
inc
rjmp
lcd_number_1:
push
mov
rcall
pop
subi
temp2
lcd_number_10
temp1
temp1,temp2
lcd_data
temp1
temp1, -10
ldi
add
rcall
temp2, '0'
temp1, temp2
lcd_data
pop
ret
temp2
;
;
;
;
;
;
;
;
;
;
;
;
In der ISR wird nur die Zeit in den Registern neu berechnet, die Ausgabe auf das LCD erfolgt in
der Hauptschleife. Das ist notwendig, da die LCD-Ausgabe bisweilen sehr lange dauern kann.
Wenn sie lnger als ~2/15 Sekunden dauert werden Timerinterrupts "verschluckt" und unsere
Uhr geht noch mehr falsch. Dadurch, dass aber die Ausgabe in der Hauptschleife durchgefhrt
wird, welche jederzeit durch einen Timerinterrupt unterbrochen werden kann, werden keine
Timerinterrupts verschluckt. Das ist vor allem wichtig, wenn mit hheren Interruptfrequenzen
gearbeitet wird, z. B. 1/100s im Beispiel weiter unten. Auch wenn in diesem einfachen Beispiel
die Ausgabe bei weitem nicht 2/15 Sekunden dauert, sollte man sich diesen Programmierstil
allgemein angewhnen. Siehe auchInterrupt.
Eine weitere Besonderheit ist das Register flag (=r19). Dieses Register fungiert als Anzeiger,
wie eine Flagge, daher auch der Name. In der ISR wird dieses Register auf 1 gesetzt. Die
Hauptschleife, also alles zwischen loop und rjmp loop, prft dieses Flag und nur dann, wenn
das Flag auf 1 steht, wird die LCD Ausgabe gemacht und das Flag wieder auf 0 zurckgesetzt.
Auf diese Art wird nur dann Rechenzeit fr die LCD Ausgabe verbraucht, wenn dies tatschlich
notwendig ist. Die ISR teilt mit dem Flag der Hauptschleife mit, dass eine bestimmte Aufgabe,
nmlich der Update der Anzeige gemacht werden muss und die Hauptschleife reagiert darauf
bei nchster Gelegenheit, indem sie diese Aufgabe ausfhrt und setzt das Flag zurck. Solche
Flags werden daher auch Job-Flags genannt, weil durch ihr setzten das Abarbeiten eines Jobs
(einer Aufgabe) angestoen wird. Auch hier gilt wieder: Im Grunde wrde man in diesem
speziellen Beispiel kein Job-Flag bentigen, weil es in der Hauptschleife nur einen einzigen
mglichen Job, die Neuausgabe der Uhrzeit, gibt. Sobald aber Programme komplizierter
werden und mehrere Jobs mglich sind, sind Job-Flags eine gute Mglichkeit, ein Programm so
zu organsieren, dass bestimmte Dinge nur dann gemacht werden, wenn sie notwendig sind.
Im Moment gibt es keine Mglichkeit, die Uhr auf eine bestimmte Uhrzeit einzustellen. Um dies
tun zu knnen, mssten noch zustzlich Taster an den Mikrocontroller angeschlossen werden,
mit deren Hilfe die Sekunden, Minuten und Stunden hndisch vergrert bzw. verkleinert
werden knnen. Studieren Sie mal die Bedienungsanleitung einer kuflichen Digitaluhr und
versuchen sie zu beschreiben, wie dieser Stellvorgang bei dieser Uhr vor sich geht. Sicherlich
werden Sie daraus eine Idee entwickeln knnen, wie ein derartiges Stellen mit der hier
vorgestellten Digitaluhr funktionieren knnte. Als Zwischenlsung kann man im Programm die
Uhr beim Start anstelle von 00:00:00 z. B. auch auf 20:00:00 stellen und exakt mit dem Start der
Tagesschau starten. Wobei der Start der Tagesschau verzgert bei uns ankommt, je nach
bertragung knnen das mehrere Sekunden sein.
[Bearbeiten]Ganggenauigkeit
Wird die Uhr mit einer gekauften Uhr verglichen, so stellt man schnell fest, dass sie ganz schn
ungenau geht. Sie geht vor! Woran liegt das? Die Berechnung sieht so aus:
Daraus errechnet sich, dass in einer Sekunde 4000000 / 1024 / 256 = 15.258789 Overflow
Interrupts auftreten. Im Programm wird aber bereits nach 15 Overflows eine Sekunde
weitergeschaltet, daher geht die Uhr vor. Rechnen wir etwas:
So wie bisher luft die Uhr also rund 1.7 % zu schnell. In einer Minute ist das immerhin etwas
mehr als eine Sekunde. Im Grunde ist das ein hnliches Problem wie mit unserer Jahreslnge.
Ein Jahr dauert nicht exakt 365 Tage, sondern in etwa einen viertel Tag lnger. Die Lsung, die
im Kalender dafr gemacht wurde - der Schalttag -, knnte man fast direkt bernehmen. Nach 3
Stck 15er Overflow Sekunden folgt eine Sekunde fr die 16 Overflows ablaufen mssen. Wie
sieht die Rechnung bei einem 15, 15, 15, 16 Schema aus? Fr 4 Sekunden werden exakt
15.258789 * 4 = 61,035156 Overflow Interrupts bentigt. Mit einem 15, 15, 15, 16 Schema
werden in 4 Sekunden genau 61 Overflow Interrupts durchgefhrt. Der relative Fehler betrgt
dann
Mit diesem Schema ist der Fehler betrchtlich gesunken. Nur noch 0.06%. Bei dieser Rate
muss die Uhr immerhin etwas lnger als 0,5 Stunden laufen, bis der Fehler auf eine Sekunde
angewachsen ist. Das sind aber immer noch 48 Sekunden pro Tag bzw. 1488 Sekunden (=24,8
Minuten) pro Monat. So schlecht sind nicht mal billige mechanische Uhren!
Jetzt knnte man natrlich noch weiter gehen und immer kompliziertere "Schalt-Overflow"Schemata austfteln und damit die Genauigkeit nher an 100% bringen. Aber gibt es noch
andere Mglichkeiten?
Im ersten Ansatz wurde ein Vorteiler von 1024 eingesetzt. Was passiert bei einem anderen
Vorteiler? Nehmen wir mal einen Vorteiler von 64. Das heit, es mssen ( 4000000 / 64 ) / 256
= 244.140625 Overflows auflaufen, bis 1 Sekunde vergangen ist. Wenn also 244 Overflows
gezhlt werden, dann beluft sich der Fehler auf
Nicht schlecht. Nur durch Verndern von 2 Zahlenwerten im Programm (Teilerfaktor und Anzahl
der Overflow Interrupts bis zu einer Sekunden) kann die Genauigkeit gegenber dem
ursprnglichen Overflow-Schema betrchtlich gesteigert werden. Aber geht das noch besser?
Ja das geht. Allerdings nicht mit dem Overflow Interrupt.
[Bearbeiten]Der
Worin liegt denn das eigentliche Problem, mit dem die Uhr zu kmpfen hat? Das Problem liegt
darin, dass jedesmal ein kompletter Timerzyklus bis zum Overflow abgewartet werden muss,
um darauf zu reagieren. Da aber nur jeweils ganzzahlige Overflowzyklen abgezhlt werden
knnen, heit das auch, dass im ersten Fall nur in Vielfachen von 1024 * 256 = 262144 Takten
operiert werden kann, whrend im letzten Fall immerhin schon eine Granulierung von 64 * 256
= 16384 Takten erreicht wird. Aber offensichtlich ist das nicht genau genug. Bei 4 MHz
entsprechen 262144 Takte bereits einem Zeitraum von 65,5ms, whrend 16384 Takte einem
Zeitbedarf von 4,096ms entsprechen. Beide Zahlen teilen aber 1000ms nicht ganzzahlig. Und
daraus entsteht der Fehler. Angestrebt wird ein Timer, der seinen Overflow so erreicht, dass
sich ein ganzzahliger Teiler von 1 Sekunde einstellt. Dazu msste man dem Timer aber
vorschreiben knnen, bei welchem Zhlerstand der Overflowerfolgen soll. Und genau dies ist
im CTC-Modus, allerdings nur beim Timer 1, mglich. CTC bedeutet "Clear Timer on Compare
match".
Timer 1, ein 16-Bit-Timer, wird mit einem Vorteiler von 1 betrieben. Dadurch wird erreicht, dass
der Timer mit hchster Zeitauflsung arbeiten kann. Bei jedem Ticken des Systemtaktes von 4
MHz wird auch der Timer um 1 erhht. Zustzlich wird noch das WGM12-Bit bei der
Konfiguration gesetzt. Dadurch wird der Timer in den CTC-Modus gesetzt. Dabei wird der Inhalt
des Timers hardwaremig mit dem Inhalt des OCR1A-Registers verglichen. Stimmen beide
berein, so wird der Timer auf 0 zurckgesetzt und im nchsten Taktzyklus ein OCIE1AInterrupt ausgelst. Dadurch ist es mglich, exakt die Anzahl an Taktzyklen festzulegen, die von
einem Interrupt zum nchsten vergehen sollen. Das Compare Register OCR1A wird mit dem
Wert 39999 vorbelegt. Dadurch vergehen exakt 40000 Taktzyklen von einem Compare-Interrupt
zum nchsten. "Zufllig" ist dieser Wert so gewhlt, dass bei einem Systemtakt von 4 MHz von
einem Interrupt zum nchsten genau 1/100 Sekunde vergeht, denn 40000 / 4000000 = 0.01.
Bei einem mglichen Umbau der Uhr zu einer Stoppuhr knnte sich das als ntzlich erweisen.
Im Interrupt wird das Hilfsregister SubCount bis 100 hochgezhlt und nach 100 Interrupts
kommt wieder die Sekundenweiterschaltung wie oben in Gang.
.include "m8def.inc"
.def
.def
.def
.def
temp1
temp2
temp3
Flag
=
=
=
=
.def
.def
.def
.def
SubCount
Sekunden
Minuten
Stunden
r16
r17
r18
r19
=
=
=
=
r21
r22
r23
r24
.org 0x0000
rjmp
.org OC1Aaddr
rjmp
main
; Reset Handler
timer1_compare
.include "lcd-routines.asm"
main:
ldi
out
ldi
out
temp1, HIGH(RAMEND)
SPH, temp1
temp1, LOW(RAMEND) ; Stackpointer initialisieren
SPL, temp1
rcall
rcall
lcd_init
lcd_clear
ldi
out
; Vergleichswert
temp1, high( 40000 - 1 )
OCR1AH, temp1
temp1, low( 40000 - 1 )
OCR1AL, temp1
; CTC Modus einschalten
; Vorteiler auf 1
temp1, ( 1 << WGM12 ) | ( 1 << CS10 )
TCCR1B, temp1
ldi
out
clr
clr
clr
clr
clr
Minuten
Sekunden
Stunden
SubCount
Flag
ldi
out
ldi
out
loop:
; Flag lschen
sei
cpi
breq
ldi
flag,0
loop
flag,0
rcall
mov
rcall
ldi
rcall
mov
rcall
ldi
rcall
mov
rcall
lcd_clear
temp1, Stunden
lcd_number
temp1, ':'
lcd_data
temp1, Minuten
lcd_number
temp1, ':'
lcd_data
temp1, Sekunden
lcd_number
rjmp
loop
timer1_compare:
push
in
; temp 1 sichern
; SREG sichern
inc
cpi
brne
SubCount
SubCount, 100
end_isr
clr
inc
cpi
brne
SubCount
Sekunden
Sekunden, 60
Ausgabe
clr
inc
cpi
brne
berlauf
SubCount rcksetzen
plus 1 Sekunde
sind 60 Sekunden vergangen?
wenn nicht kann die Ausgabe schon
gemacht werden
Sekunden
Minuten
Minuten, 60
Ausgabe
;
;
;
;
;
berlauf
Sekunden wieder auf 0 und dafr
plus 1 Minute
sind 60 Minuten vergangen ?
wenn nicht, -> Ausgabe
clr
inc
cpi
brne
Minuten
Stunden
Stunden, 24
Ausgabe
;
;
;
;
;
berlauf
Minuten zurcksetzen und dafr
plus 1 Stunde
nach 24 Stunden, die Stundenanzeige
wieder zurcksetzen
clr
Stunden
; berlauf
; Stunden rcksetzen
flag,1
sreg,temp1
temp1
Ausgabe:
ldi
end_isr:
out
pop
reti
ldi
add
rcall
temp2
temp2, '0'
; register sichern,
; wird fr Zwsichenergebnisse gebraucht
temp1, 10
lcd_number_1
temp2
lcd_number_10
temp1
temp1,temp2
lcd_data
temp1
temp1, -10
;
;
;
;
;
;
;
;
;
;
;
;
temp2, '0'
temp1, temp2
lcd_data
pop
ret
temp2
In der Interrupt-Routine werden wieder, genauso wie vorher, die Anzahl der Interrupt-Aufrufe
gezhlt. Beim 100. Aufruf sind daher 40.000 * 100 = 4.000.000 Takte vergangen und da der
Quarz mit 4.000.000 Schwingungen in der Sekunde arbeitet, ist daher eine Sekunde
vergangen. Sie wird genauso wie vorher registriert und die Uhr entsprechend hochgezhlt. Wird
jetzt die Uhr mit einer kommerziellen verglichen, dann fllt nach einiger Zeit auf ... Sie geht
immer noch falsch! Was ist jetzt die Ursache? Die Ursache liegt in einem Problem, das nicht
direkt behebbar ist. Am Quarz! Auch wenn auf dem Quarz drauf steht, dass er eine Frequenz
von 4MHz hat, so stimmt das nicht exakt. Auch Quarze haben Fertigungstoleranzen und
verndern ihre Frequenz mit der Temperatur. Typisch liegt die Fertigungstoleranz bei +/100ppm = 0,01% (parts per million, Millionstel Teile), die Temperaturdrift zwischen -40 Grad und
85 Grad liegt je nach Typ in der selben Grenordnung. Das bedeutet, dass die Uhr pro Monat
um bis zu 268 Sekunden (~4 1/2 Minuten) falsch gehen kann. Diese Einflsse auf die
Quarzfrequenz sind aber messbar und per Hardware oder Software behebbar. In Uhren
kommen normalerweise genauer gefertigte Uhrenquarze zum Einsatz, die vom Uhrmacher auch
noch auf die exakte Frequenz abgeglichen werden (mittels Kondensatoren und
Frequenzzhler). Ein Profi verwendet einen sehr genauen Frequenzzhler, womit er innerhalb
weniger Sekunden die Frequenz sehr genau messen kann. Als Hobbybastler kann man die Uhr
eine zeitlang (Tage, Wochen) laufen lassen und die Abweichung feststellen (z. B. exakt 20:00
Uhr zum Start der Tagsschau). Aus dieser Abweichung lsst sich dann errechnen, wie schnell
der Quarz wirklich schwingt. Und da dank CTC die Messperiode taktgenau eingestellt werden
kann, ist es mglich, diesen Frequenzfehler auszugleichen. Der genaue Vorgang ist in dem
Wikiartikel AVR - Die genaue Sekunde / RTC beschrieben.
AVR-Tutorial: ADC
Inhaltsverzeichnis
[Verbergen]
1 Was macht der ADC?
2 Elektronische Grundlagen
o
6.1 ADMUX
6.2 ADCSRA
7 Die Ergebnisregister ADCL und ADCH
8 Beispiele
[Bearbeiten]Was
Wenn es darum geht, Spannungen zu messen, wird der Analog-/Digital-Wandler (kurz: A/DWandler) oder englisch Analog Digital Converter (ADC) benutzt. Er konvertiert eine elektrische
Spannung in eine Digitalzahl. Prinzipiell wird dabei die Messspannung mit einer
Referenzspannung verglichen. Die Zahl drckt daher das Verhltnis der Messspannung zu
dieser Referenzspannung aus. Sie kann in gewohnter Weise von
einem Mikrocontroller weiterverarbeitet werden.
[Bearbeiten]Elektronische
Grundlagen
Die ADC-Versorgungsspannung (AVCC) darf maximal um +/-0,3V von der Versorgung des
Digitalteils (VCC) abweichen, jedoch nicht 5,5V berschreiten. Die externe Referenzspannung
VREF darf nicht kleiner als die im Datenblatt unter ADC Characteristics als VREFmin
angegebene Spannung (z. B. ATmega8: 2V, ATmega644P: 1V) und nicht grer als AVCC sein.
Die Spannungen an den Wandlereingngen mssen im Intervall GND VIN VREF liegen.
Im Extremfall bedeutet dies: Sei VCC = 5,5V, folgt AVCC_max = VREF_max = VIN_max = 5,5V.
Der Eingangswiderstand des ADC liegt in der Grenordnung von einigen Megaohm, so dass
der ADC die Signalquelle praktisch nicht belastet. Desweiteren enthlt der Mikrocontroller eine
sog.Sample&Hold Schaltung. Dies ist wichtig, wenn sich whrend des Wandlungsvorgangs die
Eingangsspannung verndert, da die AD-Wandlung eine bestimmte Zeit dauert. Die
Sample&Hold-Stufe speichert zum Beginn der Wandlung die anliegende Spannung und hlt sie
whrend des Wandlungsvorgangs konstant.
[Bearbeiten]Beschaltung des ADC-Eingangs
Um den ADC im Folgenden zu testen wird eine einfache Schaltung an den PC0-Pin des
ATmega8 angeschlossen. Dies ist der ADC-Kanal 0. Bei anderen AVR-Typen liegt der
entsprechende Eingang auf einem andern Pin, hier ist ein Blick ins Datenblatt angesagt.
Testschaltung
Der Wert des Potentiometers ist Dank des hohen Eingangswiderstandes des ADC ziemlich
unkritisch. Es kann jedes Potentiometer von 1k bis 1M benutzt werden.
Wenn andere Messgren gemessen werden sollen, so bedient man sich oft und gern des
Prinzips des Spannungsteilers. Der Sensor ist ein vernderlicher Widerstand. Zusammen mit
einem zweiten, konstanten Widerstand bekannter Gre wird ein Spannungsteiler aufgebaut.
Aus der Variation der durch den variablen Spannungsteiler entstehenden Spannung kann auf
den Messwert zurckgerechnet werden.
Vcc ----------+
|
--| |
Vcc ---------+
|
Sensor,
der seinen Widerstand
| |
--|
+------- PC0
|
Sensor,
der seinen Widerstand
in Abhngigkeit der
Messgre ndert
|
GND ---------+
in Abhngigkeit der
Messgre ndert
|
+-------- PC0
|
--| |
| |
--|
GND --------+
Die Gre des zweiten Widerstandes im Spannungsteiler richtet sich nach dem Wertebereich,
in welchem der Sensor seinen Wert ndert. Als Daumenregel kann man sagen, dass der
Widerstand so gross sein sollte wie der Widerstand des Sensors in der Mitte des Messbereichs.
Beispiel: Wenn ein Temperatursensor seinen Widerstand von 0..100 Grad von 2k auf 5k
ndert, sollte der zweite Widerstand eine Grsse von etwa (2+5)/2 = 3,5k haben.
Aber egal wie immer man das auch macht, der entscheidende Punkt besteht darin, dass man
seine Messgre in eine vernderliche Spannung 'bersetzt' und mit dem ADC des Mega8 die
Hhe dieser Spannung misst. Aus der Hhe der Spannung kann dann wieder in der Umkehrung
auf die Messgre zurckgerechnet werden.
[Bearbeiten]Referenzspannung AREF
Der ADC bentigt fr seine Arbeit eine Referenzspannung. Dabei gibt es 2 Mglichkeiten:
interne Referenzspannung
externe Referenzspannung
Bei der Umstellung der Referenzspannung sind Wartezeiten zu beachten, bis die ADCHardware einsatzfhig ist (Datenblatt und [1]).
[Bearbeiten]Interne
Referenzspannung
Referenzspannung
Wird eine externe Referenz verwendet, so wird diese an AREF angeschlossen. Aber
aufgepasst! Wenn eine Referenz in Hhe der Versorgungsspannung benutzt werden soll, so ist
es besser, dies ber die interne Referenz zu tun. Auer bei anderen Spannungen als 5V bzw.
2,56V gibt es eigentlich keinen Grund, an AREF eine Spannungsquelle anzuschlieen. In
Standardanwendungen fhrt man immer besser, wenn die interne Referenzspannung mit einem
Kondensator an AREF benutzt wird. Die 10H-Spule L1 kann man meist auch durch einen 47Widerstand ersetzen.
[Bearbeiten]Ein
paar ADC-Grundlagen
Der ADC ist ein 10-Bit ADC, d.h. er liefert Messwerte im Bereich 0 bis 1023. Liegt am
Eingangskanal 0V an, so liefert der ADC einen Wert von 0. Hat die Spannung am
Eingangskanal die Referenzspannung erreicht (stimmt nicht ganz), so liefert der ADC einen
Wert von 1023. Unterschreitet oder berschreitet die zu messende Spannung diese Grenzen,
so liefert der ADC 0 bzw. 1023. Wird die Auflsung von 10 Bit nicht bentigt, so ist es mglich
die Ausgabe durch ein Konfigurationsregister so einzuschrnken, dass ein leichter Zugriff auf
die 8 hchstwertigen Bits mglich ist.
Wie bei vielen analogen Schaltungen, unterliegt auch der ADC einem Rauschen. Das bedeutet,
dass man nicht davon ausgehen sollte, dass der ADC bei konstanter Eingangsspannung auch
immer denselben konstanten Wert ausgibt. Ein "Zittern" der niederwertigsten 2 Bits ist durchaus
nicht ungewhnlich. Besonders hervorgehoben werden soll an dieser Stelle nochmals die
Qualitt der Referenzspannung. Diese Qualitt geht in erheblichem Mae in die Qualitt der
Wandlergebnisse ein. Die Beschaltung von AREF mit einem Kondensator ist die absolut
notwendige Mindestbeschaltung, um eine einigermaen akzeptable Referenzspannung zu
erhalten. Reicht dies nicht aus, so kann die Qualitt einer Messung durch Oversampling erhht
werden. Dazu werden mehrere Messungen gemacht und deren Mittelwert gebildet.
Oft interessiert auch der absolute Spannungspegel nicht. Im Beschaltungsbeispiel oben ist man
normalerweise nicht direkt an der am Poti entstehenden Spannung interessiert. Viel mehr ist
diese Spannung nur ein notwendiges bel, um die Stellung des Potis zu bestimmen. In solchen
Fllen kann die Poti-Beschaltung wie folgt abgewandelt werden:
Hier wird AREF (bei interner Referenz) als vom C gelieferte Spannung benutzt und vom
Spannungsteiler bearbeitet wieder an den C zur Messung zurckgegeben. Dies hat den
Vorteil, dass der Spannungsteiler automatisch Spannungen bis zur Hhe der
Referenzspannung ausgibt, ohne dass eine externe Spannung mit AREF abgeglichen werden
msste. Selbst Schwankungen in AREF wirken sich hier nicht mehr aus, da ja das Verhltnis
der Spannungsteilerspannung zu AREF immer konstant bleibt (ratiometrische Messung). Und
im Grunde bestimmt der ADC ja nur dieses Verhltnis. Wird diese Variante gewhlt, so muss
bercksichtigt werden, dass die Ausgangsspannung an AREF nicht allzusehr belastet wird. Der
Spannungsteiler muss einen Gesamtwiderstand von deutlich ber 10k besitzen. Werte von
100k oder hher sind anzustreben. Verwendet man anstatt AREF AVCC und schaltet auch die
Referenzspannung auf AVCC um, ist die Belastung durch den Poti unkritisch, weil hier die
Stromversorgung direkt zur Speisung verwendet wird.
Ist hingegen die absolute Spannung von Interesse, so muss man darauf achten, dass ein ADC
in digitalen Bereichen arbeitet (Quantisierung). An einem einfacheren Beispiel soll demonstriert
werden was damit gemeint ist.
Angenommen der ADC wrde nur 5 Stufen auflsen knnen und AREF sei 5V:
Volt
0 -+
|
1 -+
|
2 -+
|
3 -+
|
4 -+
|
5 -+
Ein ADC Wert von 0 bedeutet also keineswegs, dass die zu messende Spannung exakt den
Wert 0 hat. Es bedeutet lediglich, dass die Messspannung irgendwo im Bereich von 0V bis 1V
liegt. Sinngem bedeutet daher auch das Auftreten des Maximalwertes nicht, dass die
Spannung exakt AREF betrgt, sondern lediglich, dass die Messspannung sich irgendwo im
Bereich der letzten Stufe (also von 4V bis 5V) bewegt.
[Bearbeiten]Umrechnung
Der Messwert vom ADC rechnet sich dann wie folgt in eine Spannung um:
Wird der ADC also mit 10 Bit an 5 V betrieben, so lauten die Umrechnungen:
Wenn man genau hinsieht stellt man fest, dass sowohl die Referenzspannung als auch der
Maximalwert Konstanten sind. D.h. der Quotient aus Referenzspannung und Maximalwert ist
konstant. Somit muss nicht immer eine Addition und Division ausgefhrt werden, sondern nur
eine Multiplikation! Das spart viel Aufwand und Rechenzeit! Dabei kann
sinnvollerweise Festkommaarithmetik zum Einsatz kommen.
[Bearbeiten]Kalibrierung
Hat man eine externe, genaue Referenzspannung zur Hand, dann kann ein Korrekturfaktor
berechnet werden, mit dem die Werte des ADCs im Nachhinein korrigiert werden knnen. Dies
geschieht normalerweise ber eine sogenannte gain offset Korrektur an einer Geraden oder
einer Parabel. In erster Nherung kann man auch die interne Referenzspannung um das
Inverse des ermittelten Korrekturwertes verstellen, um einen genaueren bereits digitalisierten
Wert zu bekommen.
[Bearbeiten]Die
[Bearbeiten]ADMUX
ADMUX
MU
X3
MU
X2
MU
X1
MU
X0
REFS1
REFS0
externe Referenz
Referenz
Ausrichtung ADLAR
ADL
AR
MUX3
MUX2
MUX1
MUX0
Kanal
Kanal 6 (*)
Kanal 7 (*)
1.23V, Vbg
0V, GND
(*) Bei Atmega8 nur in der Gehusebauform TQFP und MLF verfgbar, nicht in PDIP
[Bearbeiten]ADCSRA
ADCSRA
ADEN
"ADC Enable": Mittels ADEN wird der ADC ein und ausgeschaltet. Eine 1 an dieser
Bitposition schaltet den ADC ein.
ADSC
"ADC Start Conversion": Wird eine 1 an diese Bitposition geschrieben, so beginnt der
ADC mit der Wandlung. Das Bit bleibt auf 1, solange die Wandlung im Gange ist. Wenn
die Wandlung beendet ist, wird dieses Bit von der ADC Hardware wieder auf 0 gesetzt.
ADFR
"ADC Free Running": Wird eine 1 an ADFR geschrieben, so wird der ADC im Free
Running Modus betrieben. Dabei startet der ADC nach dem Abschluss einer Messung
automatisch die nchste Messung. Die erste Messung wird ganz normal ber das
Setzen des ADSC-Bits gestartet.
ADIF
"ADC Interrupt Flag": Wenn eine Messung abgeschlossen ist, wird das ADIF Bit gesetzt.
Ist zustzlich noch das ADIE Bit gesetzt, so wird ein Interrupt ausgelst und der
entsprechende Interrupt Handler angesprungen.
ADIE
"ADC Interrupt Enable": Wird eine 1 an ADIE geschrieben, so lst der ADC nach
Beendigung einer Messung einen Interrupt aus.
ADPS2, ADPS1, ADPS0
"ADC Prescaler": Mit dem Prescaler kann die ADC-Frequenz gewhlt werden. Laut
Datenblatt sollte diese fr die optimale Auflsung zwischen 50kHz und 200kHz liegen.
Ist die Wandlerfrequenz langsamer eingestellt, kann es passieren dass die eingebaute
Sample & Hold Schaltung die Eingangsspannung nicht lange genug konstant halten
kann. Ist die Frequenz aber zu schnell eingestellt, dann kann es passieren dass sich die
Sample & Hold Schaltung nicht schnell genug an die Eingangsspannung anpassen
kann.
ADPS2
ADPS1
ADPS0
Vorteiler
16
32
64
128
[Bearbeiten]Die
Da das Ergebnis des ADC ein 10 Bit Wert ist, passt dieser Wert naturgem nicht in ein
einzelnes Register, das ja bekanntlich nur 8 Bit breit ist. Daher wird das Ergebnis in 2
Register ADCL undADCH abgelegt. Von den 10 Ergebnisbits sind die niederwertigsten 8 im
Register ADCL abgelegt und die noch fehlenden 2 Bits werden im Register ADCH an den
niederwertigsten Bitpositionen gespeichert.
ADCH
+---+---+---+---+---+---+---+---+
ADCL
+---+---+---+---+---+---+---+---
+
|
|
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---
+
9
Diese Zuordnung kann aber auch gendert werden: Durch setzen des ADLAR Bits
im ADMUX Register wird die Ausgabe gendert zu:
ADCH
+---+---+---+---+---+---+---+---+
ADCL
+---+---+---+---+---+---+---+---
+
|
|
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---
+
9
Dies ist besonders dann interessant, wenn das ADC Ergebnis als 8 Bit Zahl weiterverarbeitet
werden soll. In diesem Fall stehen die 8 hchstwertigen Bits bereits verarbeitungsfertig im
Register ADCHzur Verfgung.
Beim Auslesen der ADC-Register ist zu beachten: Immer zuerst ADCL und erst
dann ADCH auslesen. Beim Zugriff auf ADCL wird das ADCH Register gegenber
Vernderungen vom ADC gesperrt. Erst beim nchsten Auslesen des ADCH-Registers wird
diese Sperre wieder aufgehoben. Dadurch ist sichergestellt, da die Inhalte
von ADCL und ADCH immer aus demselben Wandlungsergebnis stammen, selbst wenn
der ADC im Hintergrund selbstttig weiterwandelt. Das ADCH Register muss ausgelesen
werden!
[Bearbeiten]Beispiele
[Bearbeiten]Ausgabe als ADC-Wert
Das folgende Programm liest in einer Schleife stndig den ADC aus und verschickt das
Ergebnis im Klartext (ASCII) ber die UART. Zur Verringerung des unvermeidlichen Rauschens
werden 256 Messwerte herangezogen und deren Mittelwert als endgltiges Messergebnis
gewertet. Dazu werden die einzelnen Messungen in den Registern temp2, temp3, temp4 als 24
Bit Zahl aufaddiert. Die Division durch 256 erfolgt dann ganz einfach dadurch, dass das
Register temp2 verworfen wird und die Register temp3 und temp4 als 16 Bit Zahl aufgefasst
werden. Eine Besonderheit ist noch, dass je nach dem Wert in temp2 die 16 Bit Zahl in temp3
und temp4 noch aufgerundet wird: Enthlt temp2 einen Wert grer als 128, dann wird zur 16
Bit Zahl in temp3/temp4 noch 1 dazu addiert.
In diesem Programm findet man oft die Konstruktion
subi
sbci
temp3, low(-1)
temp4, high(-1)
; addieren von 1
; addieren des Carry
Dabei handelt es sich um einen kleinen Trick. Um eine Konstante zu einem Register direkt
addieren zu knnen bruchte man einen Befehl ala addi (Add Immediate, Addiere Konstante),
den der AVR aber nicht hat. Ebenso gibt es kein adci (Add with carry Immediate, Addiere
Konstante mit Carry Flag). Man msste also erst eine Konstante in ein Register laden und
addieren. Das kostet aber Programmspeicher, Rechenzeit und man muss ein Register
zustzlich frei haben.
; 16 Bit Addition mit Konstante, ohne Cleverness
ldi
temp5, low(1)
add
temp3, temp5
; addieren von 1
ldi
temp5, high(1)
adc
temp3, temp5
; addieren des Carry
Hier greift man einfach zu dem Trick, dass eine Addition gleich der Subtraktion der negativen
Werts ist. Also "addiere +1" ist gleich "subtrahiere -1". Dafr hat der AVR zwei Befehle, subi
(Substract Immediate, Subtrahiere Konstante) und sbci (Substract Immediate with carry,
Subtrahiere Konstante mit Carry Flag).
Das folgende Programm ist fr den ATmega8 geschrieben. Fr moderne Nachfolgetypen wie
den ATmega88 muss der Code angepasst werden ([2], AVR094: Replacing ATmega8 by
ATmega88 (PDF)).
.include "m8def.inc"
.def
.def
.def
.def
.def
.def
.def
.def
temp1
temp2
temp3
temp4
adlow
adhigh
messungen
ztausend
=
=
=
=
=
=
=
=
r16
r17
r18
r19
r20
r21
r22
r23
;
;
;
;
;
;
;
;
.def
.def
.def
.def
tausend
hundert
zehner
zeichen
=
=
=
=
r24
r25
r26
r27
;
;
;
;
; Systemtakt in Hz
; Baudrate
; Berechnungen
.equ UBRR_VAL
= ((F_CPU+BAUD*8)/(BAUD*16)-1)
.equ BAUD_REAL = (F_CPU/(16*(UBRR_VAL+1)))
.equ BAUD_ERROR = ((BAUD_REAL*1000)/BAUD-1000)
; clever runden
; Reale Baudrate
; Fehler in Promille
temp1, LOW(RAMEND)
SPL, temp1
temp1, HIGH(RAMEND)
SPH, temp1
; Stackpointer initialisieren
;UART Initalisierung
ldi
out
ldi
out
temp1,
UBRRL,
temp1,
UBRRH,
LOW(UBRR_VAL)
temp1
HIGH(UBRR_VAL)
temp1
sbi
UCSRB, TXEN
; Baudrate einstellen
; TX einschalten
; ADC initialisieren: ADC0, Vcc als Referenz, Single Conversion, Vorteiler 128
ldi
out
ldi
out
Main:
clr
clr
clr
clr
ldi
temp1, (1<<REFS0)
; Kanal 0, interne Referenzspannung 5V
ADMUX, temp1
temp1, (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)
ADCSRA, temp1
temp1
temp2
temp3
temp4
messungen, 0
; 256 Schleifendurchlufe
sample_adc:
sbi
ADCSRA, ADSC
wait_adc:
sbic
rjmp
ADCSRA, ADSC
wait_adc
; ADC einlesen:
in
in
adlow, ADCL
adhigh, ADCH
;
;
;
;
;
;
;
;
;
;
temp2, adlow
temp3, adhigh
temp4, temp1
messungen
sample_adc
;
;
;
;
;
addieren
addieren ber Carry
addieren ber Carry, temp1 enthlt 0
Schleifenzhler MINUS 1
wenn noch keine 256 ADC Werte -> nchsten Wert einle
temp2,128
no_round
; Aufrunden
subi
temp3, low(-1)
sbci
temp4, high(-1)
no_round:
;
;
adlow, temp3
adhigh, temp4
ztausend, '0'-1
; Ziffernzhler direkt als ASCII Code
'0'-1 siehe http://www.mikrocontroller.net/topic/198681
ztausend
adlow, low(10000)
; -10,000
adhigh, high(10000) ; 16 Bit
Z_ztausend
adlow, low(-10000) ; nach Unterlauf wieder einmal addieren
adhigh, high(-10000); +10,000
tausend, '0'-1
tausend
adlow, low(1000)
adhigh, high(1000)
Z_tausend
; -1,000
; 16 Bit
adlow, low(-1000)
; nach Unterlauf wieder einmal addieren
adhigh, high(-1000) ; +1,000
hundert, '0'-1
hundert
subi
sbci
brcc
adlow, low(100)
adhigh, high(100)
Z_hundert
; -100
; 16 Bit
subi
sbci
adlow, low(-100)
adhigh, high(-100)
zehner, '0'-1
zehner
adlow, low(10)
adhigh, high(10)
Z_zehner
; -10
; 16 Bit
subi
sbci
adlow, low(-10)
adhigh, high(-10)
subi
adlow, -'0'
ldi
Z_zehner:
inc
subi
sbci
brcc
zeichen,
transmit
zeichen,
transmit
zeichen,
transmit
zeichen,
transmit
zeichen,
transmit
zeichen,
transmit
zeichen,
transmit
rjmp
Main
transmit:
sbis
rjmp
out
ret
ztausend
; Zehntausender Stelle
tausend
hundert
zehner
adlow
13
10
UCSRA,UDRE
transmit
UDR, zeichen
Referenzspannung : 5V
Der Faktor wird dreimal mit 10 multipliziert und das Ergebnis auf 4883 gerundet. Die neue
Auflsung wird dreimal durch 10 dividiert und betrgt 1V. Der relative Fehler betrgt
Dieser Fehler ist absolut vernachlssigbar. Nach der Multiplikation des ADC-Wertes mit 4883
liegt die gemessene Spannung in der Einheit V vor. Vorsicht! Das ist nicht die reale Auflsung
und Genauigkeit, nur rein mathematisch bedingt. Fr maximale Genauigkeit sollte man die
Versorgungsspannung AVCC, welche hier gleichzeitig als Referenzspannung dient, exakt
messen, die Rechnung nachvollziehen und den Wert im Quelltext eintragen. Damit fhrt man
eine einfach Einpunktkalibrierung durch.
Da das Programm schon um einiges grer und komplexer ist, wurde es im Vergleich zur
Vorgngerversion gendert. Die Multiplikation sowie die Umwandung der Zahl in einen ASCIIString sind als Unterprogramme geschrieben, dadurch erhlt man wesentlich mehr berblick im
Hauptprogramm und die Wiederverwendung in anderen Programmen vereinfacht sich.
Ausserdem wird der String im RAM gespeichert und nicht mehr in CPU-Registern. Die
Berechung der einzelnen Ziffern erfolgt ber ein Schleife, das ist kompakter und bersichtlicher.
.include "m8def.inc"
.def
.def
.def
.def
.def
.def
.def
.def
.def
.def
.def
.def
.def
.def
z0
z1
z2
z3
temp1
temp2
temp3
temp4
adlow
adhigh
messungen
zeichen
temp5
temp6
=
=
=
=
=
=
=
=
=
=
=
=
=
=
r1
r2
r3
r4
r16
r17
r18
r19
r20
r21
r22
r23
r24
r25
;
;
;
;
;
;
;
;
; Systemtakt in Hz
; Baudrate
; Berechnungen
.equ UBRR_VAL
= ((F_CPU+BAUD*8)/(BAUD*16)-1)
.equ BAUD_REAL = (F_CPU/(16*(UBRR_VAL+1)))
.equ BAUD_ERROR = ((BAUD_REAL*1000)/BAUD-1000)
; clever runden
; Reale Baudrate
; Fehler in Promille
.org 0x60
Puffer: .byte 10
; hier geht das Programm los
.cseg
.org 0
ldi
out
ldi
out
temp1, LOW(RAMEND)
SPL, temp1
temp1, HIGH(RAMEND)
SPH, temp1
; Stackpointer initialisieren
;UART Initalisierung
ldi
out
ldi
out
temp1,
UBRRL,
temp1,
UBRRH,
LOW(UBRR_VAL)
temp1
HIGH(UBRR_VAL)
temp1
sbi
UCSRB, TXEN
; Baudrate einstellen
; TX einschalten
temp1, (1<<REFS0)
ADMUX, temp1
temp1, (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)
ADCSRA, temp1
Hauptschleife:
clr
temp1
clr
temp2
clr
temp3
clr
temp4
ldi
messungen, 0
; 256 Schleifendurchlufe
(Schleife - 256 mal)
adc_messung:
sbi
ADCSRA, ADSC
adc_warten:
sbic
ADCSRA, ADSC
rjmp
adc_warten
; ADC einlesen:
in
in
;
;
;
;
adlow, ADCL
adhigh, ADCH
temp2, adlow
temp3, adhigh
temp4, temp1
messungen
adc_messung
;
;
;
;
;
addieren
addieren ber Carry
addieren ber Carry, temp1 enthlt 0
Schleifenzhler MINUS 1
wenn noch keine 256 ADC Werte -> nchsten Wert einle
;
;
;
;
;
temp2,128
nicht_runden
; Aufrunden
subi
temp3, low(-1)
sbci
temp4, high(-1)
nicht_runden:
;
;
adlow, temp3
adhigh, temp4
; in Spannung umrechnen
ldi
ldi
rcall
temp5,low(Faktor)
temp6,high(Faktor)
mul_16x16
; in ASCII umwandeln
ldi
ldi
rcall
XL, low(Puffer)
XH, high(Puffer)
Int_to_ASCII
ZL, low(Puffer+3)
ZH, high(Puffer+3)
temp1, 1
sende_zeichen
ldi
rcall
zeichen, ','
; Komma ausgeben
sende_einzelzeichen
ldi
rcall
temp1, 3
sende_zeichen
ldi
rcall
zeichen, 'V'
; Volt Zeichen ausgeben
sende_einzelzeichen
ldi
rcall
zeichen, 10
; New Line Steuerzeichen
sende_einzelzeichen
ldi
rcall
zeichen, 13
; Carrige Return Steuerzeichen
sende_einzelzeichen
rjmp
Hauptschleife
Int_to_ASCII:
push
push
push
push
ZL
ZH
temp5
temp6
; Register sichern
ldi
ldi
ldi
ZL,low(Tabelle*2)
ZH,high(Tabelle*2)
temp5, 10
Int_to_ASCII_schleife:
ldi
temp6, -1+'0'
lpm
z0,Z+
lpm
z1,Z+
lpm
z2,Z+
lpm
z3,Z+
Int_to_ASCII_ziffer:
inc
temp6
sub
temp1, z0
sbc
temp2, z1
sbc
temp3, z2
sbc
temp4, z3
brge
Int_to_ASCII_ziffer
add
adc
adc
adc
st
dec
brne
temp1, z0
temp2, z1
temp3, z2
temp4, z3
X+,temp6
temp5
Int_to_ASCII_schleife
; Schleifenzhler
; Ziffernzhler zhlt direkt im ASCII Code
; Nchste Zahl laden
; Ziffer erhhen
; Zahl subrahieren
; 32 Bit
; noch kein Unterlauf, nochmal
; Unterlauf, eimal wieder addieren
; 32 Bit
; Ziffer speichern
; noch eine Ziffer?
pop
pop
pop
pop
ret
temp6
temp5
ZH
ZL
mul_16x16:
push
clr
clr
clr
clr
clr
zeichen
temp1
temp2
temp3
temp4
zeichen
; Null, fr Carry-Addition
mul
add
adc
adlow, temp5
temp1, r0
temp2, r1
; erste Multiplikation
; und akkumulieren
mul
add
adc
adhigh, temp5
temp2, r0
temp3, r1
; zweite Multiplikation
; und gewichtet akkumlieren
mul
add
adc
adc
adlow,
temp2,
temp3,
temp4,
; dritte Multiplikation
; und gewichtet akkumlieren
mul
add
adc
adhigh, temp6
temp3, r0
temp4, r1
pop
ret
zeichen
temp6
r0
r1
zeichen
; carry addieren
; vierte Multiplikation
; und gewichtet akkumlieren
Fr alle, die es besonders eilig haben gibt es hier eine geschwindigkeitsoptimierte Version der
Integer in ASCII Umwandlung. Zunchst wird keine Schleife verwendet sondern alle Stufen der
Schleife direkt hingeschrieben. Das braucht zwar mehr Programmspeicher, ist aber schneller.
Ausserdem wird abwechselnd subtrahiert und addiert, dadurch entfllt das immer wieder
notwendige addieren nach dem Unterlauf. Zu guter Letzt werden die Berechnungen nur mit der
minimal notwenigen Wortbreite durchgefhrt. Am Anfang mit 32 Bit, dann nur noch mit 16 bzw. 8
Bit.
;
;
;
;
;
Int_to_ASCII:
ldi
temp5, -1 + '0'
_a1ser:
inc
temp5
subi
temp1,BYTE1(1000000000) ; - 1.000.000.000
sbci
temp2,BYTE2(1000000000)
sbci
temp3,BYTE3(1000000000)
sbci
temp4,BYTE4(1000000000)
brcc
_a1ser
st
ldi
_a2ser:
dec
subi
sbci
sbci
sbci
brcs
X+,temp5
temp5, 10 + '0'
st
ldi
_a3ser:
inc
subi
sbci
sbci
sbci
brcc
X+,temp5
temp5, -1 + '0'
st
ldi
_a4ser:
dec
subi
sbci
sbci
sbci
brcs
X+,temp5
temp5, 10 + '0'
st
ldi
_a5ser:
inc
subi
sbci
sbci
brcc
X+,temp5
temp5, -1 + '0'
st
ldi
_a6ser:
dec
subi
sbci
sbci
brcs
X+,temp5
temp5, 10 + '0'
st
ldi
_a7ser:
X+,temp5
temp5, -1 + '0'
; im Puffer speichern
temp5
temp1,BYTE1(-100000000) ; + 100.000.000
temp2,BYTE2(-100000000)
temp3,BYTE3(-100000000)
temp4,BYTE4(-100000000)
_a2ser
temp5
temp1,low(10000000)
temp2,high(10000000)
temp3,BYTE3(10000000)
temp4,BYTE4(10000000)
_a3ser
temp5
temp1,low(-1000000)
temp2,high(-1000000)
temp3,BYTE3(-1000000)
temp4,BYTE4(-1000000)
_a4ser
temp5
temp1,low(100000)
temp2,high(100000)
temp3,BYTE3(100000)
_a5ser
temp5
temp1,low(-10000)
temp2,high(-10000)
temp3,BYTE3(-10000)
_a6ser
; im Puffer speichern
; - 10.000.000
; im Puffer speichern
; + 1.000.000
; im Puffer speichern
; -100.000
; im Puffer speichern
; +10,000
; im Puffer speichern
inc
subi
sbci
brcc
temp5
temp1,low(1000)
temp2,high(1000)
_a7ser
st
ldi
_a8ser:
dec
subi
sbci
brcs
X+,temp5
temp5, 10 + '0'
st
ldi
_a9ser:
inc
subi
brcc
X+,temp5
temp5, -1 + '0'
st
ldi
_a10ser:
dec
subi
brcs
X+,temp5
temp5, 10 + '0'
st
ret
temp5
temp1,low(-100)
temp2,high(-100)
_a8ser
temp5
temp1, 10
_a9ser
; -1000
; im Puffer speichern
; +100
; im Puffer speichern
; -10
; im Puffer speichern
temp5
temp1, -1
_a10ser
; +1
X+,temp5
; im Puffer speichern
AVR-Tutorial: Tasten
Bisher beschrnkten sich die meisten Programme auf reine Ausgabe an einem Port. Mchte
man Eingaben machen, so ist der Anschluss von Tasten an einen Port unumgnglich. Dabei
erheben sich aber 2 Probleme
Wie kann man erreichen, dass ein Tastendruck nur einmal ausgewertet wird?
Inhaltsverzeichnis
[Verbergen]
1 Erkennung von Flanken am Tasteneingang
2 Prellen
3 Entprellung
4 Kombinierte Entprellung und Flankenerkennung
o
[Bearbeiten]Erkennung
Mchte man eine Taste auswerten, bei der eine Aktion nicht ausgefhrt werden soll, solange die
Taste gedrckt ist, sondern nur einmal beim Drcken einer Taste, dann ist eine Erkennung der
Schaltflanke der Weg zum Ziel. Anstatt eine gedrckte Taste zu erkennen, wird bei einer
Flankenerkennung der Wechsel des Zustands des Eingangspins detektiert.
Dazu vergleicht man in regelmigen Zeitabstnden den momentanen Zustand des Eingangs
mit dem Zustand zum vorhergehenden Zeitpunkt. Unterscheiden sich die beiden, so hat man
eine Schaltflanke erkannt und kann darauf reagieren. Solange sich der Tastenzustand nicht
ndert, egal ob die Taste gedrckt oder losgelassen ist, unternimmt man nichts.
Die Erkennung des Zustandswechsels kann am einfachsten mit einer XOR (Exklusiv Oder)
Verknpfung durchgefhrt werden.
Wahrheitstabelle XOR
Ergebnis
Nur dann, wenn sich der Zustand A vom Zustand B unterscheidet, taucht im Ergebnis eine 1
auf. Sind A und B gleich, so ist das Ergebnis 0.
A ist bei uns der vorhergehende Zustand eines Tasters, B ist der jetzige Zustand so wie er vom
Port Pin eingelesen wurde. Verknpft man die beiden mit einem XOR, so bleiben im Ergebnis
genau an jenen Bitpositionen 1en brig, an denen sich der jetzige Zustand vom
vorhergehenden unterscheidet.
Nun ist bei Tastern aber nicht nur der erkannte Flankenwechsel interessant, sondern auch in
welchen Zustand die Taste gewechselt hat:
Eine einfache UND Verknpfung der Tastenflags mit dem XOR Ergebnis liefert diese
Information
Das folgende Programm soll bei jedem Tastendruck eines Tasters am Port D (egal welcher Pin)
eine LED am Port B0 in den jeweils anderen Zustand umschalten:
.include "m8def.inc"
.def key_old
.def key_now
= r3
= r4
.def temp1
.def temp2
= r17
= r18
.equ key_pin
.equ key_port
.equ key_ddr
= PIND
= PORTD
= DDRD
.equ led_port
.equ led_ddr
.equ LED
= PORTB
= DDRB
= 0
loop:
ldi
out
temp1, 1<<LED
led_ddr, temp1
ldi
out
ldi
out
temp1, $00
key_ddr, temp1
temp1, $FF
key_port, temp1
mov
key_old, temp1
in
key_now, key_pin
mov
eor
mov
temp1, key_now
key_now, key_old
key_old, temp1
;
;
;
;
breq loop
and
temp1, key_now
brne loop
in
com
out
rjmp
temp1, led_port
temp1
led_port, temp1
loop
Probiert man diese Implementierung aus, so stellt man fest: Sie funktioniert nicht besonders gut.
Es kann vorkommen, dass bei einem Tastendruck die LED zwar kurzzeitig umschaltet aber
gleich darauf wieder ausgeht. Genauso gut kann es passieren, dass die LED beim Loslassen
einer Taste ebenfalls wieder den Zustand wechselt. Die Ursache dafr ist: Taster prellen.
[Bearbeiten]Prellen
Das Prellen entsteht in der Mechanik der Tasten: Eine Kontaktfeder wird durch das Drcken des
Tastelements auf einen anderen Kontakt gedrckt. Wenn die Kontaktfeder das Kontaktfeld
berhrt, federt sie jedoch nach. Dies kann soweit gehen, dass die Feder wieder vom Feld
abhebt und den elektrischen Kontakt kurzzeitig wieder unterbricht. Auch wenn diese Effekte
sehr kurz sind, sind sie fr einen Mikrocontroller viel zu lang. Fr ihn sieht die Situation so aus,
dass beim Drcken der Taste eine Folge von: Taste geschlossen, Taste offen, Taste
geschlossen, Taste offen Ereignissen am Port sichtbar sind, die sich dann nach einiger Zeit auf
den Zustand Taste geschlossen einpendelt. Beim Loslassen der Taste dann dasselbe Spielchen
in der umgekehrten Richtung.
Nun kann es natrlich sein, dass ein neuer Taster zunchst berhaupt nicht prellt. Ist der Taster
vom Hersteller nicht explizit als 'prellfreier Taster' verkauft worden, besteht aber kein Grund zur
Freude. Auch wenn der Taster heute noch nicht prellt, irgendwann wird er es tun. Dann nmlich,
wenn die Kontaktfeder ein wenig ihrer Spannung verliert und ausleiert, bzw. wenn sich die
Kontaktflchen durch hufige Benutzung abgewetzt haben.
[Bearbeiten]Entprellung
Aus diesem Grund mssen Tasten entprellt werden. Im Prinzip kann eine Entprellung sehr
einfach durchgefhrt werden. Ein 'Tastendruck' wird nicht bei der Erkennung der ersten Flanke
akzeptiert, sondern es wird noch eine zeitlang gewartet. Ist nach Ablauf dieser Zeitdauer die
Taste immer noch gedrckt, dann wird diese Flanke als Tastendruck akzeptiert und
ausgewertet.
.include "m8def.inc"
.def key_old
.def key_now
= r3
= r4
.def temp1
.def temp2
= r17
= r18
.equ key_pin
.equ key_port
.equ key_ddr
= PIND
= PORTD
= DDRD
.equ led_port
.equ led_ddr
.equ LED
= PORTB
= DDRB
= 0
temp1, 1<<LED
led_ddr, temp1
ldi
out
ldi
out
temp1, $00
key_ddr, temp1
temp1, $FF
key_port, temp1
mov
key_old, temp1
in
mov
eor
mov
key_now, key_pin
temp1, key_now
key_now, key_old
key_old, temp1
;
;
;
;
;
loop:
ldi
out
breq loop
and
brne loop
ldi
temp1, $FF
ldi
temp2, $FF
wait1:
wait2:
temp1, key_now
dec temp2
brne wait2
dec temp1
brne wait1
in
temp1, key_pin
and temp1, key_now
brne loop
in
com
out
rjmp
temp1, led_port
temp1
led_port, temp1
loop
Wie lange gewartet werden muss, hngt im wesentlichen von der mechanischen Qualitt und
dem Zustand des Tasters ab. Neue und qualitativ hochwertige Taster prellen wenig, ltere
Taster prellen mehr. Grundstzlich prellen aber alle mechanischen Taster irgendwann. Man
sollte nicht dem Trugschluss verfallen, da ein Taster nur weil er heute nicht erkennbar prellt,
dieses auch in einem halben Jahr nicht tut.
[Bearbeiten]Kombinierte
Von Herrn Peter Dannegger stammt eine clevere Routine, die mit wenig Aufwand an einem Port
gleichzeitig bis zu 8 Tasten erkennen und zuverlssig entprellen kann. Dazu wird ein Timer
benutzt, der mittels Overflow-Interrupt einen Basistakt erzeugt. Die Zeitdauer von einem
Interrupt zum nchsten ist dabei ziemlich unkritisch. Sie sollte sich im Bereich von 5 bis 50
Millisekunden bewegen.
In jedem Overflow Interrupt wird der jetzt am Port anliegende Tastenzustand mit dem Zustand
im letzten Timer Interrupt verglichen. Nur dann wenn an einem Pin eine nderung festgestellt
werden kann (Flankenerkennung) wird dieser Tastendruck zunchst registriert. Ein clever
aufgebauter Zhler zhlt danach die Anzahl der Timer Overflows mit, die die Taste nach
Erkennung der Flanke im gedrckten Zustand verharrte. Wurde die Taste nach Erkennung der
Flanke 4 mal hintereinander als gedrckt identifiziert, so wird der Tastendruck weitergemeldet.
Die 4 mal sind relativ willkrlich und so gewhlt, dass man einen Zhler leicht aufbauen kann.
Wird die Interrupt Routine also alle 5 Millisekunden aufgerufen, dann muss die Taste bei 4
Stichproben hintereinander durchgehend gedrckt worden sein. Prellt die Taste in dieser Zeit,
dann wird der Zhler einfach auf 0 zurckgesetzt und die Wartezeit beginnt erneut zu laufen.
Sptestens 20 Millisekunden nach dem letzten Tastenpreller vermeldet daher diese Routine
einen Tastendruck, der dann ausgewertet werden kann.
[Bearbeiten]Einfache Tastenentprellung und Abfrage
.include "m8def.inc"
.def iwr0
.def iwr1
= r1
= r2
.def key_old
= r3
.def key_state = r4
.def key_press = r5
.def temp1
= r17
.equ key_pin
.equ key_port
.equ key_ddr
= PIND
= PORTD
= DDRD
.def leds
.equ led_port
.equ led_ddr
= r16
= PORTB
= DDRB
.org 0x0000
rjmp
init
.org OVF0addr
rjmp
timer_overflow0
timer_overflow0:
push
in
push
push
push
get8key:
mov
in
eor
com
mov
or
and
eor
and
or
;
;
pop
pop
pop
out
pop
reti
init:
ldi
out
ldi
out
r0
r0, SREG
r0
iwr0
iwr1
iwr0, key_old
key_old, key_pin
iwr0, key_old
key_old
iwr1, key_state
key_state, iwr0
iwr0, key_old
key_state, iwr0
iwr1, iwr0
key_press, iwr1
iwr1
iwr0
r0
SREG, r0
r0
;/old
state
iwr1
;00110011 10101010
;11110000
;
;00001111
;
10101010
;
11101011
;
;
11101000
;
00000010
; gedrckte Taste merken
iwr0
00110011
11000011
00000011
; Register wiederherstellen
temp1, HIGH(RAMEND)
SPH, temp1
temp1, LOW(RAMEND)
SPL, temp1
ldi
out
temp1, 0xFF
led_ddr, temp1
ldi
out
temp1, 0xFF
key_port, temp1
ldi
out
ldi
out
temp1,
TCCR0,
temp1,
TIMSK,
; Stackpointer initialisieren
1<<CS02 | 1<<CS00
temp1
1<<TOIE0
temp1
clr
clr
clr
key_old
key_state
key_press
sei
ldi
out
main:
cli
mov
clr
sei
;
; Einen ev. Tastendruck merken und ...
; Tastendruck zurcksetzen
cpi
breq
temp1, 0
main
eor
out
rjmp
leds, temp1
led_port, leds
main
= r1
= r2
.def
.def
.def
.def
=
=
=
=
key_state
key_press
key_rep_press
key_rep
r4
r5
r6
r16
.def temp1
= r17
.equ KEY_PIN
.equ KEY_PORT
.equ KEY_DDR
= PIND
= PORTD
= DDRD
.equ KEY_REPEAT_START = 50
.equ KEY_REPEAT_NEXT = 15
.def leds
.equ led_port
.equ led_ddr
.equ XTAL
= r20
= PORTB
= DDRB
rjmp
= 4000000
init
.org OVF0addr
rjmp
timer_overflow0
timer_overflow0:
push
in
push
r0
r0, SREG
r0
push
; TCNT0
ldi
;
;
;
;
;
out
pop
r16
so vorladen, dass der nchste Overflow nach 10 ms auftritt.
r16, -( XTAL / 1024 * 10 / 1000)
^
^
^^^^^^^^^
|
|
= 10 ms
|
Vorteiler
Quarz-Takt
get8key:
in
com
eor
and
com
and
eor
and
and
eor
and
or
tst
breq
dec
brne
mov
ldi
rjmp
TCNT0, r16
r16
r0, KEY_PIN
; Tasten einlesen
r0
; gedrckte Taste werden zu 1
r0, key_state
; nur nderunden bercksichtigen
iwr0, r0
; in iwr0 und iwr1 zhlen
iwr0
iwr1, r0
iwr1, iwr0
r0, iwr0
r0, iwr1
key_state, r0
;
r0, key_state
key_press, r0
; gedrckte Taste merken
key_state
; irgendeine Taste gedrckt ?
get8key_rep
; Nein, Zeitdauer zurcksetzen
key_rep
get8key_finish;
; Zeit abgelaufen?
key_rep_press, key_state
key_rep, KEY_REPEAT_NEXT
get8key_finish
get8key_rep:
ldi
key_rep, KEY_REPEAT_START
get8key_finish:
pop
r0
out
SREG, r0
pop
r0
reti
; Register wiederherstellen
;
;
init:
ldi
out
ldi
out
temp1, HIGH(RAMEND)
SPH, temp1
temp1, LOW(RAMEND)
SPL, temp1
ldi
out
temp1, 0xFF
led_ddr, temp1
ldi
out
temp1, 0xFF
KEY_PORT, temp1
ldi
out
ldi
out
temp1,
TCCR0,
temp1,
TIMSK,
clr
clr
clr
clr
key_state
key_press
key_rep_press
key_rep
ldi
out
leds, 0xFF
led_port, leds
; Stackpointer initialisieren
1<<CS00 | 1<<CS02
temp1
1<<TOIE0
; Timer mit Vorteiler 1024
temp1
main:
; einen einzelnen Tastendruck auswerten
cli
mov
clr
sei
temp1, key_press
key_press
cpi
breq
temp1, 0x01
toggle
cli
mov
clr
sei
temp1, key_rep_press
key_rep_press
cpi
breq
temp1, 0x01
toggle
rjmp
main
; Hauptschleife abgeschlossen
leds, temp1
led_port, leds
main
toggle:
eor
out
rjmp
[Bearbeiten]Fallbeispiel
Das folgende Programm hat durchaus praktischen Wert. Es zeigt auf dem LCD den ASCII Code
dezimal und in hexadezimal an, sowie das zugehrige LCD-Zeichen. An den PORTD werden an
den Pins 0 und 1 jeweils 1 Taster angeschlossen. Mit dem einen Taster kann der ASCII Code
erhht werden, mit dem anderen Taster wird der ASCII Code erniedrigt. Auf beiden Tastern liegt
jeweils ein Autorepeat, sodass jeder beliebige Code einfach angesteuert werden kann.
Insbesondere die ASCII Codes grer als 128 sind interessant :-)
.include "m8def.inc"
.def iwr0
.def iwr1
= r1
= r2
.def
.def
.def
.def
=
=
=
=
key_state
key_press
key_rep_press
key_rep
r4
r5
r6
r16
.def temp1
= r17
.equ KEY_PIN
.equ KEY_PORT
.equ KEY_DDR
= PIND
= PORTD
= DDRD
.equ KEY_REPEAT_START = 40
.equ KEY_REPEAT_NEXT = 15
.def code
= r20
init
.org OVF0addr
rjmp
timer_overflow0
timer_overflow0:
push
in
push
r0
r0, SREG
r0
push
ldi
out
pop
r16
r16, -( XTAL / 1024 * 10 / 1000 + 1 )
TCNT0, r16
r16
get8key:
in
com
eor
and
com
and
eor
and
and
eor
and
or
tst
breq
dec
brne
r0, KEY_PIN
r0
r0, key_state
iwr0, r0
iwr0
iwr1, r0
iwr1, iwr0
r0, iwr0
r0, iwr1
key_state, r0
r0, key_state
key_press, r0
key_state
get8key_rep
key_rep
get8key_finish;
;
;
;
;
Tasten einlesen
gedrckte Taste werden zu 1
nur nderunden bercksichtigen
in iwr0 und iwr1 zhlen
;
; gedrckte Taste merken
; irgendeine Taste gedrckt ?
; Nein, Zeitdauer zurcksetzen
; Zeit abgelaufen?
mov
ldi
rjmp
key_rep_press, key_state
key_rep, KEY_REPEAT_NEXT
get8key_finish
get8key_rep:
ldi
key_rep, KEY_REPEAT_START
get8key_finish:
pop
r0
out
SREG, r0
pop
r0
reti
;
;
init:
ldi
out
ldi
out
ldi
out
rcall
rcall
; Register wiederherstellen
temp1, HIGH(RAMEND)
SPH, temp1
temp1, LOW(RAMEND)
SPL, temp1
temp1, 0xFF
KEY_PORT, temp1
temp1,
TCCR0,
temp1,
TIMSK,
clr
clr
clr
clr
key_state
key_press
key_rep_press
key_rep
ldi
rjmp
code, 0x30
update
cli
mov
clr
sei
cpi
breq
cpi
breq
rjmp
lcd_init
lcd_clear
ldi
out
ldi
out
main:
cli
mov
clr
sei
cpi
breq
cpi
breq
; Stackpointer initialisieren
1<<CS00 | 1<<CS02
temp1
1<<TOIE0
; Timer mit Vorteiler 1024
temp1
temp1, key_press
key_press
temp1, 0x01
increment
temp1, 0x02
decrement
; normaler Tastendruck
; Increment
; Decrement
; gedrckt und halten -> repeat
temp1, key_rep_press
key_rep_press
temp1, 0x01
increment
temp1, 0x02
decrement
main
; Increment
; Decrement
increment:
inc
rjmp
code
update
decrement:
dec
code
update:
rcall
mov
rcall
ldi
rcall
mov
rcall
ldi
rcall
mov
rcall
lcd_home
temp1, code
lcd_number
temp1, ' '
lcd_data
temp1, code
lcd_number_hex
temp1, ' '
lcd_data
temp1, code
lcd_data
rjmp
main
.include "lcd-routines.asm"
[Bearbeiten]Weblinks
10 Keys on One Port Pin? - Eine ganz andere Art der Tastenerkennung ber
einen ADC. Damit lsst sich auch eine von vielen Tasten an nur einem ADCEingangspin abfragen.
Input Matrix Scanning von Open Music Labs. Tutorials und Kostenbetrachtung fr 12
Verfahren, um eine Eingabematrix (1-128 Schalter an 1-20 Pins) abzufragen.
AVR-Tutorial: PWM
PWM - Dieses Krzel steht fr Puls Weiten Modulation.
Inhaltsverzeichnis
[Verbergen]
1 Was bedeutet PWM?
2 PWM und der Timer
o
3.1 Prinzip
3.2 Programm
4 Siehe auch
[Bearbeiten]Was
bedeutet PWM?
Viele elektrische Verbraucher knnen in ihrer Leistung reguliert werden, indem die
Versorgungsspannung in weiten Bereichen verndert wird. Ein normaler Gleichstrommotor wird
z. B. langsamer laufen, wenn er mit einer geringeren Spannung versorgt wird, bzw. schneller
laufen, wenn er mit einer hheren Spannung versorgt wird. LEDs werden zwar nicht mit einer
Spannung gedimmt, sondern mit dem Versorgungsstrom. Da dieser Stromfluss aber im
Normalfall mit einem Vorwiderstand eingestellt wird, ist durch das Ohmsche Gesetz dieser
Stromfluss bei konstantem Widerstand wieder direkt proportional zur Hhe der
Versorgungsspannung.
Im wesentlichen geht es also immer um diese Kennlinie, trgt man die Versorgungsspannung
entlang der Zeitachse auf:
Die Flche unter der Kurve ist dabei ein direktes Ma fr die Energie die dem System zugefhrt
wird. Bei geringerer Energie ist die Helligkeit geringer, bei hherer Energie entsprechend heller.
Jedoch gibt es noch einen zweiten Weg, die dem System zugefhrte Energie zu verringern.
Anstatt die Spannung abzusenken, ist es auch mglich die volle Versorgungsspannung ber
einen geringeren Zeitraum anzulegen. Man mu nur dafr Sorge tragen, dass im Endeffekt die
einzelnen Pulse nicht mehr wahrnehmbar sind.
Die Flche unter den Rechtecken hat in diesem Fall dieselbe Gre wie die Flche unter der
Spannung V=, glttet man die Spannung also mit einem Kondensator, ergibt sich eine
niedrigere konstante Spannung. Die Rechtecke sind zwar hher, aber dafr schmaler. Die
Flchen sind aber dieselben. Diese Lsung hat den Vorteil, dass keine Spannung geregelt
werden muss, sondern der Verbraucher immer mit derselben Spannung versorgt wird.
Und genau das ist das Prinzip einer PWM. Durch die Abgabe von Pulsen wird die abgegebene
Energiemenge gesteuert. Es ist auf einem C wesentlich einfacher Pulse mit einem definiertem
Puls/Pausen Verhltnis zu erzeugen als eine Spannung zu variieren.
[Bearbeiten]PWM
Der Timer1 des Mega8 untersttzt direkt das Erzeugen von PWM. Beginnt der Timer
beispielsweise bei 0 zu zhlen, so schaltet er gleichzeitig einen Ausgangspin ein. Erreicht der
Zhler einen bestimmten Wert X, so schaltet er den Ausgangspin wieder aus und zhlt weiter
bis zu seiner Obergrenze. Danach wiederholt sich das Spielchen. Der Timer beginnt wieder bei
0 und schaltet gleichzeitig den Ausgangspin ein etc. Durch Verndern von X kann man daher
steuern, wie lange der Ausgangspin im Verhltnis zur kompletten Zeit, die der Timer bentigt,
um seine Obergrenze zu erreichen, eingeschaltet ist.
Dabei gibt es aber verwirrenderweise verschiedene Arten der PWM:
Fast PWM
Phasen-korrekte PWM
[Bearbeiten]Fast PWM
Die Fast PWM gibt es beim Mega8 mit mehreren unterschiedlichen Bit-Zahlen. Bei den BitZahlen geht es immer darum, wie weit der Timer zhlt, bevor ein Rcksetzen des Timers auf 0
erfolgt
Modus 14: Fast PWM mit beliebiger Schrittzahl (festgelegt durch ICR1)
Modus 15: Fast PWM mit beliebiger Schrittzahl (festgelegt durch OCR1A)
Grundstzlich funktioniert der Fast-PWM Modus so, dass der Timer bei 0 anfngt zu zhlen,
wobei natrlich der eingestellte Vorteiler des Timers bercksichtigt wird. Erreicht der Timer
einen bestimmten Zhlerstand (festgelegt durch die Register OCR1A und OCR1B) wird eine
Aktion ausgelst. Je nach Festlegung kann der entsprechende C Pin (OC1A und OC1B)
entweder
umgeschaltet
auf 1 gesetzt
auf 0 gesetzt
werden. Wird der OC1A/OC1B Pin so konfiguriert, dass er auf 1 oder 0 gesetzt wird, so wird
automatisch der entsprechende Pin beim Timerstand 0 auf den jeweils gegenteiligen Wert
gesetzt.
Der OC1A Pin befindet sich beim Mega8 am Port B, konkret am Pin PB1. Dieser Pin muss ber
das zugehrige Datenrichtungsregister DDRB auf Ausgang gestellt werden. Anders als beim
UART geschieht dies nicht automatisch.
Das Beispiel zeigt den Modus 14. Dabei wird der Timer-Endstand durch das
Register ICR1 festgelegt. Des Weiteren wird die Funktion des OC1A Pins so festgelegt, dass
der Pin bei einem Timer Wert von 0 auf 1 gesetzt wird und bei Erreichen des
im OCR1A Registers festgelegten Wertes auf 0 gesetzt wird. Der Vorteiler des Timers, bzw. der
ICR-Wert wird zunchst so eingestellt, dass eine an PB1 angeschlossene LED noch blinkt, die
Auswirkungen unterschiedlicher Register Werte gut beobachtet werden knnen. Den Vorteiler
zu verringern ist kein Problem, hier geht es aber darum, zu demonstrieren wie PWM
funktioniert.
Hinweis: Wie berall im ATMega8 ist darauf zu achten, dass beim Beschreiben eines 16-Bit
Registers zuerst das High-Byte und dann das Low-Byte geschrieben wird.
.include "m8def.inc"
.def temp1
= r17
init
;.include "keys.asm"
;
;
init:
ldi
out
ldi
out
temp1, HIGH(RAMEND)
SPH, temp1
temp1, LOW(RAMEND)
SPL, temp1
; Stackpointer initialisieren
;
; Timer 1 einstellen
;
; Modus 14:
;
Fast PWM, Top von ICR1
;
;
WGM13
WGM12
WGM11
WGM10
;
1
1
1
0
;
;
Timer Vorteiler: 256
;
CS12
CS11
CS10
;
1
0
0
;
; Steuerung des Ausgangsport: Set at BOTTOM, Clear at match
;
COM1A1
COM1A0
;
1
0
;
ldi
out
ldi
out
TCCR1B, temp1
;
; den Endwert (TOP) fr den Zhler setzen
; der Zhler zhlt bis zu diesem Wert
;
ldi
temp1, 0x6F
out
ICR1H, temp1
ldi
temp1, 0xFF
out
ICR1L, temp1
;
; der Compare Wert
; Wenn der Zhler diesen Wert erreicht, wird mit
; obiger Konfiguration der OC1A Ausgang abgeschaltet
; Sobald der Zhler wieder bei 0 startet, wird der
; Ausgang wieder auf 1 gesetzt
;
ldi
temp1, 0x3F
out
OCR1AH, temp1
ldi
temp1, 0xFF
out
OCR1AL, temp1
; Den Pin OC1A zu guter letzt noch auf Ausgang schalten
ldi
temp1, 0x02
out
DDRB, temp1
main:
rjmp
main
Wird dieses Programm laufen gelassen, dann ergibt sich eine blinkende LED. Die LED ist die
Hlfte der Blinkzeit an und in der anderen Hlfte des Blinkzyklus aus. Wird der Compare Wert
in OCR1Averndert, so lsst sich das Verhltnis von LED Einzeit zu Auszeit verndern. Ist die
LED wie im I/O Kapitel angeschlossen, so fhren hhere OCR1A Werte dazu, dass die LED nur
kurz aufblitzt und in der restlichen Zeit dunkel bleibt.
ldi
out
ldi
out
temp1, 0x6D
OCR1AH, temp1
temp1, 0xFF
OCR1AL, temp1
Sinngem fhren kleinere OCR1A Werte dazu, da die LED lnger leuchtet und die
Dunkelphasen krzer werden.
ldi
out
ldi
out
temp1, 0x10
OCR1AH, temp1
temp1, 0xFF
OCR1AL, temp1
Nachdem die Funktion und das Zusammenspiel der einzelnen Register jetzt klar ist, ist es Zeit
aus dem Blinken ein echtes Dimmen zu machen. Dazu gengt es den Vorteiler des Timers auf 1
zu setzen:
ldi
out
Werden wieder die beiden OCR1A Werte 0x6DFF und 0x10FF ausprobiert, so ist deutlich zu
sehen, dass die LED scheinbar unterschiedlich hell leuchtet. Dies ist allerdings eine optische
Tuschung. Die LED blinkt nach wie vor, nur blinkt sie so schnell, da dies fr uns nicht mehr
wahrnehmbar ist. Durch Variation der Einschalt- zu Ausschaltzeit kann die LED auf viele
verschiedene Helligkeitswerte eingestellt werden.
Theoretisch wre es mglich die LED auf 0x6FFF verschiedene Helligkeitswerte einzustellen.
Dies deshalb, weil in ICR1 genau dieser Wert als Endwert fr den Timer festgelegt worden ist.
Dieser Wert knnte genauso gut kleiner oder grer eingestellt werden. Um eine LED zu
dimmen ist der Maximalwert aber hoffnungslos zu hoch. Fr diese Aufgabe reicht eine
Abstufung von 256 oder 512 Stufen normalerweise vllig aus. Genau fr diese Flle gibt es die
anderen Modi. Anstatt den Timer Endstand mittels ICR1 festzulegen, gengt es den Timer
einfach nur in den 8, 9 oder 10 Bit Modus zu konfigurieren und damit eine PWM mit 256 (8 Bit),
512 (9 Bit) oder 1024 (10 Bit) Stufen zu erzeugen.
[Bearbeiten]PWM
in Software
Die Realisierung einer PWM mit einem Timer, wobei der Timer die ganze Arbeit macht, ist zwar
einfach, hat aber einen Nachteil. Fr jede einzelne PWM ist ein eigener Timer notwendig
(Ausnahme: Der Timer 1 besitzt 2 Compare Register und kann damit 2 PWM Stufen erzeugen).
Und davon gibt es in einem Mega8 nicht all zu viele.
Es geht auch anders: Es ist durchaus mglich viele PWM Stufen mit nur einem Timer zu
realisieren. Der Timer wird nur noch dazu bentigt, eine stabile und konstante Zeitbasis zu
erhalten. Von dieser Zeitbasis wird alles weitere abgeleitet.
[Bearbeiten]Prinzip
Das Grundprinzip ist dabei sehr einfach: Eine PWM ist ja im Grunde nichts anderes als eine
Blinkschleife, bei der das Verhltnis von Ein- zu Auszeit variabel eingestellt werden kann. Die
Blinkfrequenz selbst ist konstant und ist so schnell, dass das eigentliche Blinken nicht mehr
wahrgenommen werden kann. Das lsst sich aber auch alles in einer ISR realisieren:
Ein Timer (Timer0) wird so aufgesetzt, dass er eine Overflow-Interruptfunktion (ISR) mit
dem 256-fachen der gewnschten Blinkfrequenz aufruft.
In der ISR wird ein weiterer Zhler betrieben (PWMCounter), der stndig von 0 bis 255
zhlt.
Fr jede zu realisierende PWM Stufe gibt es einen Grenzwert. Liegt der Wert des
PWMCounters unter diesem Wert, so wird der entsprechende Port Pin eingeschaltet.
Liegt er darber, so wird der entsprechende Port Pin ausgeschaltet
Damit wird im Grunde nichts anderes gemacht, als die Funktionalitt der Fast-PWM in Software
nachzubilden. Da man dabei aber nicht auf ein einziges OCR Register angewiesen ist, sondern
in gewissen Umfang beliebig viele davon implementieren kann, kann man auch beliebig viele
PWM Stufen erzeugen.
[Bearbeiten]Programm
Am Port B werden an den Pins PB0 bis PB5 insgesamt 6 LEDs gem der Verschaltung aus
dem I/O Artikel angeschlossen. Jede einzelne LED kann durch Setzen eines Wertes von 0 bis
127 in die zugehrigen Register ocr_1 bis ocr_6 auf einen anderen Helligkeitswert eingestellt
werden. Die PWM-Frequenz (Blinkfrequenz) jeder LED betrgt: ( 4000000 / 256 ) / 127 =
123Hz. Dies reicht aus, um das Blinken unter die Wahrnehmungsschwelle zu drcken und die
LEDs gleichmssig erleuchtet erscheinen zu lassen.
.include "m8def.inc"
.def temp
= r16
ocr_1
ocr_2
ocr_3
ocr_4
ocr_5
ocr_6
=
=
=
=
=
=
.org 0x0000
rjmp
.org OVF0addr
rjmp
r18
r19
r20
r21
r22
r23
main:
;
;
;
;
;
;
Helligkeitswert
Helligkeitswert
Helligkeitswert
Helligkeitswert
Helligkeitswert
Helligkeitswert
Led1:
Led2:
Led3:
Led4:
Led5:
Led6:
0
0
0
0
0
0
..
..
..
..
..
..
main
; Reset Handler
timer0_overflow
ldi
out
ldi
out
temp, LOW(RAMEND)
SPL, temp
temp, HIGH(RAMEND)
SPH, temp
; Stackpointer initialisieren
ldi
out
temp, 0xFF
DDRB, temp
ldi
ldi
ldi
ldi
ldi
ldi
ocr_1,
ocr_2,
ocr_3,
ocr_4,
ocr_5,
ocr_6,
ldi
out
temp, 1<<CS00
TCCR0, temp
ldi
out
temp, 1<<TOIE0
TIMSK, temp
0
1
10
20
80
127
sei
loop:
127
127
127
127
127
127
rjmp
loop
timer0_overflow:
inc
PWMCount
cpi
PWMCount, 128
brne
WorkPWM
clr
PWMCount
WorkPWM:
ldi
temp, 0b11000000
cp
brlo
ori
PWMCount, ocr_1
OneOn
temp, $01
OneOn:
cp
brlo
ori
PWMCount, ocr_2
TwoOn
temp, $02
TwoOn:
cp
brlo
ori
PWMCount, ocr_3
ThreeOn
temp, $04
ThreeOn:cp
brlo
ori
PWMCount, ocr_4
FourOn
temp, $08
FourOn: cp
brlo
ori
PWMCount, ocr_5
FiveOn
temp, $10
FiveOn: cp
brlo
ori
PWMCount, ocr_6
SetBits
temp, $20
SetBits:
out
PORTB, temp
reti
Wrde man die LEDs anstatt direkt an einen Port anzuschliessen, ber ein oder
mehrere Schieberegister anschlieen, so kann auf diese Art eine relativ groe Anzahl an LEDs
gedimmt werden. Natrlich msste man die softwareseitige LED Ansteuerung gegenber der
hier gezeigten verndern, aber das PWM Prinzip knnte so bernommen werden.
[Bearbeiten]Siehe
auch
PWM
AVR_PWM
Pulsweitenmodulation
Inhaltsverzeichnis
[Verbergen]
1 Einleitung
2 Beispiele
3 Leistung
3.1 Beispiel
4 Anwendung
o
4.2 Motorsteuerung
5.2 Wie schtze ich die Verlustleistung am MOSFET im PWM Betrieb ab?
6 Siehe auch
7 Weblinks
[Bearbeiten]Einleitung
Bei der Pulsweitenmodulation (engl. Pulse Width Modulation, abgekrzt PWM) wird die Einund Ausschaltzeit eines Rechtecksignals bei fester Grundfrequenz variiert. Das Verhltnis tein /
(tein +taus) bezeichnet man als Tastverhltnis (laut DIN IEC 60469-1: Tastgrad) (engl. Duty Cycle,
meist abgekrzt DC, bitte nicht verwechseln mit Direct Current = Gleichstrom ). Das
Tastverhltnis ist eine Zahl zwischen 0..1.
Wie leicht zu erkennen ist gilt fr den Mittelwert der Spannung mit der Periode tein + taus = T:
Uaus ist dabei normalerweise 0V, Uein die Betriebsspannung VCC, z. B. 5V. Deshalb kann man
vereinfacht schreiben:
.
[Bearbeiten]Beispiele
Die folgenden Beispiele zeigen PWM-Signale mit einem Tastverhltnis von 75% bzw. 25%.
Beispiel 1
Beispiel 2
[Bearbeiten]Leistung
Steuert man mit einem pulsweitenmodulierten Signal direkt einen ohmschen Verbraucher an
(z. B. Heizdraht), so ist darauf zu achten, dass man zur Bestimmung der Leistung nicht einfach
rechnen darf, sondern die Leistung whrend der Ein- und Ausschaltzeit getrennt betrachten
muss:
.
Da praktisch fast immer gilt Uaus = 0V sowie Uein = VCC
kann man vereinfacht schreiben und damit rechnen.
.
[Bearbeiten]Beispiel
Wrde man mit diesem Wert die Leistung berechnen, so kme man auf
[Bearbeiten]Anwendung
[Bearbeiten]Digitaler Verstrker statt linearer Verstrker
Eine Heizung (Beispiel) mit 10-Widerstand soll mit bis zu 12 V angesteuert werden. Dazu wird
ein 13 V-Netzteil sowie ein linearer Verstrker verwendet (ein linearer Verstrker braucht immer
eine etwas hhere Betriebsspannung als die maximale Ausgangsspannung).
Sollen nun 12 V auf die Heizung gegeben werden, fllt (fast) die gesamte Spannung ber der
Heizung selber ab, der Verstrker "verbraucht" nur 1 V. Es flieen ca. 1,2 A, es werden ca. 14,4
W in der Heizung in Wrme umgesetzt, im Verstrker ca. 1,2 W, der Wirkungsgrad betrgt 92%.
Wenn jetzt aber nur noch 6 V an der Heizung anliegen sollen, muss der lineare Verstrker die
"brigen" 7 V verbrauchen, d.h. von den 13 V, welche konstant vom Netzteil geliefert werden,
fallen 7 V ber dem Verstrker und 6 V ber der Heizung ab. Die Transistoren des linearen
Verstrkers sind nur halb durchgesteuert. Es fliet ein Strom von ca. 600 mA, in der Heizung
werden ca. 3,6 W in Wrme umgesetzt. Allerdings werden auch 4,2 W im Verstrker in Wrme
umgesetzt! Der Wirkungsgrad ist nur noch 46%!
Im Gegensatz dazu sind bei einer PWM die Transistoren des digitalen Verstrkers immer nur
entweder voll durchgesteuert oder gar nicht durchgesteuert. Im ersteren Fall fllt nur eine
geringe Verlustleistung ber dem Transistor ab, da die Sttigungsspannung VSAT sehr gering ist
(meist weniger als 1 V). Im zweiten Fall fllt gar keine Verlustleistung ber dem Transistor ab,
da kein Strom fliet (P=U*I). Im Fall der 6 V an der Heizung betrgt das notwendige
Tastverhltnis 0,23. D.h. nur whrend 23% der PWM-Periode wird Verlustleistung im digitalen
Verstrker erzeugt und zwar ca.
[Bearbeiten]Motorsteuerung
Eine der Hauptanwendungen fr PWM ist die Ansteuerung von (Gleichstrom-) Motoren. Der
groe Vorteil von PWM ist hier der gute Wirkungsgrad. Wrde man einen Digital-AnalogWandler mit einem nachgeschalteten analogen Verstrker zur Ansteuerung verwenden, dann
wrde im Verstrker eine sehr hohe Verlustleistung in Wrme umgewandelt werden. Ein
digitaler Verstrker mit PWM hat dagegen sehr geringe Verluste. Die verwendete Frequenz liegt
meist im Bereich von einigen 10kHz. Zur Berechnung der Drehzahl eines Motors kann im
Normalfall der Mittelwert der PWM-Spannung als Betriebsspannung angenommen werden.
[Bearbeiten]AD-Wandlung mit PWM
Der folgende Tipp stammt noch aus der Zeit, als es keinen Mikroprozessor mit AD-Wandler gab.
Einen recht billigen und einfachen AD-Wandler mit "1-Draht Kommunikation" kann man mit dem
IC 556 (NE556 o..) realisieren: der eine Timer des 556 arbeitet als 50% duty-cycle
Rechteckgenerator bei beispielsweise 1 kHz und steuert den zweiten Timer an. Dieser besitzt
einen Steuereingang zu Beeinflussung des Tastverhltnisses und auf diesen Pin gibt man das
analoge Signal. Ein angeschlossener C oder PC misst bei jedem Impuls die Impulslnge und
man erhlt so das Messergebnis. Bei einer Frequenz von >10 kHz liesse sich sogar Sprache
digital bertragen oder speichern. Allerdings ist dafr eine Auflsung von wenigstens 8 Bit ntig,
wodurch 256 Stufen und eine entsprechemde Abstatfrequenz durch den Chip gefordert sind.
Ohne Chip lsst sich dies nur mit eimem Logikbaustein und etwas Signalverarbeitung lsen,
siehe Analog-IO_mit_digitalen_Bausteinen.
[Bearbeiten]DA-Wandlung mit PWM
Die meisten Mikrocontroller haben keine DA-Wandler integriert, da diese relativ aufwndig sind.
Allerdings kann man mittels eines PWM-Ausgangs auch eine DA-Wandlung vornehmen und
eine Gleichspannung bereitstellen. Wird ein PWM-Signal ber einen Tiefpass gefiltert
(geglttet), entsteht eine Gleichspannung mit Wechselanteil, deren Mittelwert dem des PWMSignals entspricht und dessen Wechselanteil von der Beschaltung abhngig ist. Nun bleibt das
Problem der Dimensionierung des Tiefpasses. Ein Beispiel:
PWM-Takt 1 MHz, 8 Bit Auflsung (256 Stufen), 0/5V. -> 3906 Hz PWM Frequenz
RC-Tiefpass 22nF, 100k -> 72 Hz Grenzfrequenz
.)
Bei diesem Tiefpass mit 72 Hz Bandbreite verbleibt am Ausgang noch ein Ripple auf der
Gleichspannung, da die PWM nie ideal gefiltert werden kann. Eine Rechnung bzw. Simulation in
PSPICE zeigen ca. 150mV Ripple. Das ist ziemlich viel, da ein idealer 8-Bit DA-Wandler bei 5V
Referenzspannung eine Auflsung von 20mV hat. Wir haben hier also ein Strsignal von
150mV/20mv=7,5 LSB. Um den Ripple bis auf die Auflsungsgrenze von 20mV zu reduzieren,
muss die Grenzfrequenz auf ca. 10 Hz reduziert werden. Es ist somit effektiv nur ein 390tel der
PWM-Frequenz nutzbar. Das ist fr einige Anwendungen ausreichend, wo praktisch nur
statische Gleichspannungen erzeugt werden sollen, z. B. fr programierbare Netzteile. Fr
Anwendungen, in denen schneller ndernde Gleichspannungen generiert werden sollen, muss
die PWM-Frequenz entsprechend erhht werden oder ein steilerer Tiefpa verwendet werden.
[Bearbeiten]RC-Filter
dimensionieren
ber die Definition des Kondensators kann man den Ripple berechnen.
Die Ladung in As (Amperesekunden) ergeben sich aus der halben PWM-Periode mal I. Damit
kann man brauchbar den Ripple abschtzen.
Die Einschwingzeit
Die Abschtzung gilt aber nur dann, wenn der Ausgang des RC-Filter kaum belastet ist, wie
z. B. durch einen Operationsverstrker oder einen andern hochohmigen IC-Eingang.
Beispiel:
100 Hz PWM Frequenz(T_PWM=10ms), R=100k, C=1F, Vcc=5V
Will man aber nicht soviel Bandbreite verschenken, muss man anders filtern. Das Problem des
einfachen RC-Tiefpasses ist der relativ langsame Anstieg der Dmpfung oberhalb der
Grenzfrequenz. Genauer gesagt steigt die Dmpfung mit 20dB/Dekade. Das heisst, dass ein
Signal mit der 10fachen Frequenz (Dekade) um den Faktor 10 (20dB) gedmpft wird. Will man
nun eine hhere Dmpfung ereichen, mssen mehrere Tiefpsse in Kette geschaltet werden.
Bei dem gleichen Beispiel erreicht man mit zwei Tiefpssen mit 6,8nF/100k eine
Grenzfrequenz von ca. 70 Hz, bei gleicher Dmpfung des Ripples auf 20mV. Die Dmpfung
dieses sogenannten Tiefpasses 2. Ordnung betrgt 40dB/Dekade. Das heisst, ein Signal mit
zehnfacher Frequenz (Dekade) wird um den Faktor 100 (40dB) gedmpft! Damit erzielt man
hier bereits die 7fache Bandbreite! Zum Schluss muss beachtet werden, dass die passiven
Tiefpsse nur sehr schwach belastet werden knnen. Hier ist fast immer ein
Operationsverstrker als Spannungsfolger ntig. Der kann auch genutzt werden, um das
gefilterte Signal weiter zu verstrken (nichtinvertierender Verstrker).
Mehr Informationen zur Restwelligkeit bei RC Tiefpssen kann man diesem Thread entnehmen.
Das Spiel kann noch um einiges gesteigert werden, wenn man Tiefpsse dritter, vierter und
noch hherer Ordung einsetzt. Das wird vor allem im Audiobereich gemacht. Dazu werden
praktisch Operationsverstrker eingesetzt. In der AVR Application-Note AVR335: Digital Sound
Recorder with AVR and DataFlash wird zum Beispiel ein mit Operationsverstrkern aufgebauter
Chebychev-Tiefpass fnfter Ordnung verwendet. Man findet im Audiobereich gelegentlich auch
Schaltungen ohne expliziten Tiefpass. Dabei wird der Ausgang eines Class-D Verstrkers (der
nichts anderes als ein PWM-Signal erzeugt) ber einen Widerstand auf einen Lautsprecher
gegeben. Die mechanische Trgheit und die Induktivitt der Lautsprecherspule bilden mit dem
Widerstand einen Tiefpass.
[Bearbeiten]Dimmen von Leuchtmitteln
Siehe Artikel:
[Bearbeiten]Oft
Vereinfacht kann man sagen, dass whrend der Umschaltzeit die Verlustleistung am MOSFET =
1/4 der Verlustleistung am Verbraucher ist, wenn der eingeschaltet ist (Leistungsanpassung).
Beispiel: 150 Hz PWM = 6,6ms, Schaltzeit 500ns, Verbraucher 60W. Macht 15W Verlust
whrend der zwei Umschaltungen pro Takt, sprich 2x500ns = 1s. Aber das nur alle 6,6ms, Im
Mittel macht das 1us/6,6ms*15W = 2,2mW. Glck gehabt ;-) Bei hohen PWM-Frequenzen im
Bereich 20-500kHz, wie sie heute bei Schaltnetzteilen blich sind, kommt da aber schon richtig
viel zusammen.
Etwas
genauer: http://www.mikrocontroller.net/articles/FET#Verlustleistung_.28N.C3.A4herung_f.C3.B
Cr_eine_getaktete_Anwendung.29
[Bearbeiten]Siehe
auch
AVR-Tutorial: PWM
AVR-GCC-Tutorial: PWM
Ambilight in Hardware
1-Bit Digital-Analog-Wandlung
[Bearbeiten]Weblinks
AVR PWM
Inhaltsverzeichnis
[Verbergen]
1 Vorwort
2 Einfhrung
3 Timer / Counter
o
3.3 Betriebsmodi
3.3.1 Normal
5.1.1 Pseudocode
5.1.2 ASM
5.1.3 C
5.1.4 BASCOM
5.2 PWM per Hardware
5.2.1 ASM
5.2.2 C
6 Tiefpassfilter-Berechnung
7 Siehe auch
[Bearbeiten]Vorwort
Dieser Artikel ist noch nicht vollstndig! Und auerdem berschneidet er sich teilweise mit dem
Tutorial, weil PWM und Timer zum Verstndnis praktisch dasselbe sind. Vielleicht kann ja
jemand, der gerade dabei ist, sich diese Dinge anzueignen, die Beschreibung vorantreiben
(erweitern/entschlacken)?
Hier sollen die Mglichkeiten und die Funktionsweise der PWM mit AVRs erlutert werden, so
da Anfnger auf ihrem Weg zum Ziel untersttzt werden, ohne sich erst durch die wenig
erklrenden Beitrge im Forum zu qulen. Auch wenn das Verstndnis (hoffentlich) dann nicht
mehr aus dem Datenblatt kommen mu, ist dieses fr die spezifischen Einstellungen und
Feinheiten absolut notwendig. Aber mit dieser bersicht sollte es leichter fallen, die relevanten
Informationen schneller zu finden.
Ich gehe dabei von meiner Situation aus: "Gerade mit AVRs angefangen, die LED blinkt, Taster
wird abgefragt, schonmal von PWM gehrt und unter den AVR Pins welche mit OC.. entdeckt,
das hngt damit irgendwie zusammen." Man sollte sich auch um die Prozessorfrequenz
gekmmert haben, also die AVR_Fuses entsprechend gesetzt haben.
Wer in Begriff steht, sein erstes Board zu tzen, sollte sich ber die verschiedenen
Mglichkeiten, die die OCnx Pins bieten, informiert haben.
brigens lsst es sich besser lesen, wenn man sein Browserfenster so schmal macht, da der
Text in eine schne Spalte gezwungen wird.
[Bearbeiten]Einfhrung
Im AVR-GCC-Tutorial werden im Abschnitt DAC-Optionen verschiedene Mglichkeiten
angesprochen, analoge Spannungen zu generieren.
Darunter fllt auch die Pulsweitenmodulation, bei der durch schnelles Ein- und Ausschalten
eines Ausgangs (ber einen Filter) eine analoge Spannung generiert werden kann.
Beim Dimmen von Lichtquellen wirkt die Trgheit des Auges als Filter, wenn z. B. eine LED im
Mittel die Hlfte der Zeit eingeschaltet ist, scheint es also, als wrde sie nur halb so hell
leuchten.
Bei Motoren lt sich PWM gut zum Dosieren des Stroms einsetzen, ohne groe Verluste zu
haben. Fr einen Teil der Zeit wird also der volle Motorstrom eingeschaltet, d.h. das
Drehmoment ist maximal.
Die Rechtecksignale lassen sich mit Mikrocontrollern auf zwei Wegen erzeugen:
PWM per Software
oder
PWM per Hardware
Alles was mit Pulsen und Modulation zu tun hat, hat auch was mit Zeit zu tun denn im Prinzip
soll mit einer bestimmten Frequenz fr eine bestimmte Dauer ein Pin eingeschaltet werden.
Alles was bei Mikrocontrollern mit Zeit zu tun hat, hat wahrscheinlich auch etwas mit
einem Timer bzw. Counter zu tun.
[Bearbeiten]Timer
/ Counter
Unter Timer bzw. Counter (T/C) steht noch nicht soviel, aber man sollte kurz mal reinsehen,
oder mehr dazu schreiben, oder die fehlende Verknpfung anlegen.
Ein Timer ist nichts anderes als ein selbstndiger Zhler (Counter), der mit einer bestimmten
Frequenz einen Wert raufzhlt. Und zwar in Hardware, also unabhngig vom Programm. Seine
Zhlfrequenz wird vom Prozessortakt abgeleitet, das erledigt der Prescaler in einstellbaren
Schritten (Frequenzteiler).
Der Zhlerstand lt sich sowohl in Software als auch von der Hardware selbst berwachen und schon lassen sich damit periodisch Ereignisse auslsen.
Deswegen lassen sich die T/C fr viele Zwecke verwenden, wir wollen den T/C fr PWM nutzen
(trotzdem gleich eine bersicht ber die verschiedenen Modi).
Es lohnt sich natrlich, das Prinzip der T/C verstanden zu haben. Ein Blick ins GCC-Tutorial
lohnt, die Atmel Application Note 130: Setup and Use the AVR Timers schadet auch nicht.
Wie schon angedeutet, gibt es - je nach AVR - einen oder mehrere T/C . Sie unterscheiden sich
erwartungsgem durch ihre Parameter und Optionen, z. B. die Auflsung, Frequenz,
Zhlweise und andere Betriebsmodi.
Und natrlich auch durch den Namen, der sich auch in den Registern widerspiegelt: Sie werden
nmlich numeriert (im Folgenden hier und im Datenblatt mit Platzhalter n).
T/C 0 ist beim tiny2313 der 'einfache' mit 8 Bit Auflsung (das Aus-An Verhltnis lt sich in 256
Stufen einstellen), T/C 1 dagegen hat eine Auflsung von 16 Bit und bietet einige weitere
Mglichkeiten.
[Bearbeiten]8 oder 16 Bit ?
Auer der Tatsache, da die Auflsung bei 16 Bit mit 65536 Stufen um einiges feiner ist, gibt es
noch folgende Unterschiede:
Mit dem Zhler alleine kann man noch nicht so viel anfangen. Ausgedacht wurde deswegen
auerdem die
[Bearbeiten]Output Compare Unit
was soviel bedeutet wie Ausgangsvergleichseinheit.
Jeder Zhler hat eine oder mehrere voneinander unabhngige Output Compare Units (OC),
auch wieder mit den dazugehrigen Registern.
Die verschiedenen OCs und ihre Register werden mit Buchstaben ('A', 'B') benannt. (Im PWM
Modus hngt das direkt mit den Pins zusammen: OC1B ist der Ausgang der OC des T/C 1.
Dazu gleich mehr..)
Die OC vergleicht den Zhlerstand (im Register TCNTn) stndig mit ihren eigenen
Registerinhalten (OCRnx). Wenn diese bereinstimmen, passiert etwas.
Was passiert, wird bestimmt durch die
[Bearbeiten]Betriebsmodi
Der Zhler zhlt. Die OC Unit vergleicht dessen Zhlerstand mit einem Wert. Wenn diese
bereinstimmen, kann etwas passieren.
Weil es hier gleich mit den Einstellungen in den Registern losgeht, noch ein Hinweis:
Die Kontrolle ber das Verhalten der Zhler und OCs wird ber Register vorgenommen, deren
Namen nichts mit den OC Units zu tun haben! Die Einstellungen sind lediglich auf zwei Register
verteilt, die Timer/Counter Control Register - TCCRnA & TCCRnB.
Ein paar Notizen:
In verschiedenen Modi haben auch Bits in den Registern eine andere Bedeutung!
Prescaler
Counter Modus
Fast PWM
(eingeschrnkte PWM)
Der Zhler zhlt hoch, bis er mit OCRnx bereinstimmt (BOTTOM->OCRnx: Match!) und wird
dann auf Null gesetzt. Der maximale Wert lsst sich also ber das Register OCRnx komfortabel
bestimmen.
Konkret bedeutet das, dass die in diesem Modus vom Prescaler erzeugte Basisfrequenz
nochmals durch den Wert von OCRnx geteilt wird.
Fr PWM:
Wenn eingestellt ist, dass der OC-Ausgang bei jedem Match umschaltet (toggle), entspricht der
eingestellt Wert dem Pulsweitenverhltnis. Bei OCRnx=128 des 8 Bit T/C wre also etwa die
Hlfte der Zeit der Pin eingeschaltet.
Allerdings kann das beim T/C 0 des tiny2313 nur der Ausgang A (OC0A). Also ins Datenblatt
gucken!
[Bearbeiten]Fast
PWM
Correct PWM
Ist nur halb so schnell wie Fast PWM, dafr aber mit symmetrischer Wellenform.
Erreicht wird das, indem von BOTTOM->TOP gezhlt wird, und dann wieder runter: TOPBOTTOM.
TOP kann entweder 0xFF oder OCRnx sein.
Auch hier gibt es wieder den nicht-invertierenden, den invertierenden, und den toggle-Modus
(nicht an OC0B).
Der symmetrische PWM-Modus wird gerne fr Motorsteuerungen verwendet, wenn man den
Strom in den Motorwindungen messen mchte. Da man nicht whrend der Schaltzeitpunkte der
H-Brckentransistoren messen mchte (noise), braucht man einen Messzeitpunkt der maximal
weit von diesen Schaltzeitpunkten entfernt ist. Die BOTTOM und TOP Werte des Counters
bieten genau dies, da sie in der Mitte des High- bzw. Lowpegels liegen.
[Bearbeiten]Praktisches
Vorgehen
[Bearbeiten]Programmbeispiele
[Bearbeiten]PWM per Software
[Bearbeiten]Pseudocode
//Initialisierung
pwm_phase = 0
// von 0 bis 100(99) ergibt ein moduliertes Signal
pwm_soll = 30 // Tastverhltnis in Prozent (Werte von 0..100)
//alle s Sekunden tue:
wenn pwm_soll = pwm_phase dann
ausgang = LOW
wenn pwm_phase++ = 100 dann
pwm_phase = 0
ausgang = HIGH
Der Code ist nicht von mir, ich hab den John Honniball auch nicht um Erlaubnis gefragt, den
Code hier zu posten. Trozdem finde ich das Ganze recht ntzlich und hab' es mir trozdem
erlaubt:
; ledpwm.asm --- drive a blue LED with PWM
; Copyright (c) 2006 John Honniball
.include "m8def.inc"
.org 0x0000
21/04/2006
This program drives a single LED connected to the AVR's I/O port. It
is connected so that the cathode of the LED is wired to the AVR pin,
and the anode of the LED is wired to the 5V power supply via a
resistor. The value of that resistor depends on the colour of the LED,
but is usually a few hundred ohms.
;
;
;
;
;
We control the brightness of the LED with Pulse Width Modulation (PWM),
for two reasons. Firstly, we have no analog outputs on the AVR chip,
only digital ones. Secondly, a LED's brightness does not respond
linearly to variations in supply voltage, but it responds much better
to PWM.
; Pulsating LED looks better if it never quite goes "off", but cycles from
; full brightness to a dim state, and back again
.equ MINBRIGHT = 25
.equ MAXBRIGHT = 255
; This value controls how fast the LED cycles from bright to dim. It is
; the number of PWM cycles that we generate for each step in the brightness
; ramp, up and down. Larger numbers will make the pulsation slower.
.equ NCYCLES = 1
; Start of program execution after a Reset
ldi r16,low(RAMEND)
out SPL,r16
ldi r16,high(RAMEND)
out SPH,r16
; Initialise the hardware
ldi r16,0xff
out DDRB,r16
sbi LEDPORT,LEDBIT
;
;
;
;
;
;
sub r16,r17
rcall delayn4us
dec r18
brne l4
dec r17
cpi r17,MINBRIGHT
brne l3
rjmp dopwm
; DELAYN4US
; Delay for (R16 * 4) microseconds
delayn4us: tst r16
breq dly4
dly2:
ldi r24,low(16)
ldi r25,high(16)
dly3:
sbiw r24,1
brne dly3
dec r16
brne dly2
dly4:
ret
; 2 cycles
; 2 cycles
; Return to caller
[Bearbeiten]C
Dies ist ein einfaches Beispiel einer dimmbaren LED als Software PWM in C.
// F_CPU 4 MHz
#include <avr/io.h>
int main( void )
{
uint8_t pwm_soll = 30; // gewnschter Dimmerwert 0..100
uint8_t pwm_phase = 0; // Laufwert der Schleife 0..100
// LED + Widerstand mit PB0 und +5V verbunden
// PB0 o-----|<-----###------o Vcc 5V
DDRB |= (1<<PB0); // Pin PB0 an Port B als Ausgang
// LED ist bereits an
while( 1 )
{
if( pwm_soll == pwm_phase )
{
PORTB |= (1<<PB0); // active low LED aus
}
pwm_phase++;
if( pwm_phase == 100 )
{
pwm_phase = 0;
PORTB &= ~(1<<PB0); // active low LED an
}
}
return 0;
}
Eine komplexere Variante mit Interrupts wird im Artikel Soft-PWM beschrieben.
[Bearbeiten]BASCOM
$crystal = 4000000
Ddrb = &H01
Dim Pwm_phase As Integer , Pwm_soll As Integer
Do
If Pwm_soll = Pwm_phase Then
Portb.0 = 1
End If
Incr Pwm_phase
If Pwm_phase = 100 Then
Pwm_phase = 0
Portb.0 = 0
End If
Loop
End
[Bearbeiten]PWM per Hardware
Programmbeispiele
[Bearbeiten]ASM
Fr AtMega8.
.include
.def
start:
ldi
out
ldi
out
"m8def.inc"
temp
= r16
ldi
out
temp, 0xFF
DDRB, temp
ldi
out
temp, 0xF3
TCCR1A, temp
ldi
out
temp, 0x0A
TCCR1B, temp
;set Prescaler
main:
ldi
out
temp, 0x1
OCR1AH, temp
ldi
out
temp, 0x00
OCR1AL, temp
ldi
temp, 0x00
;define P
sei
out
OCR1BH, temp
ldi
out
temp, 0x00
OCR1BL, temp
loop:
rjmp
loop
[Bearbeiten]C
Hier wird mit dem 16-Bit-Counter 1 im PWM phase correct 8-Bit Modus eine LED am Pin OC1A
gedimmt. Die Frequenz betrgt
In [1] wurde beobachtet, dass der Ausgabepin OC1A unbedingt vor der Initialisierung der PWM
auf Ausgang gesetzt werden muss, wie auch oben unter Praktisches Vorgehen erlutert ist.
DDRB |= (1<<OC1A); // Port OC1A mit angeschlossener LED als Ausgang
TCCR1A = (1<<WGM10) | (1<<COM1A1); // PWM, phase correct, 8 bit.
TCCR1B = (1<<CS11) | (1<<CS10); // Prescaler 64 = Enable counter
OCR1A = 128-1; // Duty cycle 50% (Anm. ob 128 oder 127 bitte prfen)
Ein sehr gut erklrtes C PWM Beispielprogramm (inc. vielen Codekommentaren) kann man
bei extremeelectronics.co.in finden.
[Bearbeiten]Tiefpassfilter-Berechnung
Die PWM-Frequenz mu meistens mit einem Tiefpassfilter entfernt werden, da sie
nachfolgende Verstrkerstufen bersteuert oder den Hrgenuss trbt. Ein einfacher RCTiefpass kann fr Motorsteuerungen ausreichen, fr Audioanwendungen ist der Abstand
zwischen hchster Niederfrequenz und PWM-Frequenz zu klein. Ein aktives Filter mit
Operationsverstrker kann die Lsung sein, oder ein passiver LC-Tiefpass. Dessen Berechnung
mittels AADE Filter Designer soll hier an einem Fallbeispiel erlutert werden.
Ein ATmega48 mit 20 MHz Quarz soll mittels 10 Bit "fast PWM" des 16 Bit- Timers 1 ein
Stereosignal am Pin OC1A und OC1B ausgeben. Die PWM-Frequenz betrgt somit knapp 20
kHz, nach dem Abtasttheorem sind maximal 10 kHz Nutzsignal mglich. Mit der Faustregel
"6dB pro Bit" erreichen wir einen Dynamikumfang oder Strabstand von 60 dB. Etwa dieselbe
Unterdrckung sollte auch das Tiefpassfilter erreichen.
Zunchst brauchen wir noch eine Abschtzung der zulssigen Ausgangsbelastung. Laut
Datenblatt betrgt der maximal zulssige Strom pro Pin 40 mA, entsprechend einem 125
Widerstand von 5V nach Masse. Mit 10 bis 20 mA entsprechend 500 bis 250 als Maximalwert
drfte also nichts passieren. Der zeitliche Mittelwert liegt fr Audioanwendungen bei 2,5V, also
der halben Maximalspannung. Ein hochohmiger Kopfhrer, z. B. 600, lt sich so noch ohne
weitere Verstrker anschlieen.
Die grte Filtersteilheit erreicht das Cauer- oder Elliptic Filter, auf Kosten von greren
Phasennderungen/ Gruppenlaufzeit gegenber anderen Filtertypen. Wir starten also das
Filterberechnungsprogramm mit den Vorgaben "lowpass", "Cauer/Elliptic", "3.Ordnung", was
eine Schaltung mit einer Induktivitt und drei Kapazitten berechnet. Den Ein- und
Ausgangswiderstand geben wir erst mal irgendwo in dem genannten Bereich vor, die
Durchlasswelligkeit kann auf 1 dB bleiben, Durchlassfrequenz wie gesagt 10000 Hz, die
Sperrfrequenz etwas unterhalb der PWM-Frequenz, ca. 17500Hz, da der Dmpfungspol dann
etwa auf 20 kHz fllt. Mit "analyze voltage insertion gain" berechnen wir eine Durchlasskurve
und kontrollieren die korrekte Lage des Dmpfungspols. Jetzt variieren wir die beiden
Widerstnde, bis die Induktivitt etwa einem leicht erhltlichen Normwert entspricht. Die drei
Kondensatoren werden am Schlu ebenfalls mit dem nchsten Normwert bestckt.
Sicherheitshalber kann man diese endgltige Schaltung noch mit
einem Schaltungssimulationsprogrammberprfen und die Bauteilwerte leicht korrigieren.
Als Induktivitt kommen eher die greren Bauformen infrage, die "Garnrollen"-Form oder die
axiale Bauform 77A von Fastron. Hier gilt: je grer desto hhere Gte, wie man aus den
auch
AVR-Tutorial: PWM
Soft-PWM
PWM ist eine oft verwendete Funktion auf dem Gebiet der Mikrocontroller. Damit lassen sich
vielfltige Aufgaben lsen, wie beispielsweise die Leistungssteuerung von Motoren,
Helligkeitssteuerung von LEDs, Digital-Analog Wandlung und vieles mehr. Die meisten
Mikrocontroller haben ein oder mehrere PWM-Module eingebaut, womit ohne CPU-Belastung
eine PWM generiert werden kann. Jedoch kommt es bisweilen vor, da die Anzahl der
verfgbaren PWM-Kanle nicht ausreicht. Dann mu eine Softwarelsung gefunden werden,
bei der die CPU die PWM-Generierung vornimmt, genannt Soft-PWM.
Inhaltsverzeichnis
[Verbergen]
1 Einfacher Lsungsansatz
o
[Bearbeiten]Einfacher
Lsungsansatz
Ein sehr einfacher Lsungsansatz findet sich im AVR-Tutorial: PWM. Hier wird schon
ein Timer benutzt, um in regelmigen Abstnden die PWM-Generierung durchzufhren. Damit
verbleibt noch Rechenzeit fr andere Aufgaben, auerdem wird die Programmierung wesentlich
vereinfacht. Allerdings ist das Beispiel in ASM, hier soll das ganze in C gemacht werden.
Es soll nun eine 8 Bit PWM mit 100 Hz (PWM-Zyklus 10ms) und acht Kanlen generiert
werden. Der verwendete Controller ist ein AVR vom Typ ATmega32, welcher mit dem internen
RC-Oszillator auf 8 MHz getaktet wird. Das Programm kann jedoch problemlos an so ziemlich
jeden anderen AVR angepat werden. Die Programme wurden mit dem Optimierungsgrad -Os
compiliert.
[Bearbeiten]Erster Versuch
Das Programm ist recht kurz und bersichtlich. Im Hauptprogramm wird der Timer 1 initialisiert
und der Output Compare 1A als variabler Timer verwendet, wobei die Output Compare Funktion
nicht mit dem IO-Pin verbunden ist. Im Interrupt, welcher regelmig aufgerufen wird, werden
nun in einer Schleife alle acht Kanle geprft. Alle Kanle werden auf HIGH gesetzt, welche
eine PWM-Einstellung grer als der aktuelle Zykluszhler haben. Sinnvollerweise werden erst
alle Kanle geprft und das Ergebnis zwischengespeichert, am Ende erfolgt nur ein Zugriff auf
den Port.
/*
Eine 8-kanalige PWM mit einfachem Lsungsansatz
ATmega32 @ 8 MHz
*/
// Defines an den Controller und die Anwendung anpassen
#define
#define
#define
#define
#define
F_CPU 8000000L
F_PWM 100
PWM_STEPS 255
PWM_PORT PORTD
PWM_DDR DDRD
//
//
//
//
//
Systemtakt in Hz
PWM-Frequenz in Hz
PWM-Schritte pro Zyklus(1..255)
Port fr PWM
Datenrichtungsregister fr PWM
#if (T_PWM<(152+5))
#error T_PWM zu klein, F_CPU muss vergrssert werden oder F_PWM oder PWM_STEPS ver
#endif
#if PWM_STEPS > 255
#error PWM_STEPS zu gross
#endif
// includes
#include
#include
#include
#include
<stdint.h>
<string.h>
<avr/io.h>
<avr/interrupt.h>
// globale Variablen
volatile uint8_t pwm_setting[8];
pwm_cnt++;
int main(void) {
// PWM einstellen
PWM_DDR = 0xFF;
sei();
/*********************************************************************/
// nur zum Testen, im Anwendungsfall lschen
volatile uint8_t tmp;
const uint8_t t1[8]={27, 40, 3, 17, 150, 99, 5, 9};
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};
const uint8_t t5[8]={0, 0, 0, 0, 0, 0, 0, 9};
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};
// Messung der Interruptdauer
tmp =0;
tmp =0;
tmp =0;
// Debug
memcpy(pwm_setting, t1, 8);
memcpy(pwm_setting, t2, 8);
memcpy(pwm_setting, t3, 8);
memcpy(pwm_setting, t4, 8);
memcpy(pwm_setting, t5, 8);
memcpy(pwm_setting, t6, 8);
/*********************************************************************/
}
return 0;
Im AVR-Studio kann man den Code simulieren. Wichtig ist hier vor allem die Ausfhrungszeit
des Interrupts. Bei 100 Hz PWM-Frequenz und 256 Schritten pro PWM-Zyklus wird diese
Funktion immerhin 25600 mal pro Sekunde aufgerufen (PWM-Takt 25,6 kHz), bei 8MHz
Taktfrequenz stehen damit maximal 312 Takte zur Verfgung. Glcklicherweise ist die Funktion
relativ kurz und der GCC leistet gute Arbeit. Der Interrupt bentigt hier 152 Takte, es verbleiben
also jeweils 160 Takte zur Bearbeitung anderer Aufgaben. Das entspricht einer CPU-Belastung
von ~49%. Das Programm bentigt 284 Byte Programmspeicher. Nicht schlecht fr den Anfang.
[Bearbeiten]Zweiter Versuch
Wo gibt es in diesem Programm noch Optimierungsmglichkeiten? Nur im Interrupt, denn das
ganze Programm besteht ja praktisch nur aus der Interruptroutine. Betrachten wir die Schleifen
genauer mssen wir feststellen, dass die Indizierung von pwm_setting[] etwas Rechenzeit
bentigt. Ebenso die Schiebeoperation von tmp, auch wenn das nur acht mal ein Takt ist. Wir
knnen jetzt per Hand das machen, was der Compiler auch manchmal macht. Die Rede ist vom
Loop-Unrolling. Dabei wird die Schleife durch mehrere diskrete Befehle ersetzt (entrollt). Der
Vorteil dabei ist, dass die Befehle zur Berechnung und Prfung der Zhlvariable entfallen,
auerdem knnen ggf. Werte im Voraus berechnet werden. Als Ergebnis hat man zwar ein
etwas greres Programm, doch das wird schneller ausgefhrt! Auerdem orientiert sich diese
Version mehr am Original der Assemblerversion. Dadurch wird sie zustzlich ein wenig krzer
und schneller.
/*
*/
// Defines an den Controller und die Anwendung anpassen
#define
#define
#define
#define
#define
F_CPU 8000000L
F_PWM 100
PWM_STEPS 256
PWM_PORT PORTD
PWM_DDR DDRD
//
//
//
//
//
Systemtakt in Hz
PWM-Frequenz in Hz
PWM-Schritte pro Zyklus(1..256)
Port fr PWM
Datenrichtungsregister fr PWM
#if (T_PWM<(93+5))
#error T_PWM zu klein, F_CPU muss vergrssert werden oder F_PWM oder PWM_STEPS ver
#endif
// includes
#include
#include
#include
#include
<stdint.h>
<string.h>
<avr/io.h>
<avr/interrupt.h>
// globale Variablen
volatile uint8_t pwm_setting[8];
// Timer 1 Output COMPARE A Interrupt
ISR(TIMER1_COMPA_vect) {
static uint8_t pwm_cnt=0;
uint8_t tmp=0;
OCR1A += (uint16_t)T_PWM;
if (pwm_setting[0] > pwm_cnt) tmp |= (1<<0);
(1<<1);
(1<<2);
(1<<3);
(1<<4);
(1<<5);
(1<<6);
(1<<7);
// PWMs aktualisieren
}
int main(void) {
// PWM einstellen
PWM_DDR = 0xFF;
sei();
/*********************************************************************/
// nur zum Testen, im Anwendungsfall lschen
volatile uint8_t tmp;
const uint8_t t1[8]={27, 40, 3, 17, 150, 99, 5, 9};
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};
const uint8_t t5[8]={0, 0, 0, 0, 0, 0, 0, 9};
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};
// Messung der Interruptdauer
tmp =0;
tmp =0;
tmp =0;
// Debug
memcpy(pwm_setting, t1, 8);
memcpy(pwm_setting, t2, 8);
memcpy(pwm_setting, t3, 8);
memcpy(pwm_setting, t4, 8);
memcpy(pwm_setting, t5, 8);
memcpy(pwm_setting, t6, 8);
/*********************************************************************/
return 0;
}
Mit dieser Interruptroutine werden nur noch 93 Takte bentigt, die CPU-Belastung verringert
sich auf ~30%. Nicht schlecht. Der Programmcode steigt auf 324 Byte, aber das ist im
Angesicht der Leistungsverbesserung zu verschmerzen. Weiter verringern kann man die CPUBelastung durch eine niedrigere PWM-Frequenz oder eine geringere Anzahl Stufen der PWM.
Wenn man beispielsweise mit 64 (6 Bit) statt 256 (8 Bit) Stufen auskommt verringert sich die
Belastung um den Faktor 4.
[Bearbeiten]Intelligenter
Lsungsansatz
Wenn auch eine CPU-Last von 30% recht akzeptabel erscheint, so hat man doch noch
irgendwie das Gefhl, da es noch besser geht. Aber wie? Mein Mathematikprofessor pflegte in
so einem Fall die Anwendung der Methode des scharfen Blicks . Was passiert eigentlich
whrend eines gesamten PWM-Zykluses?
Zu Beginn werden alle IO-Pins gesetzt, deren PWM-Einstellung nicht Null ist.
Die jeweiligen IO-Pins werden gelscht, wenn der PWM-Zhler mit der PWMEinstellung bereinstimmt
Ja klar, aber da wir nur acht Kanle haben, gibt es maximal 8 Zeitpunkte, an denen ein Pin
gelscht werden muss. Wenn mehrere Kanle die gleiche PWM-Einstellung haben sind es
sogar noch weniger. Alle anderen Interrupts verursachen keinerlei nderung der IO-Pins und
verbrauchen eigentlich nur sinnlos Rechenzeit. Ein Skandal!
Was ist also zu tun? Wir wissen nun, da es maximal 9 Ereignisse pro PWM-Zyklus gibt. Was
ist damit zu machen?
Das ist eigentlich schon alles. Praktisch ist das mit einigen Kniffligkeiten verbunden, aber die
sind lsbar. Am Ende steht eine Interruptroutine, welche maximal 9 mal pro PWM-Zyklus
aufgerufen wird und die jeweiligen IO-Pins setzt bzw. lscht. Eine normale Funktion wird
benutzt, um die PWM-Einstellungen der acht Kanle in die notwendigen Informationen fr die
Interruptroutine umzuwandeln. Das hat unter anderem den Vorteil, da nur dann zustzlich
Rechenzeit bentigt wird, wenn sich die PWM-Einstellungen ndern.
Es sollte jedoch nicht unerwhnt bleiben, dass es sich bei dieser Lsung um keine echte PWM
handelt, da sie im Fall, dass ein PWM-Wert 0 ist, den Ausgang nie auf 1 schaltet, und somit
auch kein Puls vorhanden ist. Allerdings ist das fr die meisten Anwendungen kein Problem.
/*
Eine 8-kanalige PWM mit intelligentem Lsungsansatz
ATmega32 @ 8 MHz
*/
// Defines an den Controller und die Anwendung anpassen
#define
#define
#define
#define
#define
#define
#define
F_CPU
F_PWM
PWM_PRESCALER
PWM_STEPS
PWM_PORT
PWM_DDR
PWM_CHANNELS
8000000L
100L
8
256
PORTB
DDRB
8
//
//
//
//
//
//
//
Systemtakt in Hz
PWM-Frequenz in Hz
Vorteiler fr den Timer
PWM-Schritte pro Zyklus(1..256)
Port fr PWM
Datenrichtungsregister fr PWM
Anzahl der PWM-Kanle
#if ((T_PWM*PWM_PRESCALER)<(111+5))
#error T_PWM zu klein, F_CPU muss vergrssert werden oder F_PWM oder PWM_STEPS ver
#endif
#if ((T_PWM*PWM_STEPS)>65535)
#error Periodendauer der PWM zu gross! F_PWM oder PWM_PRESCALER erhhen.
#endif
// includes
#include
#include
#include
#include
<stdint.h>
<string.h>
<avr/io.h>
<avr/interrupt.h>
// globale Variablen
uint16_t pwm_timing[PWM_CHANNELS+1];
uint16_t pwm_timing_tmp[PWM_CHANNELS+1];
uint8_t
uint8_t
pwm_mask[PWM_CHANNELS+1];
pwm_mask_tmp[PWM_CHANNELS+1];
uint8_t
uint8_t
pwm_setting[PWM_CHANNELS];
pwm_setting_tmp[PWM_CHANNELS+1];
// Zeiger austauschen
// das muss in einem Unterprogramm erfolgen,
// um eine Zwischenspeicherung durch den Compiler zu verhindern
void tausche_zeiger(void) {
uint16_t *tmp_ptr16;
uint8_t *tmp_ptr8;
tmp_ptr16 = isr_ptr_time;
isr_ptr_time = main_ptr_time;
main_ptr_time = tmp_ptr16;
tmp_ptr8 = isr_ptr_mask;
isr_ptr_mask = main_ptr_mask;
main_ptr_mask = tmp_ptr8;
// PWM_CHANNELS Datenstze
// Startindex
while(k>i) {
while ( ((pwm_setting_tmp[i]==pwm_setting_tmp[i+1]) || (pwm_setting_tmp[i]==0)
// Zeitdifferenzen berechnen
if (k==0) { // Sonderfall, wenn alle Kanle 0 sind
main_ptr_time[0]=(uint16_t)T_PWM*PWM_STEPS/2;
main_ptr_time[1]=(uint16_t)T_PWM*PWM_STEPS/2;
k=1;
}
else {
i=k;
main_ptr_time[i]=(uint16_t)T_PWM*(PWM_STEPS-pwm_setting_tmp[i]);
tmp_set=pwm_setting_tmp[i];
i--;
for (; i>0; i--) {
main_ptr_time[i]=(uint16_t)T_PWM*(tmp_set-pwm_setting_tmp[i]);
tmp_set=pwm_setting_tmp[i];
}
main_ptr_time[0]=(uint16_t)T_PWM*tmp_set;
}
// auf Sync warten
pwm_sync=0;
while(pwm_sync==0);
// Zeiger tauschen
cli();
tausche_zeiger();
pwm_cnt_max = k;
sei();
}
// Timer 1 Output COMPARE A Interrupt
ISR(TIMER1_COMPA_vect) {
static uint8_t pwm_cnt;
uint8_t tmp;
OCR1A += isr_ptr_time[pwm_cnt];
tmp
= isr_ptr_mask[pwm_cnt];
if (pwm_cnt == 0) {
PWM_PORT = tmp;
pwm_cnt++;
}
else {
PWM_PORT &= tmp;
// Ports lschen
// zustzliche PWM-Ports hier setzen
if (pwm_cnt == pwm_cnt_max) {
pwm_sync = 1;
pwm_cnt = 0;
}
else pwm_cnt++;
}
int main(void) {
// PWM Port einstellen
PWM_DDR = 0xFF;
// Port als Ausgang
// zustzliche PWM-Ports hier setzen
// Timer 1 OCRA1, als variablen Timer nutzen
TCCR1B = 2;
TIMSK |= (1<<OCIE1A);
sei();
/******************************************************************/
// nur zum testen, in der Anwendung entfernen
/*
// Test values
volatile uint8_t tmp;
const uint8_t t1[8]={255, 40, 3, 17, 150, 99, 5, 9};
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};
const uint8_t t5[8]={9, 1, 1, 1, 1, 1, 1, 1};
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};
const uint8_t t7[8]={0, 0, 0, 0, 0, 0, 0, 88};
// Messung der Interruptdauer
tmp =1;
tmp =2;
tmp =3;
// Debug
memcpy(pwm_setting, t1, 8);
pwm_update();
memcpy(pwm_setting, t2, 8);
pwm_update();
memcpy(pwm_setting, t3, 8);
pwm_update();
*/
/******************************************************************/
while(1);
return 0;
}
Das Programm ist schon um einiges lnger (968 Byte). Die Interruptroutine bentigt maximal
111 Takte und wird zwischen 2 bis 9 mal pro PWM-Zyklus aufgerufen. Zweimal, wenn alle
PWM-Einstellungen gleich sind, 9 mal, wenn alle PWM-Einstellungen verschieden sind. Damit
werden zwischen 222 bis 999 Takte bentigt, pro PWM-Zyklus, nicht pro PWM-Takt! Das
entspricht einerCPU-Belastung von 0,3..1,2%! Standing Ovations! Die Funktion pwm_update()
bentigt ca. 1500 bis 1800 Takte, das ist geringfgig abhngig von den PWM-Einstellungen, je
nach dem ob die Daten schon sortiert sind und ob PWM-Werte mehrfach vorkommen. Bei einer
Updaterate von 100 Hz (mehr ist physikalisch sinnlos) entspricht das einer CPU-Belastung von
2,3%, praktisch wird es wahrscheinlich weniger sein. Taktet man den AVR mit vollen 16 MHz
halbiert sich die CPU-Belastung noch einmal. Beachtet werden sollte hier die Datenbergabe
von der Funktion pwm_update() zur Interruptroutine. Hier werden jeweils zwei Zeiger
verwendet, um auf Arrays zu zeigen. In zwei Arrays werden durch die Funktion die
Berechnungen der neuen Daten vorgenommen. In den beiden anderen Arrays stehen die
aktuellen Daten, mit welchen die ISR arbeitet. Um am Ende der Berechung ein relativ
aufwndiges Kopieren der Daten zu vermeiden werden einfach die Zeiger vertauscht. Das ist
wesentlich schneller als das Kopieren der Arrays! Im englischen spricht man hier von double
buffering, also doppelter Pufferung. Dieses Prinzip wird oft angewendet. Wrde man allerdings
einfach am Ende die Zeiger tauschen kme es zu einem Crash! Der Interrupt kann jederzeit
aktiv werden. Wenn dann die Zeiger nur halb kopiert sind greift die Interruptroutine auf
zerstckelte Daten zu und macht Mll. Ebenso wrde es zu Fehlfunktionen kommen, wenn
whrend es PWM-Zyklus neue Daten in die Arrays kopiert werden. Das mu verhindert werden.
Und zwar dadurch, da ber eine Variable eine Synchronisation durchgefhrt wird. Diese wird
am Ende des PWM-Zyklus gesetzt und signalisiert, da neue Daten fr den nchsten Zyklus
kopiert werden knnen. Deshalb muss die Funktion pwm_update ggf. bis zu 1 vollen PWMZyklus warten, bis die Zeiger getauscht werden knnen. Wichtig ist dabei, da die Variable
pwm_sync, welche sowohl in der Funktion als auch im Interrupt geschrieben wird,
als volatile deklariert wird. Denn sonst wrde die Sequenz
pwm_sync=0;
while(pwm_sync==0);
zum Stehenbleiben der CPU fhren, weil der Compiler erkennt, da die Variable nie ungleich
Null sein kann und damit die Schleife endlos ausgefhrt wird. Der Compiler kann prinzipbedingt
nicht automatisch erkennen, da die Variable im Interrupt auf 1 gesetzt wird.
Bei dem schon recht hohen Prozessortakt von 8MHz und der relativ niedrigen PWM Frequenz
von 100 Hz haben wir allerdings ein kleines Problem. Wenn beispielsweise nur ein Kanal den
PWM-Wert 10 hat, alle anderen aber den Wert Null, dann passiert folgendes. Zum Begin eines
PWM-Zyklus wird der eine Kanal aktiviert. Jetzt wird per Timer fr 10xT_PWM = 3120 Takte
gewartet. Jetzt wird dieser Kanal wieder gelscht. Bis zum Begin des nchsten PWM-Zyklus
muss jedoch noch (256-10)*T_PWM = 76752 Takte gewartet werden. Doch diese Zahl passt
nicht mehr in eine 16 Bit Variable! Und damit kann sie auch nicht mit dem Timer verwendet
werden. Der Ausweg heisst Vorteiler (engl. Prescaler). Damit kann der Timer langsamer
getaktet werden und somit wird die gleiche Wartezeit mit weniger Timertakten erzielt. Zu
beachten ist, dass die Einstellung im #define PWM_PRESCALER mit der realen Einstellung in
TCCR1B bereinstimmen muss.
Eine Einschrnkung gilt allerdings fr alle Soft-PWMs. Die PWM-Frequenz muss niedrig genug
sein, damit sich die Interrupts nicht berschneiden. D.h. der Wert T_PWM mu immer grer
sein als die Anzahl Takte der Interruptroutine. Das wird im Quelltext mit Hilfe von #if . . #endif
geprft. Die +5 Takte sind eine Reserve. Dazu mu aber die Optimierung -Os eingeschaltet
sein, sonst stimmen die Zahlen nicht!
Achtung
Wenn man fr eine PWM aussschliesslich 8 Bit breite Datentypen verwendet, dann
steht fr den Parameter fr die Pulsbreite nur der Bereich 0..255 zur Verfgung. Da bei
einer vollstndigen PWM mit N Schritten aber N+1 mgliche Flle auftreten knnen
(0/N bis N/0), ist mit dem hier gezeigten Code eine solche PWM nur fr N ? 255
realisierbar.
Als Grenze kann mit diesem Programm bei 16 MHz eine 10-Bit PWM in Software generiert
werden, was schon sehr respektabel ist und weiches, langsames LED-Fading mit
vielen LEDs ermglicht. Dazu muss allerdings das Programm an wenigen Stellen angepasst
werden, im Speziellen mssen verschiedene Variablen und Arrays von uint8_t auf uint16_t
umgestellt werden. Da Gleiche gilt fr mehr PWM-Kanle, praktisch wurden schon bis zu 32
Kanle realisiert. Der Quelltext ist an den Stellen kommentiert.
[Bearbeiten]Maximale
Bei der Ansteuerung einer LED-Matrix als Richtwert 10 (Spalten) x 20 (Zeilen) = 200 LEDs
kommt man bei den vorangegangenen Lsungsanstzen zu den Problem, dass die
Ausfhrungszeit einer Interruptserviceroutine (ISR) einfch zu lange dauert. Zudem sind fr eine
gengend softe Steuerung bei kleinen Helligkeiten deutlich mehr als 8 Bit PWM-Auflsung
erforderlich.
Die Lsung besteht darin, die kurzen PWM-Zeiten durch eine Sequenz von zeitlich
berechneten Ausgabebefehlen zu generieren und die langen wie gehabt durch eine
Zhlervergleichs-ISR.
Man macht sich dabei die Eigenschaft typischer Mikrocontroller zu Nutze, dass die Latenzzeit
fr den Interruptaufruf nur um wenige Takte schwankt (= Jitter): Beim AVR sind es maximal 3
Takte, wenn ein RET-Befehl (4 Takte) abgearbeitet wird. Phasen mit gesperrten Interrupts oder
konkurrierende ISRs sollte man vermeiden. Eventuelle ISRs sollten sei als ersten Befehl (am
besten noch vor dem Sprung zur eigentlichen ISR) enthalten, dann wre der Jitter 5 (4 Takte
ISR-Aufruf, 1 Takt sei, 1 Takt gesperrte Interrupts fr den Befehl danach). Steht die
Hauptschleife im sleep, ist der Jitter Null.
Der nachfolgende Programmrumpf hat folgende Eigenschaften auf einem ATmega16 mit
16 MHz Quarzfrequenz:
PWM-Tiefe: 13 Bit
Die kurzen PWM-Zeiten werden derart verschachtelt auf die 3 Ports ausgegeben, dass immer
genug Zeit zum Nachladen von Registern zwischendurch vorhanden ist. Anschaulich in der
Kommentarspalte.
(Das +-Zeichen unterteilt lngliche PWM-Phasen in 8 Takte. Jede Zeile steht fr 1 CPU-Takt. Der * in der Registerspalte
bedeutet: Register mit gltigem Wert geladen.)
Das Vorhalten der auszugebenden Werte erfolgt in der Datenstruktur abt im RAM (AnodenBit-Tabelle); das Zeigerregister Y luft da durch. Ein paar Werte werden bereits im voraus in
den Registern R2..R6 vorgehalten, das erspart Ladebefehle am Anfang der ISR.
TIMER1_CAPT_vect:
// Verschachtelte Lngen
push
YL
push
YH
ldi
ldi
out
out
out
out
out
out
out
sei
ldd
YL,lo8(abt)
YH,hi8(abt)
PORTB,ONES
PORTD,ONES
PORTC,r2
PORTA,r3
PORTB,r4
PORTD,r5
PORTC,r6
r2,Y+0*3+2
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
PORTB
4096
|
|
|
|
|
32
|
|
|
|
PORTD
4096
|
|
|
|
|
|
16
|
|
|
PORTC
4096
|
|
|
|
|
|
|
8
|
|
R23456
*****
-****
--***
---**
----*
----*----
Y retten (wird bi
2-Takt-Befehl
Anoden AUS
Anoden AUS
Anoden AUS, Katod
Katode umschalten
Anoden EIN
Anoden EIN
Anoden EIN (Katod
Fr [http://www.o
Register nachlade
ldd
r3,Y+1*3+2
ldd
r4,Y+4*3+2
out
out
nop
out
nop
ldd
PORTC,r2
PORTC,r3
out
nop
ldd
PORTD,r2
ldd
r3,Y+5*3+2
ldd
r4,Y+0*3+0
ldd
r5,Y+2*3+0
ldd
r6,Y+2*3+1
out
ldd
PORTC,r2
r2,Y+1*3+0
out
out
out
out
nop
ldd
PORTB,r2
PORTC,r3
PORTB,r4
PORTB,r5
out
nop
ldd
PORTB,r2
ldd
r3,Y+0*3+1
ldd
r4,Y+1*3+1
out
cli
out
out
sei
out
ldd
PORTB,r2
ldi
out
ld
YL,abt+6*3
PORTD,r2
r2,Y+
ld
r3,Y+
ld
r4,Y+
out
out
out
PORTB,r2
PORTD,r3
PORTC,r4
PORTC,r4
r2,Y+5*3+1
r2,Y+2*3+2
r2,Y+3*3+0
r2,Y+4*3+0
PORTD,r3
PORTD,r4
PORTD,r6
r2,Y+3*3+1
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
|
|
|
+
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
2
|
1
4
|
|
|
8
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
64
|
|
|
|
|
|
+
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
1
2
|
4
|
|
|
8
|
|
|
|
|
|
|
64
|
|
|
|
|
|
1
2
|
16
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
4
|
|
|
32
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
64
**--***--**---*-----*-------*---**--***-*********
-****
*****
-****
--***
---**
----*
*---*
----*
*---*
**--*
***-*
-**-*
--*-*
----*
----*-------*---**--***--**---*------
2-Takt-Befehl
Prinzipiell knnt
auch LDS verwen
knnte man noch
Takte zum Rette
movw
pop
r2,YL
YH
pop
YL
reti
//
//
//
//
//
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Bei zwei statt drei Anoden-Ausgabeports, wenn bis zu 16 Ausgabeleitungen gengen, kommt
man mit einer programmgesteuerten maximalen Pulsweite von 16 (2 4) aus. Das verringert die
Quelltextlnge.
Die langen PWM-Zeiten werden wie gewohnt in einer ISR ausgegeben; die Besonderheit hier
ist nur, dass der nchste Unterbrechungszeitpunkt gleich mit aus der Tabelle bzt (BitzeitTabelle) im festen Offset ausgelesen wird. Das erspart das Vorhalten eines zweiten
Zeigerregisters. Das jeweils dritte Byte in bzt ist ungenutzt.
TIMER1_COMPA_vect:
push
YL
push
movw
ld
ld
ld
out
out
out
ldd
ldd
out
out
movw
pop
pop
reti
//
|
|
//
|
|
YH
//
|
|
//
|
|
YL,r2
//
|
|
r2,Y+
//
|
|
r3,Y+
//
|
|
r4,Y+
//
|
|
PORTB,r2
//
128+
|
PORTD,r3
//
|
128+
PORTC,r4
//
|
|
r2,Y+(bzt-(abt+8*3)) //
|
r3,Y+(bzt-(abt+8*3)+1)//|
|
OCR1AH,r3
//
|
|
OCR1AL,r2
//
|
|
r2,YL
//
|
|
YH
//
|
|
//
|
|
YL
//
|
|
//
|
|
//gesamt: 35 Takte
|
|
|
|
|
|
|
|
|
|
128+
|
|
|
|
|
|
|
|
|
Der Programm-Anwender kann also ganz einfach in das Array lht Helligkeitswerte
hineinpoken, die ISR kmmert sich um den Rest.
[Bearbeiten]Zusammenfassung
Durch kritische Analyse ist es mglich, eine Software-PWM drastisch zu verbessern und die
CPU-Belastung auf ein verschwindend geringes Ma zu reduzieren. Zudem zeigen diese
Beispiele ein oft vorkommendes Muster auf, welches gemeinhin als 'Time for Space' (Zeit fr
Platz) bezeichnet wird. Man meint damit, dass es oft mglich ist, dramatische Einsparungen in
der Laufzeit zu erreichen, wenn man gewillt ist dafr Speicherplatz zu opfern.
Version
Programmspeicher [Byte]
CPU-Belastung [%]
284
49
324
30
968
0,3..1,2
13-bit-PWM
1500
25
[Bearbeiten]Siehe
auch
Pulsweitenmodulation
AVR-Tutorial: PWM
AVR-GCC-Tutorial: PWM
[Bearbeiten]Weblinks
LED-Fading
Inhaltsverzeichnis
[Verbergen]
1 Das Problem
2 Die Theorie
2.1 Die Kennlinie des Auges genau betrachtet
3 Das Demoprogramm
4 FAQ
5 Funoten
6 Siehe auch
[Bearbeiten]Das
Problem
Die Aufgabe klingt eigentlich recht einfach. Eine LED soll mittels PWM in ihrer Helligkeit
gesteuert werden. Und weils so schn ist, mchte man sie geheimnisvoll aufleuchten lassen,
sprich langsam heller und dunkler werden lassen. Der Fachmann nennt das Fading. Das
Problem zeigt sich allerdings recht schnell. Wenn man eine 8-Bit PWM linear zwischen 0..255
laufen lsst, dann scheint die LED nicht linear gedimmt zu werden. Sie wird relativ schnell hell
und bleibt lange hell.
[Bearbeiten]Die
Theorie
Des Rtsels Lsung liegt in der Kennline des menschlichen Auges. Diese ist nichtlinear,
genauer gesagt: sie ist nahezu logarithmisch. Das ermglicht die Wahrnehmung eines sehr
groen Helligkeitsbereichs, angefangen von Vollmond mit ~1/4 Lux ber eine normale
Schreibtischbeleuchtung mit ca. 750 Lux bis zu einem hellen Sommertag mit bis zu 100.000
Lux. Solche hochdynamischen Signale sind nur mit einer logarithmischen Kennlinie in den Griff
zu kriegen, auch von Mutter Natur und Erfinder Papa.
[Bearbeiten]Die Kennlinie des Auges genau betrachtet
Die Kennlinie des menschlichen Auges ist annhernd logarithmisch. Das wurde vor langer Zeit
durch das Weber-Fechner-Gesetz beschrieben. Genauere Untersuchungen
zur Gammakorrektur fhrten jedoch zur Stevenschen Potenzfunktion. Diese beschreibt das
menschliche Auge etwas besser. (s. auch Diskussionsseite). Die Unterschiede sind jedoch
marginal.
Praktisch heit das, da wir unserem Auge groe physikalische Helligkeitsunterschiede
prsentieren mssen, damit es das als lineare Helligkeitsteigerung erkennt. Etwas
wissenschaftlicher formuliert heit das, wir mssen durch Verkettung der logarithmischen
Kennlinie des Auges mit einer exponentiellen Kennlinie eine physiologisch lineare
Helligkeitsverteilung erzielen.
Berechnet werden kann eine passende Tabelle beispielsweise mit folgender Funktion:
Dabei sind x und y die Ein-, bzw. Ausgabewerte der Funktion, jeweils im Bereich von 0 bis r1.
b ist die Basis der Exponentialfunktion und bestimmt, wann und wie stark die Kurve ansteigen
soll. Hier ist etwas ausprobieren erforderlich, gute Ergebnisse liefern Werte im Bereich 10100.
[Bearbeiten]Das
Demoprogramm
Das folgende Beispielprogramm gemacht demonstriert die Wirkung verschiedener PWMAuflsungen. Eine 8-Bit PWM wird mit 4/8/16 und 32 nichtlinearen Stufen betrieben, welche
ber eine Exponentialfunktion berechnet wurden. Dazu dient die Exceltabelle[1]. Die einzelnen,
benachbarten Werte haben zueinander ein konstantes Verhltnis, das in der Exceltabelle
als Factor berechnet wird. Ausserdem werden eine 10-Bit PWM mit 64 Stufen sowie eine 16-Bit
PWM mit 256 Stufen betrieben.
Das Programm ist ursprnglich auf einem AVR vom Typ ATmega32 entwickelt und getestet
worden. Aber es ist leicht auf jeden AVR portierbar, welcher eine PWM zur Verfgung hat. Der
AVR muss mit etwa 8 MHz getaktet werden, egal ob mit internem Oszillator oder von aussen
mit Quarz. Man muss nur noch eine LED mittels Vorwiderstand von ca. 1 k an Pin D5
anschliessen und los gehts. Es sollte hier noch erwhnt werden, dass das Programm mit
eingeschalteter Optimierung compiliert werden muss, sonst stimmen die Zeiten der
Warteschleifen aus util/delay.h nicht.
Bei Verwendung der LEDs auf dem STK500 bzw. bei der Verwendung von invertierenden
Treiberstufen ist das
#define STK500 0
durch
#define STK500 1
zu ersetzen.
Das Programm durchluft alle 6 PWMs und lsst dabei die LED jeweils 3 mal glimmen. Mit 4
Schritten Auflsung ist das natrlich ruckelig, mit 8 schon wesentlich besser. Mit 16 Stufen sieht
man bei langsamen nderungen noch Stufen, dreht man die Ein- und Ausblendzeiten runter, ist
der bergang schon recht flssig. Die 8-Bit PWM mit 32 Stufen unterscheidet sich praktisch
nicht von der 10-Bit PWM mit 64 Stufen, es sei denn, man macht extrem langsame
Einblendungen. Hier schlgt die Stunde der 16-Bit PWM. Diese wird bewut sehr langsam
ausgefhrt um zu demonstrieren, da hiermit praktisch keine Stufen mehr sichtbar sind, egal
wie langsam gedimmt wird. Wie man auch sieht sind die drei hherauflsenden PWMs im
10,
55,
250,
1023
};
61, 64, 67, 70, 73, 76, 79, 83, 87, 91, 95, 99, 103, 108, 112,
117, 123, 128, 134, 140, 146, 152, 159, 166, 173, 181, 189, 197,
206, 215, 225, 235, 245, 256, 267, 279, 292, 304, 318, 332, 347,
362, 378, 395, 412, 431, 450, 470, 490, 512, 535, 558, 583, 609,
636, 664, 693, 724, 756, 790, 825, 861, 899, 939, 981, 1024, 1069,
1117, 1166, 1218, 1272, 1328, 1387, 1448, 1512, 1579, 1649, 1722,
1798, 1878, 1961, 2048, 2139, 2233, 2332, 2435, 2543, 2656, 2773,
2896, 3025, 3158, 3298, 3444, 3597, 3756, 3922, 4096, 4277, 4467,
4664, 4871, 5087, 5312, 5547, 5793, 6049, 6317, 6596, 6889, 7194,
7512, 7845, 8192, 8555, 8933, 9329, 9742, 10173, 10624, 11094,
11585, 12098, 12634, 13193, 13777, 14387, 15024, 15689, 16384,
17109, 17867, 18658, 19484, 20346, 21247, 22188, 23170, 24196,
25267, 26386, 27554, 28774, 30048, 31378, 32768, 34218, 35733,
37315, 38967, 40693, 42494, 44376, 46340, 48392, 50534, 52772,
55108, 57548, 60096, 62757, 65535
[Bearbeiten]Funoten
1. Anmerkung: Bitte die Exceltabelle nochmal erklren, die Werte in der Tabelle stimmen
nicht mit denen im Programm berein
[Bearbeiten]Siehe
auch
PWM
AVR-GCC-Tutorial: PWM
AVR-Tutorial: Schieberegister
Ab und an stellt sich folgendes Problem: Man wrde wesentlich mehr Ausgangspins oder
Eingangspins bentigen als der Mikrocontroller zur Verfgung stellt. Ein mglicher Ausweg ist
eine Porterweiterung mit einem Schieberegister. Zwei beliebte Schieberegister sind
beispielsweise der 74xx595 bzw. der 74xx165.
Inhaltsverzeichnis
[Verbergen]
1 Porterweiterung fr Ausgnge
1.1 Aufbau 74xx595
1.3 Funktionsweise
2.1 Aufbau
2.2 Funktionsweise
2.3 Schaltung
[Bearbeiten]Porterweiterung
fr Ausgnge
Um neue Ausgangspins zu gewinnen kann der 74xx595 verwendet werden. Dabei handelt es
sich um ein 8-Bit 3-state Serial-in/Serial-out or Parallel-Out Schieberegister mit einem
Ausgangsregister und einem asynchronen Reset.
Hinter dieser kompliziert anmutenden Beschreibung verbirgt sich eine einfache Funktionalitt:
Das Schieberegister besteht aus zwei Funktionseinheiten: Dem eigentlichen Schieberegister
und dem Ausgangsregister. In das Schieberegister knnen die Daten seriell hineingetaktet
werden und durch ein bestimmtes Signal werden die Daten des Schieberegisters in das
Ausgangsregister bernommen und knnen von dort auf die Ausgangspins geschaltet werden.
Im Einzelnen bedeuten die Begriffe:
Begrif
Erklrung
8-Bit
Acht Ausgangsbits
3-state
Serial-in
Serial-out
Parallel-Out
Schieberegist
er
Asynchroner
Reset
[Bearbeiten]Aufbau 74xx595
Hinweis: Die Benennung der Pins in den Datenblttern verschiedener Hersteller unterscheidet
sich zum Teil. Die Funktionen der Pins sind jedoch gleich.
Achtung: Es gibt auch noch einen IC von TI mit eingebauten Treibern 50 V 150 mA, den
TPIC6B595, der hat 20 Pins und eine abweichende
Pinbelegung http://www.ti.com/product/tpic6b595
[Bearbeiten]HC
oder HCT?
Mal gibt es 74HC595, mal 74HCT595. Diese beiden Typen unterscheiden sich nur bei der
Festlegung der Schaltschwelle an Eingangspins:
Es gibt auch 74LS595. In der modernen CMOS-Welt sollte man sich Low-Power-Schottky (=
bipolar = Ruhestrom fressend) nicht mehr antun.
Im allgemeinen kann man alle Typen gleichermaen verwenden und nimmt einfach den
billigsten oder verfgbarsten. Nur beim bergang zu echtem TTL oder verschiedenen
Speisespannungen fr Mikrocontroller und Portexpander wird es interessant.
Ein hufiges Szenario ist ein Mikrocontroller mit 3-V-Speisung (etwa ein ARM7 oder MSP430).
Dann kann man mit einem 74HCT595, an 5 V betrieben, echte 5-V-Ausgnge und die
Pegelkonvertierung dazu haben. 74HC595 funktionieren hier nur mit Glck, und bei noch
geringerer Speisespannung des Controllers etwa 2,5 V gar nicht.
[Bearbeiten]Pinbelegung 74xx595
DIL
Pin-
Funktion
Dieses
Numm
er
Tutorial
Semi
NXP
ild
nts
Ausgang B
QB
QB
Q1
QB
QB
QB
Ausgang C
QC
QC
Q2
QC
QC
QC
Ausgang D
QD
QD
Q3
QD
QD
QD
Ausgang E
QE
QE
Q4
QE
QE
QE
Ausgang F
QF
QF
Q5
QF
QF
QF
Ausgang G
QG
QG
Q6
QG
QG
QG
Ausgang H
QH
QH
Q7
QH
QH
QH
Masse, 0 V
[nicht
dargestel GND
lt]
GND
GND
GN
D
GND
Serieller
Ausgang
QH*
Q7
Q'H
QH QH'
10
Reset fr
SCL
Schieberegister
RESET
/MR
/SCLR
/
SCL /SRCLR
R
11
Schiebetakt
SCK
SHIFT
CLOCK
SHCP
SCK
SCK SRCLK
12
Speichertakt
RCK
LATCH
STCP
RCK
RCK RCLK
SQH
CLOCK
13
Ausgangssteue
G
rung
OUTPUT
/OE
ENABLE
/G
/G
/OE
14
Serieller
Dateneingang
SER
DS
SER
SI
SER
15
Ausgang A
QA
QA
Q0
QA
QA
QA
16
[nicht
Betriebsspannu
dargestel VCC
ng
lt]
VCC
VCC
VCC
VCC
dem Schieberegister
dem Ausgangsregister
Eine Sonderstellung nimmt das ursprngliche Bit 7 ein. Dieses Bit steht direkt auch am
Ausgang QH* (Pin 9) zur Verfgung. Dadurch ist es mglich an ein Schieberegister einen
weiteren Baustein 74xxx595 anzuschlieen und so beliebig viele Schieberegister hintereinander
zu schalten (kaskadieren). Auf diese Art lassen sich Schieberegister mit beliebig vielen Stufen
aufbauen.
Wurde das Schieberegister mit den Daten gefllt, so wird mit einem LOW-HIGH Puls am Pin
12, RCK der Inhalt des Schieberegisters in das Ausgangsregister bernommen.
Mit dem Eingang G (Pin 13) kann das Ausgangsregister freigegeben werden. Liegt G auf 0, so
fhren die Ausgnge QA bis QH entsprechende Pegel. Liegt G auf 1, so schalten die
Ausgnge QA bisQH auf Tristate. D.h. sie treiben aktiv weder LOW oder HIGH, sondern sind
hochohmig wie ein Eingang und nehmen jeden Pegel an, der ihnen von auen aufgezwungen
wird.
Bleibt nur noch der Eingang SCL(Pin 10). Mit ihm kann das Schieberegister im Baustein
gelscht, also auf eine definierte 0, gesetzt werden.
Die Programmierung eines 74xxx595 Schieberegisters gestaltet sich sehr einfach. Im Grunde
gibt es 2 Mglichkeiten:
Mittels SPI kann der AVR das Schieberegister direkt und autark ansteuern. Das ist sehr
schnell und verbraucht nur wenig CPU-Leistung
Sind die entsprechenden SPI-Pins am AVR nicht frei, so ist auch eine softwaremige
Ansteuerung des Schieberegisters mit einfachen Mitteln durchfhrbar.
Die Programmierung gestaltet sich dann nach folgendem Schema: Die 8 Bits eines Bytes
werden nacheinander an den Ausgang PB0 (SER) ausgegeben. Durch Generierung eines
Pulses 0-1-0 an Pin PB1 (SCK) bernimmt das Schieberegister nacheinander die einzelnen
Bits. Dabei ist zu beachten, dass die Ausgabe mit dem hherwertigen Bit beginnen muss, denn
dieses Bit wandert ja am weitesten zur Stelle QH. Sind alle 8 Bits ausgegeben, so wird durch
einen weiteren 0-1-0 Impuls am Pin PB3 (RCK) der Inhalt der Schieberegisterbits 0 bis 7 in die
Ausgaberegister QA bis QH bernommen. Dadurch, dass am Schieberegister der
Eingang G konstant auf 0-Pegel gehalten wird, erscheint dann auch die Ausgabe sofort an den
entsprechenden Pins und kann zb. mit LEDs (low-current LEDs + Vorwiderstand verwenden)
sichtbar gemacht werden.
Der Schieberegistereingang SCL wird auf einer 1 gehalten. Wrde er auf 0 gehen, so wrde die
Schieberegisterkette gelscht. Mchte man einen weiteren Prozessorpin einsparen, so kann
man diesen Pin auch generell auf Vcc legen. Das Schieberegister knnte man in so einem Fall
durch Einschreiben von 0x00 immer noch lschen.
.include "m8def.inc"
.def temp1 = r16
.def temp2 = r17
.equ
.equ
.equ
.equ
.equ
.equ
SCHIEBE_DDR
SCHIEBE_PORT
RCK
SCK
SCL
SIN
ldi
out
ldi
out
=
=
=
=
=
=
DDRB
PORTB
3
1
2
0
temp1, LOW(RAMEND)
SPL, temp1
temp1, HIGH(RAMEND)
SPH, temp1
; Stackpointer initialisieren
;
; Die Port Pins auf Ausgang konfigurieren
;
ldi
temp1, (1<<RCK) | (1<<SCK) | (1<<SCL) | (1<<SIN) ; Anm.1
out
SCHIEBE_DDR, temp1
;
; die Clear Leitung am Schieberegister auf 1 stellen
;
sbi
SCHIEBE_PORT, SCL
;
; Ein Datenbyte ausgeben
;
ldi
temp1, 0b10101010
rcall Schiebe
rcall SchiebeOut
loop:
rjmp
loop
;----------------------------------------------------------------------------;
temp2
Schiebe_1
pop
ret
temp2
Noch schneller geht die Ansteuerung des Schieberegisters mittels SPI-Modul, welches in fast
allen AVRs vorhanden ist. Hier wird der Pin SCL nicht benutzt, da das praktisch keinen Sinn
hat. Er muss also fest auf VCC gelegt werden. (Oder mit den Reset-Pin des AVRs, das mit einer
RC Schaltung versehen ist, verbunden werden. Damit erreicht man einen definierten
Anfangszustand des Schieberegisters.) Die Pins fr SCK und SIN sind durch den jeweiligen
AVR fest vorgegeben. SCK vom 74xxx595 wird mit SCK vom AVR verbunden
sowie SIN mit MOSI (Master Out, Slave In). MISO (Master In, Slave Out) ist hier ungenutzt. Es
kann NICHT fr RCKverwendet werden, da es im SPI-Master Modus immer ein Eingang ist! Es
kann aber als allgemeiner Eingang oder fr 74HC165 (siehe unten) verwendet werden.
Der AVR-Pin SS wird sinnvollerweise als RCK benutzt, da er sowieso als Ausgang geschaltet
werden muss, sonst gibt es bse beraschungen (siehe Datenblatt SS Pin Functionality).
Dieser sollte mit einem Widerstand von 10 k nach Masse, whrend der Start- und
Initialisierungsphase, auf L-Potential gehalten werden. `(SS ist whrend dieser Zeit noch im TriState und es knnte passieren, dass die zuflligen Daten des Schieberegisters in das
Ausgangslatch bernommen werden.)
Bei den kleineren ATtinys mit USI (Universal Serial Interface) darf man sich von den PinBezeichnungen MOSI und MISO nicht ins Bockshorn jagen lassen: Hier ist MISO der
Ausgang(!) DO und MOSI der Eingang(!) DI. Die Pinbezeichnungen MOSI und MISO sind nur
zum Programmieren und irrefhrend, weil ohnehin nur fr den Slave-Betrieb.
Je nach Bedarf kann man die Taktrate des SPI-Moduls zwischen 1/2 ... 1/128 des CPU-Taktes
whlen. Es spricht kaum etwas dagegen mit maximaler Geschwindigkeit zu arbeiten. Die AVRs
knnen zur Zeit mit maximal 20 MHz getaktet werden, d.h. es sind maximal 10 MHz SPI-Takt
mglich. Das ist fr ein 74xxx595 kein Problem. Die bertragung von 8 Bit dauert dann gerade
mal 800 ns!
.include "m8def.inc"
.def temp1 = r16
; Die Definitionen mssen an den jeweiligen AVR angepasst werden
.equ
.equ
.equ
.equ
.equ
SCHIEBE_DDR
SCHIEBE_PORT
RCK
SCK
SIN
ldi
out
ldi
out
=
=
=
=
=
DDRB
PORTB
PB2
PB5
PB3
; SS
; SCK
; MOSI
temp1, LOW(RAMEND)
SPL, temp1
temp1, HIGH(RAMEND)
SPH, temp1
; Stackpointer initialisieren
;
; SCK, MOSI, SS als Ausgnge schalten
;
in
temp1, SCHIEBE_DDR
ori
temp1, (1<<SIN) | (1<<SCK) | (1<<RCK)
out
SCHIEBE_DDR,temp1
;
; SPI Modul konfigurieren
;
ldi
temp1, (1<<SPE) | (1<<MSTR)
out
SPCR, temp1
; keine Interrupts, MSB first, Master
; CPOL = 0, CPHA =0
; SCK Takt = 1/2 XTAL
ldi
temp1, (1<<SPI2X)
out
SPSR, temp1
; double speed aktivieren
out
SPDR, temp1
; Dummy Daten, um SPIF zu setzen
;
; Ein Datenbyte ausgeben
;
ldi
temp1, 0b10101010
rcall Schiebe
; Daten schieben
rcall SchiebeOut
; Daten in Ausgangsregister bernehmen
loop:
rjmp
loop
;----------------------------------------------------------------------------;
; Die Daten im Schieberegister in das Ausgaberegister bernehmen
;
; Dazu am RCK Eingang am Schieberegister einen 0-1-0 Puls erzeugen
;
SchiebeOut:
sbis SPSR, SPIF
; prfe ob eine alte bertragung beendet ist
rjmp SchiebeOut
sbi
SCHIEBE_PORT, RCK
cbi
SCHIEBE_PORT, RCK
ret
;----------------------------------------------------------------------------;
; 8 Bits aus temp1 an das Schieberegister ausgeben
;
Schiebe:
sbis
SPSR, SPIF
; prfe ob eine alte bertragung beendet ist
rjmp
Schiebe
out
SPDR, temp1
; Daten ins SPI Modul schreiben, bertragung beginnt autom
ret
[Bearbeiten]Kaskadieren von Schieberegistern
bertragung eines Bytes luft, dazu genutzt wird, den Schleifenzhler zu verringern und zu
prfen sowie neue Sendedaten zu laden. Zwischen den einzelnen Bytes gibt es somit nur eine
Pause von max. 6 Systemtakten.
.include "m8def.inc"
.def temp1 = r16
; Die Definitionen mssen an den jeweiligen AVR angepasst werden
.equ
.equ
.equ
.equ
.equ
SCHIEBE_DDR
SCHIEBE_PORT
RCK
SCK
SIN
=
=
=
=
=
DDRB
PORTB
PB2
PB5
PB3
; SS
; SCK
; MOSI
;----------------------------------------------------------------------------;
; Datensegment im RAM
;
;----------------------------------------------------------------------------.dseg
.org $60
Schiebedaten:
.byte 2
;----------------------------------------------------------------------------;
; Programmsegment im FLASH
;
;----------------------------------------------------------------------------.cseg
ldi
temp1, LOW(RAMEND)
; Stackpointer initialisieren
out
SPL, temp1
ldi
temp1, HIGH(RAMEND)
out
SPH, temp1
;
; SCK, MOSI, SS als Ausgnge schalten
;
in
temp1,SCHIEBE_DDR
ori
temp1,(1<<SIN) | (1<<SCK) | (1<<RCK)
out
SCHIEBE_DDR,temp1
sbi
;
; SPI Modul
;
ldi
out
ldi
out
out
SCHIEBE_PORT, RCK
konfigurieren
temp1, 0b01010000
SPCR, temp1
r16,1
SPSR,r16
SPDR,temp1
temp1,$F0
Schiebedaten,temp1
temp1,$55
sts
Schiebedaten+1,temp1
loop:
; den Datenblock ausgeben
ldi
ldi
ldi
rcall
rjmp
r16,2
zl,low(Schiebedaten)
zh, high(Schiebedaten)
Schiebe_alle
loop
; Daten ausgeben
; nur zur Simulation
;----------------------------------------------------------------------------;
; N Bytes an das Schieberegister ausgeben und in das Ausgaberegister bernehmen
;
; r16: Anzahl der Datenbytes
; Z: Zeiger auf Datenblock im RAM
;
;----------------------------------------------------------------------------Schiebe_alle:
cbi
SCHIEBE_PORT, RCK
; RCK LOW, SPI Standardverfahren
push
r17
Schiebe_alle_2:
ld
r17,Z+
Schiebe_alle_3:
sbis
SPSR,SPIF
rjmp
Schiebe_alle_3
out
SPDR,r17
dec
r16
brne
Schiebe_alle_2
Schiebe_alle_4:
sbis
SPSR,SPIF
rjmp
Schiebe_alle_4
pop
sbi
ret
r17
SCHIEBE_PORT, RCK
Der Nachteil von Schieberegistern ist allerdings, dass sich die Zeit zum Setzten aller
Ausgabeleitungen mit jedem weiteren Baustein immer weiter erhht. Dies deshalb, da ja die
einzelnen Bits im Gnsemarsch durch alle Bausteine geschleust werden mssen und fr jeden
einzelnen Schiebevorgang etwas Zeit notwendig ist. Ein Ausweg ist die Verwendung des SPIModuls, welches schneller arbeitet als die reine Softwarelsung. Ist noch mehr Geschwindigkeit
gefragt, so sind mehr Port-Pins ntig. Kann ein kompletter Port mit 8 Pins fr die Daten genutzt
werden, sowie ein paar weitere Steuerleitungen, so knnen ein oder mehrere 74xxx573 eine
Alternative sein, um jeweils ein vollstndiges Byte auszugeben. Natrlich kann der 74xxx573
(oder ein hnliches Schieberegister) auch mit dem 74xxx595 zusammen eingesetzt werden,
beispielsweise in dem ber das Schieberegister verschiedene 74xxx595 nacheinander aktiviert
werden. Weitere Tips und Tricks dazu gibt es vielleicht in einem weiteren Tutorial...
[Bearbeiten]Acht LEDs mit je 20mA pro Schieberegister
Will man nun acht LEDs mit dem Schieberegister ansteuern, kann man diese direkt ber
Vorwiderstnde anschlieen. Doch ein genauer Blick ins Datenblatt verrt, dass der 74xx595
nur maximal 70mA ber VCC bzw. GND ableiten kann. Und wenn man den IC nicht gnadenlos
qulen, und damit die Lebensdauer und Zuverlssigkeit drastisch reduzieren will, gibt es nur
zwei Auswege.
Den Strom pro LED auf 70/8 = 8,75mA begrenzen; Das reicht meistens aus um die
LEDs schn leuchten zu lassen, vor allem bei low-current und ultrahellen LEDs
Wenn doch 20 mA pro LED gebraucht werden, kann man die folgende Trickschaltung
anwenden.
Der Trick besteht darin, dass 4 LEDs ihren Strom ber das Schieberegister von VCC beziehen
(HIGH aktiv) whrend die anderen vier ihren Strom ber GND leiten (LOW aktiv). Damit bleiben
ganz offiziell fr jede LED 70/4 = 17,5mA. Um die Handhabung in der Software zu vereinfachen
muss nur vor der Ausgabe der Daten das jeweilige Byte mit 0x0F XOR verknpft werden, bevor
es in das Schieberegister getaktet wird. Dadurch werden die LOW-aktiven LEDs richtig
angesteuert und die Datenhandhabung in der Software muss nur mit HIGH-aktiven rechnen.
Auerdem wird der G Eingang verwendet, um die Helligkeit aller LEDs per PWM zu steuern.
Beachtet werden muss, dass die PWM im invertierten Modus generiert werden muss, da der
Eingang G LOW aktiv ist.
Achtung! Die Widerstnde sind auf blaue LEDs mit 3,3V Flussspannung ausgelegt. Bei roten,
gelben und grnen LEDs ist die Flussspannung geringer und dementsprechend muss der
Vorwiderstand grer sein.
Wenn 20mA immer noch nicht reichen sollten oder z.B. RGB-LEDs mit gemeinsamer Anode
angesteuert werden mssen, dann hilft nur ein strkerer IC. Der Klassiker ist der TPIC6A595
von TI, er kombiniert ein Schieberegister mit MOSFETs, sodass hier 250mA pro Kanal zur
Verfgung stehen.
[Bearbeiten]Porterweiterung
fr Eingnge
Ein naher Verwandter des 74xx595 ist der 74xx165, er ist quasi das Gegenstck. Hierbei
handet es sich um ein 8-bit parallel-in/serial-out shift register. Auf Deutsch: Ein 8 Bit
Schieberegister mit parallelem Eingang und seriellem Ausgang. Damit kann man eine groe
Anzahl Eingnge sehr einfach und preiswert zu seinem Mikrocontroller hinzufgen.
[Bearbeiten]Aufbau
Der Aufbau ist sehr hnlich zum 74xx595. Allerdings gibt es kein Register zum
Zwischenspeichern. Das ist auch gar nicht ntig, da der IC ja einen parallelen Eingang hat. Der
muss nicht zwischengespeichert werden. Es gibt hier also wirklich nur das Schieberegister.
Dieses wird ber den Eingang PL mit den parallelen Daten geladen. Dann knnen die Daten
seriell mit Takten an CLK aus dem Ausgang Q7 geschoben werden.
[Bearbeiten]Funktionsweise
DS ist der serielle Dateneingang, welcher im Falle von kaskadierten Schieberegistern mit dem
Ausgang des vorhergehenden ICs verbunden wird.
D0..D7 sind die parallelen Dateneingnge.
Mittels des Eingangs PL (Parallel Load) werden die Daten vom parallelen Eingang in das
Schieberegister bernommen, wenn dieses Signal LOW ist. Hier muss man aber ein klein wenig
aufpassen. Auf Grund der Schaltungsstruktur ist der Eingang PL mit dem Takt CLK verknpft
(obwohl es dafr keinen logischen Grund gibt :-0). Damit es nicht zu unerwnschten
Fehlschaltungen kommt, muss der Takt CLK whrend des Ladens auf HIGH liegen. Wird PL
wieder auf HIGH gesetzt, sind die Daten geladen. Das erste Bit liegt direkt am Ausgang Q7 an.
Die restlichen Bits knnen nach und nach durch das Register geschoben werden.
Der Eingang CE (Clock Enable) steuert, ob das Schieberegister auf den Takt CLK reagieren soll
oder nicht. Ist CE gleich HIGH werden alle Takte an CLK ignoriert. Bei LOW werden mit jeder
positiven Flanke die Daten um eine Stufe weiter geschoben.
Wird am Eingang CLK eine LOW-HIGH Flanke angelegt und ist dabei CE auf LOW, dann
werden die Daten im Schieberegister um eine Position weiter geschoben: DS->Q0, Q0->Q1,
Q1->Q2, Q2->Q3, Q3->Q4, Q4->Q5, Q5->Q6, Q6->Q7. Q0..Q6 sind interne Signale,
siehe Datenblatt.
Q7 ist der serielle Ausgang des Schieberegisters. Dort Werden Takt fr Takt die Daten
ausgegeben. Hier wird normalerweise der Eingang des Mikrocontrollers oder der Eingang des
nchsten Schieberegisters angeschlossen.
Q7\ ist der invertierte Ausgang des Schieberegisters. Er wird meist nicht verwendet.
[Bearbeiten]Schaltung
Um nun beispielsweise zwei Schieberegister zu kaskadieren um 16 Eingangspins zu erhalten
sollte man folgende Verschaltung vornehmen. Beachten sollte man dabei, dass
der serielle Eingang DS des ersten Schieberegisters (hier IC1) auf einen festen Pegel
gelegt wird (LOW oder HIGH).
der serielle Datenausgang bei der Benutzung des SPI-Moduls an MISO und nicht an
MOSI angeschlossen wird.
Nachfolgend werden zwei Beispiele gezeigt, welche die Ansteuerung nach bekanntem Muster
bernehmen. Nur dass hier eben Daten gelesen anstatt geschrieben werden. Zu beachten ist,
dass hier ein anderer Modus der SPI-Ansteuerung verwendet werden muss, weil der Baustein
das ntig macht. Das muss beachtet werden, wenn auch Schieberegister fr Ausgnge
verwendet werden. Dabei muss jeweils vor dem Zugriff auf die Ein- oder Ausgangsregister der
Modus des Taktes (CPOL) umgeschaltet werden.
[Bearbeiten]Ansteuerung per Software
; Porterweiterung fr Eingnge mit Schieberegister 74xx165
; Ansteuerung per Software
.include "m8def.inc"
.def temp1 = r16
.def temp2 = r17
.def temp3 = r18
; Pins anpassen, frei whlbar
.equ SCHIEBE_DDR = DDRB
.equ SCHIEBE_PORT = PORTB
.equ
.equ
.equ
.equ
SCHIEBE_PIN
CLK
PL
DIN
=
=
=
=
PINB
PB3
PB1
PB2
;----------------------------------------------------------------------------;
; Datensegment im RAM
;
;----------------------------------------------------------------------------.dseg
.org 0x60
Daten:
.byte 2
; Speicherplatz fr Eingangsdaten
;----------------------------------------------------------------------------;
; Programmsegment im FLASH
;
;----------------------------------------------------------------------------.cseg
ldi
temp1, LOW(RAMEND) ; Stackpointer initialisieren
out
SPL, temp1
ldi
temp1, HIGH(RAMEND)
out
SPH, temp1
; CLK und PL als Ausgnge schalten
ldi
out
sbi
schiebe_port, clk
ZL,low(Daten)
ZH,high(Daten)
temp1,2
schiebe_eingang
loop
;----------------------------------------------------------------------------;
; N Bytes seriell einlesen
;
; temp1 : N, Anzahl der Bytes
; Z
: Zeiger auf einen Datenbereich im SRAM
;----------------------------------------------------------------------------schiebe_eingang:
push
temp2
push
temp3
cbi
sbi
schiebe_port, pl
schiebe_port, pl
schiebe_eingang_byte_schleife:
; Register sichern
; Daten parallel laden
ldi
temp3, 8
schiebe_eingang_bit_schleife:
lsl
temp2
; Bitzhler
; Daten weiterschieben
schiebe_pin, din
temp2,1
cbi
sbi
SCHIEBE_PORT, CLK
SCHIEBE_PORT, CLK
; Taktausgang auf 0
; und wieder zurck auf 1, dabei Daten schieben
dec
brne
temp3
; Bitzhler um eins verringern
schiebe_eingang_bit_schleife ;wenn noch keine 8 Bits ausgegeben, nochmal
st
dec
brne
z+,temp2
; Datenbyte speichern
temp1
; Anzahl Bytes um eins verringern
schiebe_eingang_byte_schleife
; wenn noch mehr Bytes zu lesen sind
pop
pop
ret
temp3
temp2
SCHIEBE_DDR
SCHIEBE_PORT
PL
CLK
DIN
=
=
=
=
=
DDRB
PORTB
PB2
PB5
PB4
; SS
; SCK
; MISO
;----------------------------------------------------------------------------;
; Datensegment im RAM
;
;----------------------------------------------------------------------------.dseg
.org 0x60
Daten:
.byte 2
; Speicherplatz fr Eingangsdaten
;----------------------------------------------------------------------------;
; Programmsegment im FLASH
;
;----------------------------------------------------------------------------.cseg
ldi
temp1, LOW(RAMEND) ; Stackpointer initialisieren
out
SPL, temp1
ldi
temp1, HIGH(RAMEND)
out
SPH, temp1
temp1,(1<<CLK) | (1<<PL)
SCHIEBE_DDR,temp1
;
; SPI Modul konfigurieren
;
ldi
temp1, (1<<SPE) | (1<<MSTR) | (1<<CPOL)
out
SPCR, temp1
; keine Interrupts, MSB first, Master
; CPOL = 1, CPHA =0
; SCK Takt = 1/2 XTAL
ldi
temp1, (1<<SPI2X)
out
SPSR,temp1
; double speed aktivieren
out
SPDR,temp1
; Dummy Daten, um SPIF zu setzen
; Zwei Bytes einlesen
ldi
ldi
ldi
rcall
loop:
rjmp
ZL,low(Daten)
ZH,high(Daten)
temp1,2
schiebe_eingang
loop
;----------------------------------------------------------------------------;
; N Bytes seriell einlesen
;
; temp1 : N, Anzahl der Bytes
; Z
: Zeiger auf einen Datenbereich im SRAM
;----------------------------------------------------------------------------schiebe_eingang:
push
temp2
; Register sichern
; CLK ist im Ruhezustand schon auf HIGH, CPOL=1
dummyende:
sbis
rjmp
cbi
sbi
SPSR,7
dummyende
schiebe_port, pl
schiebe_port, pl
schiebe_eingang_1:
sbis
SPSR,7
rjmp
schiebe_eingang_1
schiebe_eingang_byte_schleife:
out
SPDR,temp1
schiebe_eingang_2:
sbis
SPSR,7
rjmp
schiebe_eingang_2
in
st
temp2, spdr
z+,temp2
dec
brne
temp1
; Anzahl Bytes um eins verringern
schiebe_eingang_byte_schleife
; wenn noch mehr Bytes zu lesen sind
pop
ret
temp2
[Bearbeiten]Bekannte
Probleme
AVR Studio 4.12 (Build 498) hat Probleme bei der korrekten Simulation des SPI-Moduls.
Das Bit SPIF im Register SPSR, welches laut Dokumentation nur lesbar ist, ist im
Simulator auch schreibbar! Das kann zu Verwirrung und Fehlern in der Simulation
fhren.
Hardwareprobleme
Wenn das SPI-Modul aktiviert wird, wird NICHT automatisch SPIF gesetzt, es bleibt auf
Null. Damit wrde die erste Abfrage in Schiebe_alles in einer Endlosschleife hngen
bleiben. Deshalb muss nach der Initialisierung des SPI-Moduls ein Dummy-Byte
gesendet werden, damit am Ende der bertragung SPIF gesetzt wird
Da das SPI-Modul in Senderichtung nur einfach gepuffert ist, ist es nicht mglich
absolut lckenlos Daten zu senden, auch wenn man mit nop eine feste minimale Zeit
zwischen zwei Bytes warten wrde. Zwischen zwei Bytes muss immer eine Pause von
mind. 2 Systemtakten eingehalten werden.
[Bearbeiten]Weblinks
AVR151: Setup And Use of The SPI Atmel Application Note (PDF)
datasheetcatalog.com: 74HC595
AVR-Tutorial: SRAM
Inhaltsverzeichnis
[Verbergen]
1 SRAM - Der Speicher des Controllers
2 Das .DSEG und .BYTE
3 spezielle Befehle
o
3.1 LDS
3.2 STS
3.3 Beispiel
4 Spezielle Register
4.2 LD
4.3 LDD
4.4 ST
4.5 STD
4.6 Beispiel
[Bearbeiten]SRAM
Nachdem in einem der vorangegangenen Kapitel eine Software-PWM vorgestellt und in einem
weiteren Kapitel darber gesprochen wurde, wie man mit Schieberegistern die Anzahl an I/OPins erhhen kann, wre es naheliegend, beides zu kombinieren und den ATmega8 mal 20
oder 30 LEDs ansteuern zu lassen. Wenn es da nicht ein Problem gbe: die Software-PWM hlt
ihre Daten in Registern, so wie das praktisch alle Programme bisher machten. Whrend
allerdings 6 PWM-Kanle noch problemlos in den Registern untergebracht werden konnten, ist
dies mit 30 oder noch mehr PWM-Kanlen nicht mehr mglich. Es gibt schlicht und ergreifend
nicht genug Register.
Es gibt aber einen Ausweg. Der ATmega8 verfgt ber 1kByte SRAM (statisches RAM). Dieses
RAM wurde bereits indirekt durch den Stack benutzt. Bei jedem Aufruf eines Unterprogrammes,
sei es ber einen expliziten CALL (bzw. RCALL) oder einen Interrupt, wird die
Um dem Assembler mitzuteilen, dass sich der folgende Abschnitt auf das SRAM bezieht, gibt es
die Direktive .DSEG (Data Segment). Alle nach einer .DSEG Direktive folgenden
Speicherreservierungen werden vom Assembler im SRAM durchgefhrt.
Die Direktive .BYTE stellt dabei eine derartige Speicherreservierung dar. Es ermglicht, der
Speicherreservierung einen Namen zu geben und es erlaubt auch, nicht nur 1 Byte sondern
eine ganze Reihe von Bytes unter einem Namen zu reservieren.
.DSEG
.BYTE
.BYTE
Counter:
Test:
1
20
[Bearbeiten]spezielle
Befehle
Fr den Zugriff auf den SRAM-Speicher gibt es spezielle Befehle. Diese holen entweder den
momentanen Inhalt einer Speicherzelle und legen ihn in einem Register ab oder legen den
Inhalt eines Registers in einer SRAM-Speicherzelle ab.
[Bearbeiten]LDS
Liest die angegebene SRAM-Speicherzelle und legt den gelesenen Wert in einem Register ab.
LDS
r17, Counter
[Bearbeiten]STS
Legt den in einem Register gespeicherten Wert in einer SRAM-Speicherzelle ab.
STS
Counter, r17
[Bearbeiten]Beispiel
Eine mgliche Implementierung der Software-PWM, die den PWM-Zhler sowie die einzelnen
OCR-Grenzwerte im SRAM anstelle von Registern speichert, knnte z. B. so aussehen:
.include "m8def.inc"
main
; Reset Handler
timer0_overflow
main:
ldi
out
ldi
out
temp, HIGH(RAMEND)
SPH, temp
temp, LOW(RAMEND)
SPL, temp
ldi
out
temp, 0xFF
DDRB, temp
ldi
sts
ldi
sts
ldi
sts
ldi
sts
ldi
sts
ldi
sts
temp2,
OCR_1,
temp2,
OCR_2,
temp2,
OCR_3,
temp2,
OCR_4,
temp2,
OCR_5,
temp2,
OCR_6,
ldi
out
temp, (1<<CS00)
TCCR0, temp
ldi
out
temp, (1<<TOIE0)
TIMSK, temp
; Stackpointer initialisieren
; Port B auf Ausgang
0
temp2
1
temp2
10
temp2
20
temp2
80
temp2
127
temp2
sei
loop:
rjmp
loop
timer0_overflow:
lds
temp1, PWMCount
inc
temp1
cpi
temp1, 128
brne
WorkPWM
clr
temp1
WorkPWM:
OneOn:
sts
ldi
PWMCount, temp1
temp, 0b11000000
lds
cp
brlt
ori
temp2, OCR_1
temp1, temp2
OneOn
temp, $01
lds
temp2, OCR_2
;
;
;
;
;
;
cp
brlt
ori
temp1, temp2
TwoOn
temp, $02
lds
cp
brlt
ori
temp2, OCR_3
temp1, temp2
ThreeOn
temp, $04
ThreeOn:lds
cp
brlt
ori
temp2, OCR_4
temp1, temp2
FourOn
temp, $08
FourOn: lds
cp
brlt
ori
temp2, OCR_5
temp1, temp2
FiveOn
temp, $10
FiveOn: lds
cp
brlt
ori
temp2, OCR_6
temp1, temp2
SetBits
temp, $20
TwoOn:
SetBits:
PORTB, temp
reti
.DSEG
PWMCount:
OCR_1:
OCR_2:
OCR_3:
OCR_4:
OCR_5:
OCR_6:
.BYTE
.BYTE
.BYTE
.BYTE
.BYTE
.BYTE
.BYTE
[Bearbeiten]Spezielle
Register
[Bearbeiten]LD
LD rxx, Z
LD rxx, Z+
LD rxx, -Z
Ldt das Register rxx mit dem Inhalt der Speicherzelle, deren Adresse im Z-Pointer angegeben
ist. Bei den Varianten mit Z+ bzw. -Z wird zustzlich der Z-Pointer nach der Operation um 1
erhht bzw. vor der Operation um 1 vermindert.
[Bearbeiten]LDD
Hier erfolgt der Zugriff wieder ber den Z-Pointer wobei vor dem Zugriff zur Adressangabe
im Z-Pointer noch das Displacement q addiert wird.
Enthlt also der Z-Pointer die Adresse $1000 und sei q der Wert $28, so wird mit einer
Ladeanweisung
LDD r18, Z + $28
der Inhalt der Speicherzellen $1000 + $28 = $1028 in das Register r18 geladen.
Der Wertebereich fr q erstreckt sich von 0 bis 63.
[Bearbeiten]ST
ST Z, rxx
ST Z+, rxx
ST -Z, rxx
Speichert den Inhalt des Register rxx in der Speicherzelle, deren Adresse im ZPointer angegeben ist. Bei den Varianten mit Z+ bzw. -Z wird zustzlich der ZPointer nach der Operation um 1 erhht bzw. vor der Operation um 1 vermindert.
[Bearbeiten]STD
Hier erfolgt der Zugriff wieder ber den Z-Pointer wobei vor dem Zugriff zur Adressangabe
im Z-Pointer noch das Displacement q addiert wird.
Enthlt also der Z-Pointer die Adresse $1000 und sei q der Wert $28, so wird mit einer
Speicheranweisung
temp
temp1
temp2
temp3
=
=
=
=
.org 0x0000
rjmp
.org OVF0addr
rjmp
r16
r17
r18
r19
main:
main
; Reset Handler
timer0_overflow
ldi
out
ldi
out
temp, HIGH(RAMEND)
SPH, temp
temp, LOW(RAMEND)
SPL, temp
ldi
out
temp, 0xFF
DDRB, temp
ldi
ldi
r30,LOW(OCR)
r31,HIGH(OCR)
ldi
st
ldi
st
ldi
st
ldi
st
ldi
st
ldi
st
temp2, 0
Z+, temp2
temp2, 1
Z+, temp2
temp2, 10
Z+, temp2
temp2, 20
Z+, temp2
temp2, 80
Z+, temp2
temp2, 127
Z+, temp2
ldi
sts
temp2, 0
PWMCount, temp2
; Stackpointer initialisieren
ldi
out
temp, (1<<CS00)
TCCR0, temp
ldi
out
temp, (1<<TOIE0)
TIMSK, temp
sei
loop:
rjmp
loop
timer0_overflow:
lds
temp1, PWMCount
inc
temp1
cpi
temp1, 128
brne
WorkPWM
clr
temp1
WorkPWM:
sts
PWMCount, temp1
ldi
ldi
ldi
ldi
r30,LOW(OCR)
r31,HIGH(OCR)
temp3, $01
temp, 0b11000000
ld
cp
brlo
or
temp2, Z+
temp1, temp2
LedOn
temp, temp3
lsl
cpi
brne
temp3
temp3, $40
pwmloop
out
reti
PORTB, temp
pwmloop:
LedOn:
;
;
;
;
;
;
.DSEG
PWMCount: .BYTE
OCR:
.BYTE
[Bearbeiten]X-Pointer, Y-Pointer
Neben dem Z-Pointer gibt es noch den X-Pointer bzw. Y-Pointer. Sie werden gebildet von den
Registerprchen
Alles ber den Z-Pointer gesagte gilt sinngem auch fr den X-Pointer bzw. Y-Pointer mit
einer Ausnahme: Mit dem X-Pointer ist kein Zugriff LDD/STD mit einem Displacement mglich.
[Bearbeiten]Siehe
Adressierung
auch
Adressierung
Mikrocontroller und -prozessoren bieten in der Regel mehrere Mglichkeiten an, um auf Daten
zuzugreifen. An dieser Stelle sollen die grundlegenden Adressierungsarten der AVR-Controller
mit internem SRAM behandelt werden.
Inhaltsverzeichnis
[Verbergen]
1 Immediate-Werte
2 Direkte Adressierung
3 Indirekte Adressierung
o
3.1 Postinkrement
3.2 Predekrement
[Bearbeiten]Immediate-Werte
Eigentlich keine Adressierungsart, aber dennoch sehr wichtig, ist die Mglichkeit direkt
konstante Werte in ein Register zu schreiben. Dabei ist schon zur Entwicklungszeit bekannt,
welcher Wert in welches Register geladen werden soll.
ldi
r16, 0xA0
ldi steht hierbei fr load immediate. Bei AVR-Mikrocontrollern ist das direkte Laden von Werten
nur mit den Registern r16 bis r31 mglich.
[Bearbeiten]Direkte
Adressierung
Um auf Daten im Speicher zuzugreifen, muss man selbstverstndlich wissen, wo sich diese
Daten befinden. Will man z. B. den Inhalt eines Registers in eine Speicherzelle schreiben, so
muss das Mikroprogramm die Adresse der gewnschten Speicherzelle kennen. Eine einfache
Mglichkeit der Adressierung ist es, dem Befehl die Adresse direkt mitzuteilen.
.dseg
variable:
.cseg
ldi
sts
.byte 1
r16, 25
variable, r16
;
;
;
;
;
;
;
;
Die Adresse der Speicherzelle wird also schon zur Entwicklungszeit im Assembler-Befehl
eingetragen, was nach sich zieht, dass so ein Befehl nur auf Speicherzellen zugreifen kann,
deren Adressen schon im Vorhinein bekannt sind. Da variable in obigem Beispiel eine Adresse
und somit nur eine Zahl darstellt, kann man zur Entwicklungszeit auch Konstanten addieren:
.dseg
variable2: .byte 2
.cseg
ldi
sts
inc
sts
r16, 17
variable2, r16
r16
variable2+1, r16
;
;
;
;
Nun steht in diesem Beispiel im ersten Byte die Zahl 17 und im zweiten Byte die Zahl 18. Dabei
muss man beachten, dass die Addition im sts-Befehl bereits whrend der Assemblierung und
nicht vom Mikrocontroller durchgefhrt wird. Das ist der Fall, weil die Adresse der reservierten
Speicherzelle schon zu dieser Zeit berechnet worden ist. Somit ist natrlich auch die Adresse +
1 bekannt.
[Bearbeiten]Indirekte
Adressierung
Wenn wir nur die direkte Adressierung zur Verfgung haben, stoen wir schnell an Grenzen.
Betrachten wir folgendes Beispiel:
Wir sollen Code schreiben, welcher eine variable Anzahl an Zahlen addieren soll. Die Zahlen
stehen bereits hintereinander im Speicher, beginnend mit der Adresse zahlen_start, und im
Register r16 steht, wie viele Zahlen es sind. Man merkt leicht, dass dies mit direkter
Adressierung nur schwer mglich ist, denn es ist zur Entwicklungszeit noch nicht bekannt, wie
viele Zahlen es sind.
Wir lsen diese Aufgabe, indem wir eine Schleife programmieren, die die Zahlen liest und
aufaddiert, und das ganze so oft, wie im Register r16 steht. Da wir hier von einer Schleife reden,
mssen wir bei jedem Lesen aus dem Speicher mit demselben Befehl auf eine andere
Speicherzelle zugreifen. Wir brauchen also die Mglichkeit die Adresse dynamisch im
Programmablauf zu ndern. Dieses bietet uns die indirekte Adressierung, bei der die Adresse
der gewnschten Speicherstelle in einem Register steht.
Bei AVR-Mikrocontrollern gibt es dafr drei 16 Bit breite Register, die jeweils aus zwei 8-BitRegistern bestehen. Dies rhrt daher, dass ein 8-Bit-Register nur maximal 256 verschiedene
Speicherzellen adressieren knnte, was fr Mikrocontroller mit mehr Speicher nicht ausreicht.
Die Register (r26, r27) und (r28, r29) und (r30, r31) bilden die besagten drei 16 Bit breiten
Register zur indirekten Adressierung. Da diese Register auf Daten zeigen, nennt man sie
logischerweise Zeigerregister (engl. Pointer). Sie tragen die Namen X, Y und Z, wobei die
einzelnen 8-Bit-Register neben ihren rxx-Namen auch
mit XL, XH, YL, YH, ZL und ZH angesprochen werden knnen. L(low) und H(high) bedeutet
hierbei dass die unteren respektive die oberen 8 Bits der 16-Bit-Adresse gemeint ist.
Register
alternativer Name
r26
XL
16-Bit Zeigerregister
X
r27
XH
r28
YL
Y
r29
YH
r30
ZL
Z
r31
ZH
Wir werden beispielhalber das Z-Register fr unser Problem verwenden. Dazu mssen wir
zunchst die Adresse der ersten Zahl in dieses laden. Da das Z-Register 16 Bit breit ist, mssen
wir ZH undZL in zwei einzelnen ldi Operationen beschreiben. Der AVR-Assembler bietet uns
hier zwei praktische Funktionen: Mit LOW(...) und HIGH(...) bekommt man die unteren
respektive die oberen 8 Bit einer Speicheradresse. Das kommt uns gerade recht, da wir gerade
die unteren/oberen 8 Bit der Adresse in die Register ZL/ZH schreiben wollen.
Dann knnen wir mit dem ld-Befehl die Zahl von der Speicherstelle lesen, auf die das ZRegister verweist. Wir schreiben den Wert in das Register r17. Zum Aufsummieren wollen wir
das Register r18 verwenden, welches ganz zu Anfang mit clr auf 0 gesetzt wird.
.dseg
zahlen_start: .byte 20
.cseg
; Irgendwo vorher werden die Zahlen geschrieben, das interessiert
; erstmal nicht weiter, wie das geschieht. Wir gehen jetzt davon aus,
; dass beginnend bei der Speicheradresse zahlen_start so viele Zahlen
; im Speicher stehen, wie im Register r16 steht.
ldi
ldi
clr
schleife:
ld
ZL, LOW(zahlen_start)
ZH, HIGH(zahlen_start)
r18
r17, Z
adiw
ZH:ZL, 1
add
dec
brne
r18, r17
r16
schleife
;
;
;
;
;
; An dieser Stelle ist die Schleife fertig und in r18 steht das Ergebnis.
Das Programm funktioniert zwar schon, aber eine Sache ist unpraktisch: Das Z-Register muss
jedes mal manuell inkrementiert werden, um im nchsten Schleifendurchlauf die nchste Zahl
zu lesen. Da das sequenzielle Lesen oder Schreiben von Daten aus dem bzw. in das SRAM
sehr oft in Programmen vorkommt, gibt es folgende Mglichkeiten:
[Bearbeiten]Postinkrement
Die beiden Zeilen
ld
adiw
r17, Z
ZH:ZL, 1
r17, Z+
Das spart Ausfhrungszeit und macht den Code krzer. Zu beachten ist, dass die
Inkrementierung erst nach der Ausfhrung des eigentlichen Befehls durchgefhrt wird.
[Bearbeiten]Predekrement
quivalent zum Postinkrement gibt es auch die Mglichkeit des Dekrementierens. Hierbei wird
der Wert jedoch vor der Ausfhrung des Befehls dekrementiert. Das Predekrement eignet sich,
umrckwrts durch linear angeordnete Daten zu gehen.
ld
r17, -Z
AVR-Tutorial: 7-Segment-Anzeige
Die Ausgabe von Zahlenwerten auf ein Text-LCD ist sicherlich das Nonplusultra, aber
manchmal liegen die Dinge sehr viel einfacher. Um beispielsweise eine Temperatur anzuzeigen
ist ein LCD etwas Overkill. In solchen Fllen kann die Ausgabe auf ein paar 7-Segmentanzeigen
gemacht werden. Ausserdem haben 7-Segmentanzeigen einen ganz besonderen Charme :-)
Inhaltsverzeichnis
[Verbergen]
1 Typen von 7-Segment Anzeigen
2 Eine einzelne 7-Segment Anzeige
o
2.1 Schaltung
2.2 Codetabelle
2.3 Programm
3 Mehrere 7-Segment Anzeigen (Multiplexen)
3.1 Programm
4 Anwendungsbeispiele
5 Forenbeitrge
[Bearbeiten]Typen
Eine einzelne 7-Segmentanzeige besteht aus sieben (mit Dezimalpunkt acht) einzelnen LEDs in
einem gemeinsamen Gehuse. Aus praktischen Grnden wird einer der beiden Anschlsse
jeder LED mit den gleichen Anschlssen der anderen LED verbunden und gemeinsam aus dem
Gehuse herausgefhrt. Das spart Pins am Gehuse und spter bei der Ansteuerung.
Dementsprechend spricht man von Anzeigen mit gemeinsamer Anode (engl. common anode)
bzw. gemeinsamer Kathode (engl. common cathode).
[Bearbeiten]Eine
[Bearbeiten]Schaltung
Eine einzelne 7-Segmentanzeige wird nach dem folgenden Schema am Port D des Mega8
angeschlossen. Port D wurde deshalb gewhlt, da er am Mega8 als einziger Port aus den
vollen 8 Bit besteht. Die 7-Segmentanzeige hat neben den Segmenten a bis geine gemeinsame
Anode CA sowie einen Dezimalpunkt dp (siehe Bild).
Welcher Pin an der Anzeige welchem Segment (a-g) bzw. dem Dezimalpunkt entspricht wird am
besten dem Datenblatt zur Anzeige entnommen. Im Folgenden wird von dieser
Segmentbelegung ausgegangen:
Wird eine andere Belegung genutzt dann ist das prinzipiell mglich, jedoch msste das in der
Programmierung bercksichtigt werden.
Da eine 7-Segmentanzeige konzeptionell sieben einzelnen LEDs entspricht, ergibt sich im
Prinzip keine nderung in der Ansteuerung einer derartigen Anzeige im Vergleich zur LED
Ansteuerung wie sie im AVR-Tutorial: IO-Grundlagen gezeigt wird. Genau wie bei den einzelnen
LEDs wird eine davon eingeschaltet, indem der zugehrige Port Pin auf 0 gesetzt wird. Aber
anders als bei einzelnen LED mchte man mit einer derartigen Anzeige eine Ziffernanzeige
erhalten. Dazu ist es lediglich notwendig, fr eine bestimmte Ziffer die richtigen LEDs
einzuschalten.
[Bearbeiten]Codetabelle
Die Umkodierung von einzelnen Ziffern in ein bestimmtes Ausgabemuster kann ber eine sog.
Codetabelle geschehen: Die auszugebende Ziffer wird als Offset zum Anfang dieser Tabelle
aufgefasst und aus der Tabelle erhlt man ein Byte (Code), welches direkt auf den Port
ausgegeben werden kann und das entsprechende Bitmuster enthlt, sodass die fr diese Ziffer
notwendigen LED ein- bzw. ausgeschaltet sind.
Beispiel
Um die Ziffer 3 anzuzeigen, mssen auf der Anzeige die
Segmente a, b, c, d und g aufleuchten. Alle anderen Segmente sollen dunkel sein.
Aus dem Anschluschema ergibt sich, dass die dazu notwendige Ausgabe am Port
binr 10110000 lauten muss. Untersucht man dies fr alle Ziffern, so ergibt sich folgende
Tabelle:
.db
.db
.db
.db
.db
.db
.db
.db
.db
.db
0b11000000
0b11111001
0b10100100
0b10110000
0b10011001
0b10010010
0b10000010
0b11111000
0b10000000
0b10010000
;
;
;
;
;
;
;
;
;
;
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
a,
b,
a,
a,
b,
a,
a,
a,
a,
a,
b,
c
b,
b,
c,
c,
c,
b,
b,
b,
c, d, e, f
d,
c,
f,
d,
d,
c
c,
c,
e,
d,
g
f,
e,
g
g
g
f, g
d, e, f, g
d, f, g
[Bearbeiten]Programm
Das Testprogramm stellt nacheinander die Ziffern 0 bis 9 auf der 7-Segmentanzeige dar. Die
jeweils auszugebende Zahl steht im Register count und wird innerhalb der Schleife um jeweils
1 erhht. Hat das Register den Wert 10 erreicht, so wird es wieder auf 0 zurckgesetzt. Nach
der Erhhung folgt eine Warteschleife, welche dafr sorgt, dass bis zur nchsten Ausgabe eine
gewisse Zeit vergeht. Normalerweise macht man keine derartigen langen Warteschleifen, aber
hier geht es ja nicht ums Warten sondern um die Ansteuerung einer 7-Segmentanzeige. Einen
Timer dafr zu benutzen wre zunchst zuviel Aufwand.
Die eigentliche Ausgabe und damit der in diesem Artikel interessante Teil findet jedoch direkt
nach dem Label loop statt. Die bereits bekannte Codetabelle wird mittels .db Direktive
(define byte) in den Flash-Speicher gelegt. Der Zugriff darauf erfolgt ber den Z-Pointer und
dem Befehl lpm. Zustzlich wird vor dem Zugriff noch der Wert des Registers count und damit
der aktuelle Zhlerwert zum Z-Pointer addiert.
Beachtet werden muss nur, dass der Zhlerwert verdoppelt werden muss. Dies hat folgenden
Grund: Wird die Tabelle so wie hier gezeigt mittels einzelnen .db Anweisungen aufgebaut, so
fgt der Assembler sog. Padding Bytes zwischen die einzelnen Bytes ein, damit
jede .db Anweisung auf einer geraden Speicheradresse liegt. Dies ist eine direkte Folge der
Tatsache, dass der Flash-Speicher wortweise (16 Bit) und nicht byteweise (8 Bit) organisiert
ist. Da aber von einem .db in der Tabelle zum nchsten .db eine Differenz von 2 Bytes vorliegt,
muss dies in der Berechnung bercksichtigt werden. Im zweiten Beispiel auf dieser Seite wird
dies anders gemacht. Dort wird gezeigt wie man durch eine andere Schreibweise der Tabelle
das Erzeugen der Padding Bytes durch den Assembler verhindern kann.
Aus dem gleichen Grund wird auch der Z-Pointer mit dem 2-fachen der Startadresse der Tabelle
geladen. Die Startadresse wird vom Assembler in wortweiser Adressierung
eingesetzt, lpm mchte die Zugriffsadresse als Byteadresse angegeben haben.
Interessant ist auch, dass in der Berechnung ein Register bentigt wird, welches den Wert 0
enthlt. Dies deshalb, da es im AVR keinen Befehl gibt der eine Konstante mit gleichzeitiger
Bercksichtigung des Carry-Bits addieren kann. Daher muss diese Konstante zunchst in ein
Register geladen werden und erst dann kann die Addition mithilfe dieses Registers
vorgenommen werden. Das Interessante daran ist nun, dass dieser Umstand in sehr vielen
Programmen vorkommt und es sich bei der Konstanten in der berwiegenden Mehrzahl der
Flle um die Konstante 0 handelt. Viele Programmierer reservieren daher von vorne herein ein
Register fr diesen Zweck und nennen es das Zero-Register. Sinnvollerweise legt man dieses
Register in den Bereich r0..r15, da diese Register etwas zweitklassig sind (ldi, cpi etc.
funktionieren nicht damit).
.include "m8def.inc"
.def zero = r1
.def count = r16
.def temp1 = r17
.org 0x0000
rjmp
main
; Reset Handler
ldi
out
ldi
out
temp1, HIGH(RAMEND)
SPH, temp1
temp1, LOW(RAMEND) ; Stackpointer initialisieren
SPL, temp1
ldi
out
temp1, $FF
DDRD, temp1
ldi
mov
count, 0
zero, count
ldi
ldi
mov
add
temp1, count
temp1, count
add
adc
ZL, temp1
ZH, zero
;
main:
;
;
;
loop:
lpm
;
;
wait:
wait0:
wait1:
wait2:
out
PORTD, r0
inc
cpi
brne
ldi
count
count, 10
wait
count, 0
ldi
ldi
ldi
dec
brne
dec
brne
dec
brne
r17, 10
r18, 0
r19, 0
r19
wait2
r18
wait1
r17
wait0
rjmp
loop
0b11000000
0b11111001
0b10100100
0b10110000
;
;
;
;
;
;
;
;
;
Codes:
.db
.db
.db
.db
a,
b,
a,
a,
b, c, d, e, f
c
b, d, e, g
b, c, d, g
.db
.db
.db
.db
.db
.db
[Bearbeiten]Mehrere
0b10011001
0b10010010
0b10000010
0b11111000
0b10000000
0b10010000
;
;
;
;
;
;
4:
5:
6:
7:
8:
9:
b,
a,
a,
a,
a,
a,
c,
c,
c,
b,
b,
b,
f,
d,
d,
c
c,
c,
g
f, g
e, f, g
d, e, f, g
d, f, g
Mit dem bisherigen Vorwissen knnte man sich jetzt daran machen, auch einmal drei oder vier
Anzeigen mit dem Mega8 anzusteuern. Leider gibt es da ein Problem, denn fr eine Anzeige
sind acht Portpins notwendig - vier Anzeigen wrden demnach 32 Portpins bentigen. Die hat
der Mega8 aber nicht. Dafr gibt es aber mehrere Auswege. Schieberegister sind bereits in
einem anderen Tutorialbeschrieben. Damit knnte man sich ganz leicht die bentigten 32
Ausgangsleitungen mit nur 3 Portpins erzeugen. Das Prinzip der Ansteuerung unterscheidet
sich in nichts von der Ansteurung einer einzelnen 7-Segment Anzeige, lediglich die Art und
Weise, wie die 'Ausgangspins' zu ihren Werten kommen ist anders und durch die Verwendung
von Schieberegistern vorgegeben. An dieser Stelle soll aber eine andere Variante der
Ansteuerung gezeigt werden. Im Folgenden werden wir uns daher das Multiplexen einmal nher
ansehen.
Multiplexen bedeutet, dass nicht alle vier Anzeigen gleichzeitig eingeschaltet sind, sondern
immer nur Eine fr eine kurze Zeit. Geschieht der Wechsel zwischen den Anzeigen schneller als
wir Menschen das wahrnehmen knnen, so erscheinen uns alle vier Anzeigen gleichzeitig in
Betrieb zu sein obwohl immer nur Eine fr eine kurze Zeit aufleuchtet. Dabei handelt es sich
praktisch um einen Sonderfall einer LED-Matrix mit nur einer Zeile. Die vier Anzeigen knnen
sich dadurch die einzelnen Segmentleitungen teilen und alles was bentigt wird sind 4
zustzliche Steuerleitungen fr die 4 Anzeigen, mit denen jeweils eine Anzeige eingeschaltet
wird. Dieses Ein/Ausschalten wird mit einem pnp-Transistor in der Versorgungsspannung jeder
Anzeige realisiert, die vom Mega8 amPortC angesteuert werden.
Ein Aspekt dieser Ansteuerungsart ist die Multiplexfrequenz, also ein kompletter Zyklus das
Weiterschaltens von einer Anzeige zur nchsten . Sie muss hoch genug sein, um ein Flimmern
der Anzeige zu vermeiden. Das menschliche Auge ist trge, im Kino reichen 24 Bilder pro
Sekunde, beim Fernseher sind es 50. Um auf der sicheren Seite zu sein, dass auch Standbilder
ruhig wirken, sollen jedes Segment mit mindestens 100 Hz angesteuert werden, es also
mindestens alle 10ms angeschaltet ist. In Ausnahmefllen knnen aber selbst 100 Hz noch
flimmern, z. B. wenn die Anzeige schnell bewegt wird oder wenn es zu
Interferenzerscheinungen mit knstlichen Lichtquellen kommt, die mit Wechselstrom betrieben
werden.
Bei genauerer Betrachtung fllt auch auf, dass die vier Anzeigen nicht mehr ganz so hell
leuchten wie die eine einzelne Anzeige ohne Multiplexen. Bei wenigen Anzeigen ist dies
praktisch kaum sichtbar, erst bei mehreren Anzeigen wird es deutlich. Um dem entgegen zu
wirken lsst man pro Segment einfach mehr Strom flieen, bei LEDs drfen dann 20mA
berschritten werden. Als Faustregel gilt, dass der n-fache Strom fr die (1/n)-fache Zeit flieen
darf. Details finden sich im Datenblatt unter dem Punkt Peak-Current (Spitzenstrom) und DutyCycle(Tastverhltnis).
Allerdings gibt es noch ein anderes Problem wenn insgesamt zu viele Anzeigen gemultiplext
werden. Die Pulsstrme durch die LEDs werden einfach zu hoch. Die meisten LEDs kann man
bis 8:1 multiplexen, manchmal auch bis 16:1. Hier fliesst aber schon ein Pulsstrom von 320mA
(16 x 20mA), was nicht mehr ganz ungefhrlich ist. Strom lsst sich durch Multiplexen nicht
sparen, denn die verbrauchte Leistung ndert sich beim n-fachen Strom fr 1/n der Zeit nicht.
Kritisch wird es aber, wenn das Multiplexen deaktiviert wird (Ausfall der Ansteuerung durch
Hardware- oder Softwarefehler) und der n-fache Strom dauerhaft durch eine Segment-LED
fliet. Bei 320mA werden die meisten LEDs innerhalb von Sekunden zerstrt. Hier muss
sichergestellt werden, dass sowohl Programm (Breakpoint im Debugger) als auch Schaltung
(Reset, Power-On,[1]) diesen Fall verhindern. Prinzipiell sollte man immer den Pulsstrom und
die Multiplexfrequenz einmal berschlagen, bevor der Ltkolben angeworfen wird.
Sollten die Anzeigen zu schwach leuchten so knnen, wie bereits beschrieben, die Strme
durch die Anzeigen erhht werden. Dazu werden die 330 Widerstnde kleiner gemacht. Da
hier 4 Anzeigen gemultiplext werden, wrden sich Widerstnde in der Grenordnung von
100 anbieten. Auch muss dann der Basiswiderstand der Transistoren verkleinert werden. Auch
muss bercksichtigt werden, dass der Mega8 in Summe an seinen Portpins und an den
Versorgungsleitungen nicht beliebig viel Strom liefern oder abfhren kann. Auch hier ist daher
wieder ein Blick ins Datenblatt angebracht und gegebenenfalls muss wieder ein Transistor als
Verstrker eingesetzt werden (oder eben fertige Treiberstufen in IC-Form).
[Bearbeiten]Programm
Das folgende Programm zeigt eine Mglichkeit zum Multiplexen. Dazu wird ein Timer benutzt,
der in regelmssigen Zeitabstnden einen Overflow Interrupt auslst. Innerhalb der Overflow
Interrupt Routine wird
Da Interruptfunktionen kurz sein sollten, holt die Interrupt Routine das auszugebende Muster fr
jede Stelle direkt aus dem SRAM, wo sie die Ausgabefunktion hinterlassen hat. Dies hat 2
Vorteile:
Zum einen braucht die Interrupt Routine die Umrechnung einer Ziffer in das
entsprechende Bitmuster nicht selbst machen
Zum anderen ist die Anzeigefunktion dadurch unabhngig von dem was angezeigt wird.
Die Interrupt Routine gibt das Bitmuster so aus, wie sie es aus dem SRAM liest.
Werden die SRAM Zellen mit geeigneten Bitmustern gefllt, knnen so auch einige
Buchstaben oder Balkengrafik oder auch kleine Balken-Animationen abgespielt werden.
Insbesondere letzteres sieht man manchmal bei Consumer-Gerten kurz nach dem
Einschalten des Gertes um eine Art Defektkontrolle zu ermglichen oder einfach nur
als optischer Aufputz.
Die Funktion out_number ist in einer hnlichen Form auch schon an anderer Stelle
vorgekommen: Sie verwendet die Technik der fortgesetzten Subtraktionen um eine Zahl in
einzelne Ziffern zu zerlegen. Sobald jede Stelle feststeht, wird ber die Codetabelle das
Bitmuster aufgesucht, welches fr die Interrupt Funktion an der entsprechenden Stelle im
SRAM abgelegt wird.
Achtung: Anders als bei der weiter oben gezeigten Variante wurde die Codetabelle ohne
Padding Bytes angelegt. Dadurch ist es auch nicht notwendig derartige Padding Bytes in der
Programmierung zu bercksichtigen.
Der Rest ist wieder die bliche Portinitialisierung, Timerinitialisierung und eine einfache
Anwendung, indem ein 16 Bit Zhler laufend erhht und ber die Funktion out_number
ausgegeben wird. Wie schon im ersten Beispiel, wurde auch hier kein Aufwand getrieben:
Zhler um 1 erhhen und mit Warteschleifen eine gewisse Verzgerungszeit einhalten. In einer
realen Applikation wird man das natrlich nicht so machen, sondern ebenfalls einen Timer fr
diesen Teilaspekt der Aufgabenstellung einsetzen.
Weiterhin ist auch noch interessant. Die Overflow Interrupt Funktion ist wieder so ausgelegt,
dass sie vllig transparent zum restlichen Programm ablaufen kann. Dies bedeutet, dass alle
verwendeten Register beim Aufruf der Interrupt Funktion gesichert und beim Verlassen
wiederhergestellt werden. Dadurch ist man auf der absolut sicheren Seite, hat aber den Nachteil
etwas Rechenzeit fr manchmal unntige Sicherungs- und Aufrumarbeiten zu 'verschwenden'.
Stehen genug freie Register zur Verfgung, dann wird man natrlich diesen Aufwand nicht
treiben, sondern ein paar Register ausschlielich fr die Zwecke der Behandlung der 7Segment Anzeige abstellen und sich damit den Aufwand der Registersicherung sparen (mit
Ausnahme von SREG natrlich!).
.include "m8def.inc"
main
; Reset Handler
multiplex
;
;********************************************************************
; Die Multiplexfunktion
;
; Aufgabe dieser Funktion ist es, bei jedem Durchlauf eine andere Stelle
; der 7-Segmentanzeige zu aktivieren und das dort vorgesehene Muster
; auszugeben
; Die Funktion wird regelmssig in einem Timer Interrupt aufgerufen
;
; verwendet werden 6 Bytes im SRAM (siehe .DSEG weiter unten im Programm)
;
NextDigit
Bitmuster fr die Aktivierung des nchsten Segments
;
NextSegment Nummer des nchsten aktiven Segments
;
Segment0
Ausgabemuster fr Segment 0
;
Segment1
Ausgabemuster fr Segment 1
;
Segment2
Ausgabemuster fr Segment 2
;
Segment3
Ausgabemuster fr Segment 3
;
; NextSegment ist einfach nur ein Zhler, der bei jedem Aufruf der Funktion
;
um 1 weitergezhlt wird und bei 4 wieder auf 0 zurckgestellt wird.
;
er wird benutzt, um ausgehend von der Adresse von Segment0, auf das
;
jeweils als nchstes auszugebende Muster aus Segement0, Segment1,
;
Segment2 oder Segment3 zuzugreifen. Die Adresse von Segment0 wird
;
in den Z-Pointer geladen und NextSegment addiert.
;
; NextDigit enthlt das Bitmuster, welches direkt an den Port C ausgegeben
;
wird und den jeweils nchsten Transistor durchschaltet.
;
Dazu enthlt NextDigit am Anfang das Bitmuster 0b11111110, welches
;
bei jedem Aufruf um 1 Stelle nach links verschoben wird. Beim nchsten
;
Aufruf findet sich dann 0b11111101 in NextDigit, dann 0b11111011 und
;
zu guter letzt 0b11110111. Wird beim nchsten Schiebevorgang 0b11101111
;
erkannt, dann wird NextDigit auf 0b11111110 zurckgesetzt (und NextSegment
;
auf 0) und das ganze Spiel beginnt beim nchsten Funktionsaufruf wieder
;
von vorne.
;
; Segment0 .. 3 enthalten die auszugebenden Bitmuster fr die Einzelleds
;
der jeweiligen 7-Segment Anzeigen. Diese Muster werden so wie sie sind
;
einfach ausgegeben. Soll eine der Anzeigen etwas bestimmtes anzeigen
;
(zb eine Ziffer), so obliegt es dem Code, der Werte in diese SRAM
;
Zellen schreibt, das dafr zustndige Bitmuster dort zu hinterlassen.
;
Die Multiplexroutine kmmert sich nicht darum, dass diese Bitmuster
;
in irgendeiner Art und Weise sinnvoll (oder was man dafr halten knnte)
;
sind.
;
; vernderte CPU-Register: keine
;
multiplex:
push
temp
; Alle verwendeten Register sichern
push
temp1
in
temp, SREG
push
temp
push
ZL
push
ZH
ldi
out
temp1, 0
PORTC, temp1
ldi
ldi
lds
add
adc
ld
out
temp, Z
PORTD, temp
lds
out
temp1, NextDigit
PORTC, temp1
lds
inc
sec
rol
cpi
brne
ldi
ldi
temp, NextSegment
temp
sts
sts
NextSegment, temp
NextDigit, temp1
pop
pop
pop
out
pop
pop
reti
ZH
ZL
temp
SREG, temp
temp1
temp
temp1
temp1, 0b11101111
multi1
temp, 0
temp1, 0b11111110
multi1:
;
;************************************************************************
; 16 Bit-Zahl aus dem Registerpaar temp (=low), temp1 (=high) ausgeben
; die Zahl muss kleiner als 10000 sein, da die Zehntausenderstelle
; nicht bercksichtigt wird.
; Werden mehr als 4 7-Segmentanzeigen eingesetzt, dann muss dies
; natrlich auch hier bercksichtigt werden
;
out_number:
push
temp
push
temp1
ldi
_out_tausend:
inc
subi
sbci
brcc
ldi
ldi
temp2, -1
temp2
temp, low(1000)
temp1, high(1000)
_out_tausend
ZL, low(2*Codes)
ZH, high(2*Codes)
add
ZL, temp2
lpm
sts
Segment3, r0
ldi
temp2, 10
_out_hundert:
dec
subi
sbci
brcs
temp2
temp, low(-100)
temp1, high(-100)
_out_hundert
; +100
ZL, low(2*Codes)
ZH, high(2*Codes)
ZL, temp2
lpm
sts
Segment2, r0
ldi
temp2, -1
inc
subi
sbci
brcc
temp2
temp, low(10)
temp1, high(10)
_out_zehn
ldi
ldi
add
ZL, low(2*Codes)
ZH, high(2*Codes)
ZL, temp2
lpm
sts
Segment1, r0
_out_zehn:
ldi
ldi
add
_out_einer:
subi
sbci
temp, low(-10)
temp1, high(-10)
ldi
ldi
add
ZL, low(2*Codes)
ZH, high(2*Codes)
ZL, temp
; ... Codetabelle
lpm
sts
Segment0, r0
pop
pop
temp1
temp
ret
;
;**************************************************************************
;
main:
ldi
temp, HIGH(RAMEND)
out
SPH, temp
ldi
temp, LOW(RAMEND) ; Stackpointer initialisieren
out
SPL, temp
;
die Segmenttreiber initialisieren
ldi
temp, $FF
out
DDRD, temp
;
die Treiber fr die einzelnen Stellen
ldi
out
temp, $0F
DDRC, temp
;
;
temp, 0b11111110
NextDigit, temp
ldi
sts
temp, 0
NextSegment, temp
ldi
out
ldi
out
sei
ldi
ldi
temp, 0
temp1, 0
inc
brne
inc
temp
_loop
temp1
loop:
_loop:
rcall
wait:
wait0:
wait1:
wait2:
out_number
cpi
brne
cpi
brne
ldi
ldi
temp, 0
temp1, 0
ldi
ldi
ldi
dec
brne
dec
brne
dec
brne
r21, 1
r22, 0
r23, 0
r23
wait2
r22
wait1
r21
wait0
rjmp
loop
Codes:
.db
0b11000000, 0b11111001
.db
0b10100100, 0b10110000
.db
0b10011001, 0b10010010
.db
0b10000010, 0b11111000
.db
0b10000000, 0b10010000
.DSEG
;
;
;
;
;
;
;
;
;
;
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
a,
b,
a,
a,
b,
a,
a,
a,
a,
a,
b,
c
b,
b,
c,
c,
c,
b,
b,
b,
c, d, e, f
d,
c,
f,
d,
d,
c
c,
c,
e,
d,
g
f,
e,
g
g
g
f, g
d, e, f, g
d, f, g
NextDigit:
NextSegment:
Segment0:
Segment1:
Segment2:
Segment3:
.byte
.byte
.byte
.byte
.byte
.byte
1
1
1
1
1
1
;
;
;
;
;
;
[Bearbeiten]Anwendungsbeispiele
[Bearbeiten]Forenbeitrge
/***********************************************************************
*/
/*
Initialisierung
*/
/***********************************************************************
*/
PORTD
DDRD
= 0x0F;
/* Timer Initialisierung */
TCCR1A
= 0;
TCCR1B
TIMSK
|= (1<<OCIE1A);
OCR1A
= TWAIT_1P0_S;
sei();
// Initialisierung der Startwerte
zustand
= COUNTDOWN;
minuten
= MAXMINUTEN;
sekunden
= MAXSEKUNDEN;
time_cnt
tilt
= 0;
= 0;
digit
= 0;
}
/***********************************************************************
*/
/*
*/
/***********************************************************************
*/
void poll_tilt (void)
{ // Abfrage, ob TILT-Schalter gedrckt ist
if (PIND & (1<<PIND5))
{
tilt
= 1;
}
}
/***********************************************************************
*/
/*
*/
/***********************************************************************
*/
void zustandsautomat (void)
{
bla;
bla;
bla;
// Umsetzung der Dezimal- in Binrzahlen zur Erleuchtung der
LED-Segmente
for (i = 0; i < ANZSTELLEN; i++)
{
bin_out[i] = SEGMENTE[dez_out[i]];
// Binrmuster fr Ausgabe
}
bla;
}
/***********************************************************************
*/
/*
*/
/***********************************************************************
*/
void stellenanzeige(void)
{// Ausgabe der Binrwerte auf PORTB und Weiterschalten der Stelle durch
PORTD
PORTB =
bin_out[digit];
/*
Hauptschleife
*/
/***********************************************************************
*/
void bd_loop(void)
{
// Hauptschleife
for (;;)
{
// Abfrage des Tilt-Schalters
poll_tilt();
// Zustnde verwalten, Zeit herunterzhlen und Binrmuster erzeugen
zustandsautomat();
// Anzeige verwalten, Binrmuster auf PORTB, Stellen weiterschalten
stellenanzeige();
} // ende Hauptschleife
}
void stellenanzeige(void)
{// Ausgabe der Binrwerte auf PORTB und Weiterschalten der Stelle durc
PORTB = bin_out[digit];
PORTD = (PORTD & 0xE0) | (1<<digit);
schaltest du das neue Muster fr die Anzeigen schon auf die LED, whrend
noch die vorhergehende Stelle selektiert ist. Und das siehst du
->
// Binrmuster fr Ausgabe
}
Ist nen Schnellschu... schau halt mal.
mal
aus.
Fragen:
@Bernd
Wie ist das gemeint mit sie() und cli()?
Die Anzeige wird in der Hauptschleife ausgegeben. Den Compare-Interrupt
benutze ich, um einen Zeitzhler hochzusetzen, der dann im
Zustandsautomat in der Hauptschleife ausgewertet wird.
@Anja
Ich verwende den internen 8MHz Oszillator. Die Anzeige wird vermutlich
alle ca. 1000 Taktzyklen von Stelle zu Stelle weitergeschaltet. Sollte
das fr die BC548 nicht ausreichen?
@Michael M.
Wahrscheinlich hast Du Recht, aber, so schnell kann ich gar nicht
gucken, wie das flimmert ;-)
Also noch Mal danke an alle!
Hallo Geisterlicht,
> Ich verwende den internen 8MHz Oszillator. Die Anzeige wird vermutlich
> alle ca. 1000 Taktzyklen von Stelle zu Stelle weitergeschaltet. Sollte
> das fr die BC548 nicht ausreichen?
Ups...
Also: die Wechselfrequenz von Stelle zu Stelle wrde ich nicht viel
hher machen als unbedingt ntig: also ca 50-100Hz * Anzahl der Stellen
damit die Anzeige nicht flimmert.
Also bei Dir so maximal 400 Hz bzw. alle 2,5 Millisekunden.
Bei 8MHz / 1000 Zyklen = 8000 Hz wundert es mich nicht da der Effekt so
stark sichtbar ist.
Ich mache das multiplexen gerne in einer Timer-Interrupt-Routine.
Dann ist auch sichergestellt da alle Digits gleichmig hell leuchten.
Den 1 Sekunden-Timer kann man dann als vielfaches des Multiplex-Taktes
erreichen. z.B. alle 4 Digits * 100 Durchlufe.
BC548 sind Transistoren fr Verstrker-Anwendungen und nicht fr
Schalter-Betrieb spezifiziert. Eine Sperrverzgerungszeit ist daher in
den Datenblttern nicht angegeben. Was nicht heit da man den BC548
nicht fr Schalteranwendungen verwenden darf. Das Verhalten ist halt nur
nicht spezifiziert.
Bei Schalteranwendungen verwende ich gerne den 2N2222A der hat dann so
0,25 us Sperrverzgerungszeit bei 150mA Kollektorstrom.
AVR-Tutorial: Servo
Die Seite ist noch im entstehen und bis sie einigermassen vollstndig ist, noch kein Teil des
Tutorials
Inhaltsverzeichnis
[Verbergen]
1 Allgemeines ber Servos
2 Stromversorgung
3 Das Servo-Impulstelegram
4 Programmierung
o
[Bearbeiten]
[Bearbeiten]
Stromversorgung
Werden Servos an einem C betrieben, so ist es am Besten, sie aus einer eigenen Stromquelle
(Akku) zu betreiben. Manche Servos erzeugen kleine Strungen auf der Versorgungsspannung,
die einen C durchaus zum Abstrzen bringen knnen. Muss man Servos gemeinsam mit
einem C von derselben Stromquelle betreiben, so sollte man sich gleich darauf einrichten,
diesen Strimpulsen mit Kondensatoren zu Leibe rcken zu mssen. Unter Umstnden ist hier
auch eine Mischung aus kleinen, schnellen Kondensatoren (100nF) und etwas greren, aber
dafr auch langsameren Kondensatoren (einige F) notwendig.
Die eindeutig beste Option ist es aber, die Servos strommig vom C zu entkoppeln und ihnen
ihre eigene Stromquelle zu geben. Servos sind nicht besonders heikel. Auch im Modellbau
mssen sie mit unterschiedlichen Spannungen zurechtkommen, bedingt durch die dort bliche
Versorgung aus Akkus, die im Laufe der Betriebszeit des Modells natrlich durch die Entladung
ihre Voltzahl immer weiter reduzieren. Im Modellbau werden Akkus mit 4 oder 5 Zellen
verwendet, sodass Servos mit Spannungen von ca. 4V bis hinauf zu ca. 6V zurecht kommen
mssen, wobei randvolle Akkus diese 6V schon auch mal berschreiten knnen. Bei sinkenden
Spannungslage verlieren Servos naturgem etwas an Kraft bzw. werden in ihrer
Stellgeschwindigkeit unter Umstnden langsamer.
Die Servos werden dann nur mit ihrer Masseleitung und natrlich mit ihrer Impulsleitung mit
dem C verbunden.
[Bearbeiten]
Das Servo-Impulstelegram
Das Signal, das an den Servo geschickt wird, hat eine Lnge von ungefhr 20ms. Diese 20ms
sind nicht besonders kritisch und sind ein berbleibsel von der Technik mit der mehrere Kanle
ber die Funkstrecke einer Fernsteuerung bertragen werden. Fr das Servo wichtig ist die
Impulsdauer in der ersten Phase eines Servosignals. Nominell ist dieser Impuls zwischen 1ms
und 2ms lang. Wobei das jeweils die Endstellungen des Servos sind, an denen es noch nicht
mechanisch begrenzt wird. Eine Pulslnge von 1.5ms wre dann Servomittelstellung. Fr die
Positionsauswertung des Servos haben die 20ms Wiederholdauer keine besondere Bedeutung,
sieht man einmal davon ab, dass ein Servo bei krzeren Zeiten entsprechend fter
Positionsimpulse bekommt und daher auch fter die Position gegebenenfalls korrigiert, was
mglicherweise in einem etwas hheren Stromverbrauch resultiert.
Umgekehrt lsst sich definitiv Strom sparen, indem die Pulse ganz ausgesetzt werden: Der
Servo bleibt in der Position, in der er sich gerade befindet - korrigiert sich aber auch nicht mehr.
Kommen die Impulse selten, also z.B. alle 50ms, luft der Servo langsamer in seine Zielposition
(praktische Erfahrungen, vermutlich nirgends spezifiziert). Dieses Verhalten lsst sich nutzen,
um die manchmal unerwnschten ruckartigen Bewegungen eines Servos abzumildern.
Servo Impulsdiagramm
Den meisten Servos macht es nichts aus, wenn die Lnge des Servoprotokolls anstelle von
20ms auf zb 10ms verkrzt wird. Bei der Generierung des Servosignals muss man daher den
20ms keine besondere Beachtung schenken. Eine kleine Pause nach dem eigentlichen
Positionssignal reicht in den meisten Fllen aus und es spielt keine allzugroe Rolle, wie lange
diese Pause tatschlich ist. Generiert man das Imulsdiagramm zb. mit einem Timer, so orientiert
man sich daher daran, dass man den 1.0 - 2.0ms Puls gut generieren kann und nicht an den
20ms.
Reale Servos haben allerdings in den Endstellungen noch Reserven, so dass man bei vielen
Servos auch Pulslngen von 0.9 bis 2.1 oder sogar noch kleinere/grere Werte benutzen
kann. Allerdings sollte man hier etwas Vorsicht walten lassen. Wenn das Servo unbelastet in
einer der Endstellungen deutlich zu 'knurren' anfngt, dann hat man es bertrieben. Das Servo
ist an seinen mechanischen Endanschlag gefahren worden und auf Dauer wird das der Motor
bzw. das Getriebe nicht aushalten.
[Bearbeiten]
Programmierung
[Bearbeiten]einfache
Im folgenden Programm wurden einfache Warteschleifen auf die im Tutorial bliche Taktfrequen
von 4Mhz angepasst, so dass sich die typischen Servo-Pulsdauern ergeben. Ein am Port D,
beliebiger Pin angeschlossenes Servo dreht damit stndig vor und zurck. Die Servoposition
kann durch laden eines Wertes im Bereich 1 bis ca 160 in das Register r18 und
anschliessendem Aufruf von servoPuls in einen Puls fr ein Servo umgewandelt werden.
.include "m8def.inc"
.equ XTAL = 4000000
rjmp
init
init:
ldi
out
ldi
out
r16,
SPH,
r16,
SPL,
HIGH(RAMEND)
r16
LOW(RAMEND)
r16
; Stackpointer initialisieren
ldi
r18, 0
loop1:
inc r18
cpi r18, 160
breq loop2
rcall servoPuls
rjmp loop1
loop2:
dec r18
cpi r18, 0
breq loop1
rcall servoPuls
rjmp loop2
servoPuls:
push r18
ldi r16, 0xFF
out PORTD, r16
rcall wait_puls
; Ausgabepin auf 1
; die Wartezeit abwarten
rcall wait_pause
pop r18
ret
rcall wait_1ms
ret
;
wait_pause:
ldi r19, 15
w_paus_1: rcall wait_1ms
dec r19
brne w_paus_1
ret
;
wait_1ms: ldi r18, 10
; 1 Millisekunde warten
w_loop2: ldi r17, 132
; Es muessen bei 4 Mhz 4000 Zyklen verbraten werden
w_loop1: dec r17
; die innerste Schleife umfasst 3 Takte und wird 132
brne w_loop1
; mal abgearbeitet: 132 * 3 = 396 Takte
dec r18
; dazu noch 4 Takte fr die ussere Schleife = 400
brne w_loop2
; 10 Wiederholungen: 4000 Takte
ret
; der ret ist nicht eingerechnet
;
; r18 muss mit der Anzahl der Widerholungen belegt werden
; vernnftige Werte laufen von 1 bis ca 160
wait_puls:
w_loop4: ldi r17, 10
; die variable Zeit abwarten
w_loop3: dec r17
brne w_loop3
dec r18
brne w_loop4
Wie meistens gilt auch hier: Warteschleifen sind in der Programmierung nicht erwnscht. Der
Prozessor kann in diesen Warteschleifen nichts anderes machen. Etwas ausgeklgeltere
Programme, bei denen mehrere Dinge gleichzeitig gemacht werden sollen, sind damit nicht
vernnftig realisierbar. Daher sollte die Methode mittels Warteschleifen nur dann benutzt
werden, wenn dies nicht bentigt wird, wie zb einem simplen Servotester, bei dem man die
Servoposition zb durch Auslesen eines Potis mit dem ADC festlegt.
Ausserdem ist die Berechnung der Warteschleifen auf eine bestimmte Taktfrequenz
unangenehm und fehleranfllig :-)
[Bearbeiten]einfache
Auch hier wieder: Der Vorteiler des Timers so wird so eingestellt, dass man die Pulszeit gut mit
dem Compare Match erreichen kann, die nachfolgende Pause, bis der Timer dann seinen
Overflow hat (oder den CTC Clear macht) ist von untergeordneter Bedeutung. Man nimmt was
vom Zhlbereich des Timers brig bleibt.
[Bearbeiten]Ansteuerung mit dem 8-Bit Timer 2
Mit einem 8 Bit Timer ist es gar nicht so einfach, sowohl die Zeiten fr den Servopuls als auch
die fr die Pause danach unter einen Hut zu bringen. Abhilfe schafft ein Trick.
Der Timer wird so eingestellt, dass sich der Servopuls gut erzeugen lsst. Dazu wird der Timer
in den CTC Modus gestellt und das zugehrige Vergleichsregister so eingestellt, dass sich die
entsprechenden Interrupts zeitlich so ergeben, wie es fr einen Puls bentigt wird. In einem
Aufruf des Interrupts wird der Ausgangspin fr das Servo auf 1 gestellt, im nchsten wird er
wieder auf 0 gestellt. Die kleine Pause bis zum nchsten Servoimpuls wird so erzeugt, dass
eine gewisse Anzahl an Interrupt Aufrufen einfach nichts gemacht wird. hnlich wie bei einer
PWM wird also auch hier wieder ein Zhler installiert, der die Anzahl der Interrupt Aufrufe
mitzhlt und immer wieder auf 0 zurckgestellt wird.
Die eigentliche Servoposition steht im Register OCR2. Rein rechnerisch betrgt ihr
Wertebereich:
1ms 4000000 / 64 / 1000 OCR2 = 62.5
2ms 4000000 / 64 / 500 OCR2 = 125
mit einer Mittelstellung von ( 62.5 + 125 ) / 2 = 93.75
.include "m16def.inc"
.equ XTAL = 4000000
rjmp
.org OC2addr
rjmp
init
Compare_vect
init:
ldi
out
ldi
out
r16,
SPH,
r16,
SPL,
HIGH(RAMEND)
r16
LOW(RAMEND)
r16
; Stackpointer initialisieren
ldi
out
r16, 0x80
DDRB, r16
ldi
r17, 0
; Software-Zhler
ldi
out
r16, 120
OCR2, r16
ldi
out
r16, 1<<OCIE2
TIMSK, r16
ldi
out
sei
main:
rjmp main
Compare_vect:
in
inc
cpi
breq
cpi
breq
cpi
brne
ldi
return:
out
reti
r18, SREG
r17
r17, 1
PulsOn
r17, 2
PulsOff
r17, 10
return
r17, 0
SREG, r18
PulsOn:
sbi PORTB, 0
rjmp return
PulsOff:
cbi PORTB, 0
rjmp return
[Bearbeiten]Ansteuerung
AVR-Tutorial: Watchdog
Dieser Artikel ist im Entstehen: Die Diskussion wird in [1] gefhrt.
Der Watchdog im AVR (WDT) ist ein spezieller Timer, der nach Ablauf (typisch ein paar ms)
automatisch einen RESET auslst. Im Normalbetrieb wird der Watchdog in der Hauptschleife
des Programms regelmig zurckgesetzt. Wenn durch einen Fehler dieses Zurcksetzen nicht
mehr stattfindet, luft der Watchdog-Timer ab und lst einen RESET aus, um den
Mikrocontroller und damit das Programm neu zu starten.
Inhaltsverzeichnis
[Verbergen]
1 Anwendung
2 Steuerung
3 Beispiel
o
[Bearbeiten]Anwendung
(nach Ganssle-03)
Der Watchdog ist im Prinzip zur Stelle, wenn kein Anwender da ist, um den Resetknopf zu
drcken.
Der Watchdog bringt dabei das System aus einem unvorhergesehenen Fehlerzustand wieder in
einen betriebsbereiten Zustand.
Dieser Zustand nach einem WDT Reset kann je nach Implementierung im Programm sein:
Debugzustand
Sicherheitszustand
Betriebszustand
WDTCR
WDCE[1]
WDE[2]
WDP2[3]
WDP1[3]
WDP0[3]
1. Watch Dog Change Enable; heit bei ATmega16/32: WDTOE Watchdog Turn Off
Enable
2. Watch Dog Enable
3. 3,0 3,1 3,2 Watch Dog Timer Prescaler, bit x
WDP2
WDP1
WDP0
16,3
32,5
65
130
260
520
1100
2100
Achtung
Die Zeiten sind abhngig von der Betriebspannung und der Taktfrequenz des WDTOszillators. Sie sollten daher aus dem jeweiligen Datenblatt des Cs entnommen
werden.
[Bearbeiten]Beispiel
[Bearbeiten]WDT durch WDTON-Fuse aktivieren
Am Einfachsten lt es sich durch ein kleines Programmbeispiel demonstrieren.
Ein ATmega8 wird mit 4 MHz des internen Taktgenerators mit einer Startup-Zeit von 64 ms
getaktet. Die WDTON-Fuse ist gesetzt (WDT aktiviert). An Port B ist eine LED angeschlossen
(Pin egal).
.include "m8def.inc"
; ATMega8L mit internen 4 MHz getaktet + 64 ms Startuptime
; WDTON aktiviert!
.def Temp1 = R16
.def SubCount = R17
.org 0x0000
rjmp
Reset
; Reset Handler
.org OC1Aaddr
rjmp
timer1_compare
Reset:
ldi
out
ldi
out
Temp1, HIGH(RAMEND)
SPH, Temp1
Temp1, LOW(RAMEND) ; Stackpointer initialisieren
SPL, Temp1
ldi
out
Temp1, 0xFF
DDRB, Temp1
ldi
out
ldi
out
ldi
out
Temp1, high(40000 - 1)
OCR1AH, temp1
Temp1, low(40000 - 1)
OCR1AL, temp1
; CTC Modus einschalten
; Vorteiler auf 1
Temp1, ( 1 << WGM12 ) | ( 1 << CS10 )
TCCR1B, temp1
ldi
out
Mainloop
timer1_compare:
SubCount
SubCount, 50
exit_isr
Der Timer 1 luft im CTC-Modus mit einer Frequenz von 100 Hz (10 ms). Durch den SoftSubcounter wird die Frequenz auf 2 Hz geteilt und jeweils nach 500 ms das Port B negiert.
Da die LED in diesem Beispiel nach 500 ms jeweils ein- und ausgeschaltet wird, blinkt sie mit
einer Frequenz von 1 Hz. Der WDT wird nach 10 ms zurckgesetzt, so dass er keinen RESET
auslsen kann.
Wird jetzt der Befehl WDR auskommentiert, fhrt der WDT nach 16 ms einen RESET aus. Die
LED blinkt nun, bedingt durch die Startup-Zeit von 64 ms und einem Time-out von 16ms, mit
rund 6 Hz. 1/(64ms + 16ms) ~ 12 Hz (halbe Periode)
[Bearbeiten]WDT durch Software aktivieren/deaktivieren
Der WDT lt sich auch softwaremig durch Setzen des WDE-Bits im WDTCR Register
aktivieren.
WDT_on:
in Temp1, WDTCR
ori Temp1, (1<<WDE)
out WDTCR, Temp1
ret
Dieses hat den Vorteil, dass man den WDT auch softwaremig wieder deaktivieren kann.
Ein Deaktivieren des WDTs ist nicht mglich, wenn die WDTON - Fuse gesetzt ist!
Das softwaremige Deaktivieren verlangt allerdings eine besondere Deaktivierungssequenz.
(in drei Phasen)
WDT_off:
;** 1. Phase
wdr
; reset WDT
;** 2. Phase
in Temp1, WDTCR
; Write logical one to WDCE and WDE
ori Temp1, (1<<WDCE)|(1<<WDE)
in Temp2, SREG
cli
; save I Flag
; we have only 5 cycles to reset WDE
Wenn WDCE und WDE nicht in einem Zug vor dem Deaktivieren auf 1 gesetzt werden, hat das
Rcksetzen des WDE-Bits keine Wirkung und der WDT luft munter weiter!
Dazu hat man maximal 5 Takte Zeit. Diese Sequenz darf auch nicht durch einen Interrupt
unterbrochen werden.
[Bearbeiten]Tipps
& Tricks
rjmp watchDogReset
;WDRStart
Watchdog Reset Flag auswerten Erklrung, wie das WDRF unter C abgefragt wird
1.2.4 Watchdog
2 Praxis
2.1 Assembler
2.2 C
3 Quellen
[Bearbeiten]Theorie
[Bearbeiten]Sleep Modi
Welche Sleep-Modi es gibt, hngt vom verwendeten C ab, dieser Artikel nimmt jedoch Bezug
auf den ATmega32. Um einen der verfgbaren Sleep-Modi des ATmega32 zu betreten mssen
folgende Schritte ausgefhrt werden
Bit
Bezeichn
ung
SE
SM2
SM1
SM0
ISC11
ISC10
ISC01
ISC00
Bit 7 - SE
Sleep Enable
Mit diesem Bit wird bestimmt ob der Sleep-Befehl ausgefhrt wird (1) oder nicht (0).
Bit 6..4 - SM2..0
Sleep Mode Select
Mit diesen drei Bits wird der gewnschte Sleep-Modus gewhlt
SM2
SM1
SM0
Sleep Modus
Idle
Power-down
Power-save
(1)
Reserved
Reserved
Standby(1)
Extended Standby(1)
[Bearbeiten]Modi
bersicht
Generell ist der Modus zu whlen, der die meisten nicht bentigten Module abschaltet.
Aktive
Oszillatoren
Aktive Takte
Slee
cl
cl
p
clk clkF
clk
kI
kAS
Modu CPU LASH
ADC
O
Y
s
Idle
ADC
Noise
Redu
ction
Powe
rdown
Weckquellen
IN
Time T2
Hauptta r
IN
ktgeber Oszil T1
lator IN
T0
x(2)
x(2)
x(3
)
x(3
)
TWI
Add
SPM/EE A
Tim
ress
PROM
D
er2
Mat
Ready C
ch
And
ere
I/O
Powe
rsave
x(2
x(2)
Stan
dby(1)
x(2
)
x(3
Exte
nded
Stan
dby(1)
x(3
x(2)
x(3
)
x(2)
x(2)
(1)
[Bearbeiten]Manuelles Deaktivieren
Einzelne Module knnen auch manuell deaktviert werden um Strom zu sparen, das bietet sich
vorallem an wenn bestimmte Module im gegebenen Projekt generell nicht bentigt werden und
damit deaktiviert werden knnen.
[Bearbeiten]Analog
to Digital Converter
todo...
[Bearbeiten]Analog
Comparator
Der Analogkomparator ist standardmig aktiviert. Um ihn zu deaktivieren, muss man ADC (Bit
7) im Register ACSR setzen.
[Bearbeiten]Brown-Out
Detector
Der Brown-Out Detector lsst sich entweder durch das BODEN-Bit in den Fuses oder mit
entsprechenden Befehlen aktivieren oder deaktivieren. Das Fuse-Bit ist standardmig gesetzt
(Achtung: Umgekehrte Logik!) und der BOD damit deaktiviert.
[Bearbeiten]Watchdog
Auch der Watchdog-Timer lsst sich in den Fuses standardmig aktivieren/deaktivieren, hier
ber das WDTON-Bit. Natrlich geht auch das softwareseitig [1]
[Bearbeiten]Praxis
[Bearbeiten]Assembler
TODO: ASM-Quellcode Beispiele
[Bearbeiten]C
Ein simples Testprogramm, um mit sleep modi im AVR zu spielen. Es funktioniert sehr gut mit
dem ATmega8 und ist auch auf andere AVRs portierbar. Teilweise sind weitere Modi verfgbar.
Wichtig ist, da die Interruptroutine fr den "Weckruf" vorhanden sein muss. Es mssen nicht
zwingend Aktionen in ihr durchgefhrt werden.
/* ATmega8 with internal 4Mhz clock (6cycle + 64ms) */
#include
#include
#include
#include
<avr/io.h>
<avr/sleep.h>
<avr/interrupt.h>
<util/delay.h>
int main(void)
{
DDRC |= (1 << PC2) | (1 << PC1); // leds for testing
DDRD &= ~(1 << PD2); // INT0: input...
PORTD |= (1 << PD2); // ...with pullup.
// level interrupt INT0 (low level)
MCUCR &= ~((1 << ISC01) | (1 << ISC00));
// infinite main loop
while (1)
{
// trigger leds for testing
PORTC ^= (1 << PC1);
_delay_ms(500);
PORTC ^= (1 << PC1);
// enable external interrupt
GICR |= (1 << INT0);
// set sleep mode
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
// sleep_mode() has a possible race condition
sleep_enable();
sei();
sleep_cpu();
sleep_disable();
// waking up...
// disable external interrupt here, in case the external low pulse is too long
GICR &= ~(1 << INT0);
}
ISR(INT0_vect)
{
// ISR might be empty, but is necessary nonetheless
PORTC ^= (1 << PC2); // debugging
}
[Bearbeiten]Quellen