AVR 16-Bit-Register

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

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.

Um diese „Datenmüllproduktion“ zu verhindern, gibt es in beiden Fällen eine Synchronisation, die jeweils durch den Zugriff auf das Low-Byte ausgelöst wird:

  • Bei den Timer-Registern (das gilt für alle TCNT-, OCR- und ICR-Register bei den 16-Bit-Timern) wird bei einem Lesezugriff auf das Low-Byte automatisch das High-Byte in ein temporäres Register, das ansonsten nach außen nicht sichtbar ist, geschoben. Greift man nun anschließend auf das High-Byte zu, dann wird eben dieses temporäre Register gelesen.
  • Bei einem Schreibzugriff auf eines der genannten Register wird das High-Byte in besagtem temporären Register zwischengespeichert und erst beim Schreiben des Low-Bytes werden beide gleichzeitig in das eigentliche Register übernommen.

Das bedeutet für die Reihenfolge beim Lesezugriff: Erst Low-Byte, dann High-Byte und für den Schreibzugriff: Erst High-Byte, dann Low-Byte.

Des weiteren ist zu beachten, dass es für all diese 16-Bit-Register 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.

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 */