avr ersterer schritt
  Datenaustausch mit Interrupt-Routinen
 
Datenaustausch mit Interrupt-Routinen

Variablen die sowohl in Interrupt-Routinen (ISR = Interrupt Service Routine(s)), als auch vom übrigen Programmcode geschrieben oder gelesen werden, müssen mit einem volatile deklariert werden. Damit wird dem Compiler mitgeteilt, dass der Inhalt der Variablen vor jedem Lesezugriff aus dem Speicher gelesen und nach jedem Schreibzugriff in den Speicher geschrieben wird. Ansonsten könnte der Compiler den Code so optimieren, dass der Wert der Variablen nur in Prozessorregistern zwischengespeichert wird, die nichts von der Änderung woanders mitbekommen.

Zur Veranschaulichung ein Codefragment für eine Tastenentprellung mit Erkennung einer "lange gedrückten" Taste.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
//...
 
// Schwellwerte
// Entprellung: 
#define CNTDEBOUNCE 10
// "lange gedrueckt:"
#define CNTREPEAT 200
 
// hier z.&nbsp;B. Taste an Pin2 PortA "active low" = 0 wenn gedrueckt
#define KEY_PIN  PINA
#define KEY_PINNO PA2
 
// beachte: volatile! 
volatile uint8_t gKeyCounter;
 
// Timer-Compare Interrupt ISR, wird z.B. alle 10ms ausgefuehrt
ISR(TIMER1_COMPA_vect)
{
   // hier wird gKeyCounter veraendert. Die übrigen
   // Programmteile müssen diese Aenderung "sehen":
   // volatile -> aktuellen Wert immer in den Speicher schreiben
   if ( !(KEY_PIN & (1<<KEY_PINNO)) ) {
      if (gKeyCounter < CNTREPEAT) gKeyCounter++;
   }
   else {
      gKeyCounter = 0;
   }
}
 
//...
 
int main(void)
{
//...
    /* hier: Initialisierung der Ports und des Timer-Interrupts */
//... 
   // hier wird auf gKeyCounter zugegriffen. Dazu muss der in der
   // ISR geschriebene Wert bekannt sein:
   // volatile -> aktuellen Wert immer aus dem Speicher lesen
   if ( gKeyCounter > CNTDEBOUNCE ) { // Taste mind. 10*10 ms "prellfrei"
       if (gKeyCounter == CNTREPEAT) {
          /* hier: Code fuer "Taste lange gedrueckt" */
       }
       else {
          /* hier: Code fuer "Taste kurz gedrueckt" */
       }
   }
//...
}

Wird innerhalb einer ISR mehrfach auf eine mit volatile deklarierte Variable zugegriffen, wirkt sich dies ungünstig auf die Verarbeitungsgeschwindigkeit aus, da bei jedem Zugriff mit dem Speicherinhalt abgeglichen wird. Da bei AVR-Controllern innerhalb einer ISR keine Unterbrechungen zu erwarten sind, bietet es sich an, einen Zwischenspeicher in Form einer lokalen Variable zu verwenden, deren Inhalt zu Beginn und am Ende mit dem der volatile Variable synchronisiert wird. Lokale Variable werden bei eingeschalteter Optimierung mit hoher Wahrscheinlichkeit in Prozessorregistern verwaltet und der Zugriff darauf ist daher nur mit wenigen internen Operationen verbunden. Die ISR aus dem vorherigen Beispiel lässt sich so optimieren:

//...
ISR(TIMER1_COMPA_vect)
{
   uint8_t tmp_kc;
 
   tmp_kc = gKeyCounter; // Uebernahme in lokale Arbeitsvariable
 
   if ( !(KEY_PIN & (1<<KEY_PINNO)) ) {
      if (tmp_kc < CNTREPEAT) {
         tmp_kc++;
      }
   }
   else {
      tmp_kc = 0;
   }
 
   gKeyCounter = tmp_kc; // Zurueckschreiben
}
//... 

Zum Vergleich die Disassemblies (Ausschnitte der "lss-Dateien", compiliert für ATmega162) im Anschluss. Man erkennt den viermaligen Zugriff auf die Speicheraddresse von gKeyCounter (hier 0x032A) in der ISR ohne "Cache"-Variable und den zweimaligen Zugriff in der Variante mit Zwischenspeicher. Im Beispiel ist der Vorteil gering, bei komplexeren Routinen kann die Zwischenspeicherung in lokalen Variablen jedoch zu deutlicheren Verbesserungen führen.

