AVR 16-Bit-Register
Allgemeines
Speziell bei den 16-Bit-Timern und auch beim ADC ist es bei allen Zugriffen auf Datenregister erforderlich, dass diese Daten synchronisiert sind. Wenn z. B. bei einem 16-Bit-Timer das High-Byte des Zählregisters gelesen wurde und vor dem Lesezugriff auf das Low-Byte ein Überlauf des Low-Bytes stattfindet, erhält man einen völlig unsinnigen Wert. Auch die Compare-Register müssen synchron geschrieben werden, da es ansonsten zu unerwünschten Compare-Ereignissen kommen kann.
Beim ADC besteht das Problem darin, dass zwischen den Zugriffen auf die beiden Teilregister eine Wandlung beendet werden kann und der ADC ein neues Ergebnis in ADCL und ADCH schreiben will, wodurch High- und Low-Byte nicht zusammenpassen.
Bei den Timern wird die Synchronistation über ein temporäres Register gelöst:
Beim ersten Lesezugriff wird die jeweils andere Registerhälfte in ein temporäres Register kopiert und der zweite Zugriff automatisch auf dieses umgeleitet. Beim Schreibzugriff wird der erste Zugriff umgeleitet und erst der Zweite ändert das Register.
Damit der Zugriff korrekt abläuft muss eine festgelegte Reihenfolge eingehalten werden.
Beim Lesezugriff muss erst das Low-Byte, dann das High-Byte gelesen werden.
Um den Schreibzugriff besonders kompliziert zu machen, hat Atmel die Reihenfolge dafür zwischenzeitlich geändert: Bei klassischen AVRs (z.B. ATmega328) muss zuerst das High-Byte, dann das Low-Byte geschrieben werden. Bei einigen neueren AVRs (z.B. ATtiny 0-Series) muss auch beim Schreibzugriff erst das Low-Byte, dann das High-Byte geschrieben werden. Im Zweifelsfall hilft wie immer ein Blick ins Datenblatt.
Des weiteren ist zu beachten, dass es für alle 16-Bit-Register eines Peripherals nur ein einziges temporäres Register gibt, so dass das Auftreten eines Interrupts, in dessen Handler ein solches Register manipuliert wird, bei einem durch ihn unterbrochenen Zugriff i. d. R. zu Datenmüll führt. 16-Bit-Zugriffe sind generell nicht atomar! Wenn mit Interrupts gearbeitet wird, kann es erforderlich sein, vor einem solchen Zugriff auf ein 16-Bit-Register die Interrupt-Bearbeitung (mittels cli
) zu deaktivieren. Alternativ kann man das TEMP-Register ähnlich den CPU-Registern im Interrupt sichern und anschlieẞend wiederherstellen (funktionert zumindest bei neueren AVRs)
Beim ADC-Datenregister ADCH/ADCL ist die Synchronisierung anders gelöst. Hier werden beim Lesezugriff (ADCH/ADCL sind logischerweise read-only) auf das Low-Byte ADCL beide Teilregister für Zugriffe seitens des ADC so lange gesperrt, bis das High-Byte ADCH ausgelesen wurde. Dadurch kann der ADC nach einem Zugriff auf ADCL keinen neuen Wert in ADCH/ADCL ablegen, bis ADCH gelesen wurde. Ergebnisse von Wandlungen, die zwischen einem Zugriff auf ADCL und ADCH beendet werden, gehen verloren! Daraus folgt: Nach einem Zugriff auf ADCL muss grundsätzlich ADCH gelesen werden!
Beim ADC gibt es für den Fall, dass eine Auflösung von 8 Bit ausreicht, die Möglichkeit, das Ergebnis „linksbündig“ in ADCH/ADCL auszurichten, so dass die relevanten 8 MSB in ADCH stehen. In diesem Fall muss bzw. sollte nur ADCH ausgelesen werden.
Zugriffe in C
In beiden Fällen (also sowohl bei den Timern als auch beim ADC) werden von C-Compilern 16-Bit-Pseudo-Register zur Verfügung gestellt (z. B. TCNT1H/TCNT1L → TCNT1, ADCH/ADCL → ADC bzw. ADCW), bei deren Verwendung der Compiler automatisch die richtige Zugriffsreihenfolge regelt. In C-Programmen sollten grundsätzlich diese 16-Bit-Register verwendet werden. Sollte trotzdem ein Zugriff auf ein Teilregister erforderlich sein, sind obige Angaben zu berücksichtigen.
Es ist darauf zu achten, dass auch ein Zugriff auf die 16-Bit-Register vom Compiler in zwei 8-Bit-Zugriffe aufgeteilt wird und dementsprechend genauso nicht-atomar ist wie die Einzelzugriffe. Auch hier gilt, dass unter Umständen die Interrupt-Bearbeitung gesperrt werden muss, um Datenmüll zu vermeiden.
ADC und ADCW sind unterschiedliche Bezeichner für das selbe Registerpaar. Üblicherweise kann man in C-Programmen ADC verwenden, was analog zu den anderen 16-Bit-Registern benannt ist. ADCW (ADC Word) existiert nur deshalb, weil die Headerdateien auch für Assembler vorgesehen sind und es bereits einen Assembler-Befehl namens adc
gibt.
Makros
Um den Zugriff auf diese 16-Bit-Register zu vereinfachen, hat ATMEL in der Application Note AVR072: Accessing 16-bit I/O Registers folgende Makros zur freien Verwendung vorgeschlagen:
AVR-Assembler-Makros
.macro outw
cli
out @0, @1
out @0-1, @2
sei
.endmacro
.macro inw
cli
in @1, @2-1
in @0, @2
sei
.endmacro
Anwendung
.include "8515def.inc"
inw r17, r16, TCNT1H ; Reads the counter value (high, low, adr)
outw TCNT1H, r17, r16 ; Writes the counter value (adr, high, low)
IAR-C-Makros
#include <ina90.h>
#define outw( ADDRESS, VAL )\
{\
_CLI();\
ADDRESS = VAL;\
_SEI();\
}
#define inw( ADDRESS, VAL )\
{\
_CLI();\
VAL = ADDRESS;\
_SEI();\
}
Anwendung
#include <io8515.h>
inw( TCNT1, i ) ; /* Reads the counter value */
outw( TCNT1, i ) ; /* Writes the counter value */