ISR(TIMER1_COMPA_vect)
{
     86a:	1f 92       	push	r1
     86c:	0f 92       	push	r0
     86e:	0f b6       	in	r0, 0x3f	; 63
     870:	0f 92       	push	r0
     872:	11 24       	eor	r1, r1
     874:	8f 93       	push	r24
    if ( !(KEY_PIN & (1<<KEY_PINNO)) ) {
     876:	ca 99       	sbic	0x19, 2	; 25
     878:	0a c0       	rjmp	.+20     	; 0x88e <__vector_13+0x24>
      if (gKeyCounter < CNTREPEAT) gKeyCounter++;
     87a:	80 91 2a 03 	lds	r24, 0x032A
     87e:	88 3c       	cpi	r24, 0xC8	; 200 
     880:	40 f4       	brcc	.+16     	; 0x892 <__vector_13+0x28>
     882:	80 91 2a 03 	lds	r24, 0x032A
     886:	8f 5f       	subi	r24, 0xFF	; 255
     888:	80 93 2a 03 	sts	0x032A, r24
     88c:	02 c0       	rjmp	.+4      	; 0x892 <__vector_13+0x28>
   }
   else {
      gKeyCounter = 0;
     88e:	10 92 2a 03 	sts	0x032A, r1
     892:	8f 91       	pop	r24
     894:	0f 90       	pop	r0
     896:	0f be       	out	0x3f, r0	; 63
     898:	0f 90       	pop	r0
     89a:	1f 90       	pop	r1
     89c:	18 95       	reti
ISR(TIMER1_COMPA_vect)
{
     86a:	1f 92       	push	r1
     86c:	0f 92       	push	r0
     86e:	0f b6       	in	r0, 0x3f	; 63
     870:	0f 92       	push	r0
     872:	11 24       	eor	r1, r1
     874:	8f 93       	push	r24
   uint8_t tmp_kc;
 
   tmp_kc = gKeyCounter;
     876:	80 91 2a 03 	lds	r24, 0x032A
 
   if ( !(KEY_PIN & (1<<KEY_PINNO)) ) {
     87a:	ca 9b       	sbis	0x19, 2	; 25
     87c:	02 c0       	rjmp	.+4      	; 0x882 <__vector_13+0x18>
     87e:	80 e0       	ldi	r24, 0x00	; 0
     880:	03 c0       	rjmp	.+6      	; 0x888 <__vector_13+0x1e>
      if (tmp_kc < CNTREPEAT) {
     882:	88 3c       	cpi	r24, 0xC8	; 200
     884:	08 f4       	brcc	.+2      	; 0x888 <__vector_13+0x1e>
         tmp_kc++;
     886:	8f 5f       	subi	r24, 0xFF	; 255
      }
   }
   else {
      tmp_kc = 0;
   }
 
   gKeyCounter = tmp_kc;
     888:	80 93 2a 03 	sts	0x032A, r24
     88c:	8f 91       	pop	r24
     88e:	0f 90       	pop	r0
     890:	0f be       	out	0x3f, r0	; 63
     892:	0f 90       	pop	r0
     894:	1f 90       	pop	r1
     896:	18 95       	reti

[Bearbeiten] volatile und Pointer

Bei volatile in Verbindung mit Pointern ist zu beachten, ob der Pointer selbst oder die Variable auf die der Pointer zeigt volatile ist.

volatile uint8_t *a;   // das Ziel von a ist volatile
 
uint8_t *volatile a;   // a selbst ist volatile 

Falls der Pointer volatile ist (zweiter Fall im Beispiel), ist zu beachten, dass der Wert des Pointers, also eine Speicheradresse, intern in mehr als einem Byte verwaltet wird. Lese- und Schreibzugriffe im Hauptprogramm (ausserhalb von Interrupt-Routinen) sind daher so zu implementieren, dass alle Teilbytes der Adresse konsistent bleiben, vgl. dazu den folgenden Abschnitt.

[Bearbeiten] Variablen größer 1 Byte

Bei Variablen größer ein Byte, auf die in Interrupt-Routinen und im Hauptprogramm zugegriffen wird, muss darauf geachtet werden, dass die Zugriffe auf die einzelnen Bytes außerhalb der ISR nicht durch einen Interrupt unterbrochen werden. (Allgemeinplatz: AVRs sind 8-bit Controller). Zur Veranschaulichung ein Codefragment:

//...
volatile uint16_t gMyCounter16bit;
//...
ISR(...)
{
//...
   gMyCounter16Bit++;
//...
}
 
int main(void)
{
   uint16_t tmpCnt;
//...
   // nicht gut: Mglw. hier ein Fehler, wenn ein Byte von MyCounter 
   // schon in tmpCnt kopiert ist aber vor dem Kopieren des zweiten Bytes 
   // ein Interrupt auftritt, der den Inhalt von MyCounter verändert.
   tmpCnt = gMyCounter16bit; 
 
 
   // besser: Änderungen "außerhalb" verhindern -> alle "Teilbytes"
   // bleiben konsistent
   cli();  // Interrupts deaktivieren
   tmpCnt = gMyCounter16Bit;
   sei();  // wieder aktivieren
 
   // oder: vorheriger Status des globalen Interrupt-Flags bleibt erhalten
   uint8_t sreg_tmp;
   sreg_tmp = SREG;    /* Sichern */
   cli()
   tmpCnt = gMyCounter16Bit;
   SREG = sreg_tmp;    /* Wiederherstellen */
 
   // oder: mehrfach lesen, bis man konsistente Daten hat
   uint16_t count1 = gMyCounter16Bit;
   uint16_t count2 = gMyCounter16Bit;
   while (count1 != count2) {
       count1 = count2;
       count2 = gMyCounter16Bit;
   }
   tmpCnt = count1;
//...
}

Die avr-libc bietet ab Version 1.6.0(?) einige Hilfsfunktionen/Makros, mit der im Beispiel oben gezeigten Funktionalität, die zusätzlich auch so genannte memory barriers beinhalten. Diese stehen nach #include <util/atomic.h> zur Verfügung.

//...
#include <util/atomic.h>
//...
 
    // analog zu cli, Zugriff, sei:
    ATOMIC_BLOCK(ATOMIC_FORCEON) {
        tmpCnt = gMyCounter16Bit;
    }
 
// oder:
 
    // analog zu Sicherung des SREG, cli, Zugriff und Zurückschreiben des SREG:
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
        tmpCnt = gMyCounter16Bit;
    }
 
//... 
 
  Heute waren schon 1 Besucher (31 Hits) hier!  
 
Diese Webseite wurde kostenlos mit Homepage-Baukasten.de erstellt. Willst du auch eine eigene Webseite?
Gratis anmelden