Pollin Funk-AVR-Evaluationsboard

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

Aufbau

Praktisch ist bei dem Aufbau des Bausatzes nichts besonderes zu vermelden. Für einen Ungeübten (mich) hat der Aufbau ca. 2 h gedauert. Gegen Ende der Löterei lies meine Konzentration nach und ich musste ein paar unsaubere Lötstellen mit Entlötlitze nachbehandeln. Besser eine Pause machen.

Technisch/elektrisch siehe unter Weblinks der Erfahrungsbericht von Marco Schmoll (www.controller-designs.de).

Funktionstests

Nach der genauen Betrachtung mit einer Lupe und keinen Auffälligkeiten wurde eine 9V Gleichspannung an die Klemme J5 angelegt. LED NETZ leuchtet. LED1 und LED2 sind aus. Mein Netzteil kann den Strom anzeigen. Folgende Werte wurden beobachtet:

  • ca. 20 mA bei AVR nicht eingesetzt, MAX232 nicht eingesetzt
  • ca. 22 mA bei AVR nicht eingesetzt, MAX232 nicht eingesetzt, RESET gedrückt
  • ca. 25 mA bei AVR nicht eingesetzt, MAX232 eingesetzt
  • ca. 26 mA bei AVR nicht eingesetzt, MAX232 eingesetzt, RESET gedrückt
  • ca. 30 mA bei ATmega8 eingesetzt, MAX232 eingesetzt

Einstellen der Taktquelle

Die folgenden Kommandozeilen zur Einstellung der Taktquelle in den AVR Fuses beziehen sich auf einen Windows PC und die ISP Programmiersoftware AVRDUDE.

Parallelport ISP Typ STK200

ATmega8 Fuses lesen

@echo off
echo Parallelport ISP Typ STK200
echo Lese ATmega8 Fuses
d:\winavr\bin\avrdude -v -p atmega8 -c stk200 -P lpt1
Werkseinstellung Fuses Atmega8 (Anzeige im AVR Fuse Calculator)

Die Schnittstelle LPT1 ist an die verwendete Schnittstelle auf dem PC anzupassen. Ebenso der Pfad zu dem Programm avrdude.exe.

Die ausgelesenen Fuses bei einem Fabrikneuen Atmega8 sollten dem Bild rechts entsprechen.

Siehe auch

  • AVR Fuse Calculator von Mark Hämmerling. In den dortigen Default-Einstellungen ist der Watchdog aktiviert. Lässt man das so, funktioniert das Programm Blinky (s.u.) nicht wie erwartet: Nur LED2 zappelt, LED1 ist meist aus, weil vor dem Umschalten von LED1 der Watchdog den Atmega8 resettet, d.h. das Programm von neuem starten lässt.

ATmega8 1 MHz RC

@echo off
echo Parallelport ISP Typ STK200
echo Setze ATmega8 Fuses auf 1 MHz interner RC-Oszillator
d:\winavr\bin\avrdude -v -p atmega8 -c stk200 -P lpt1 -U lfuse:w:0xC1:m -U hfuse:w:0xD9:m

ATmega8 12 MHz Quarz

Wenn das Board nach Anleitung aufgebaut wurde, d.h. in Q2 der 12,000 MHz Quarz eingesetzt wurde, kann man den ATmega8 auf max. 12 MHz einstellen:

@echo off
echo Parallelport ISP Typ STK200
echo Setze ATmega8 Fuses auf 12 MHz Quarz
d:\winavr\bin\avrdude -v -p atmega8 -c stk200 -P lpt1 -U lfuse:w:0x2F:m -U hfuse:w:0xD9:m

Serieller ISP auf dem Board

ATmega8 Fuses lesen

@echo off
echo Serieller ISP auf dem Board
echo Lese ATmega8 Fuses
d:\winavr\bin\avrdude -v -p atmega8 -c ponyser -P com1

Die Schnittstelle COM1 ist an die verwendete Schnittstelle auf dem PC anzupassen. Wichtig ist, dass kein zusätzlicher Parallelport-ISP angeschlossen ist. Wenn doch, wird der Atmega8 nicht erkannt!

ATmega8 1 MHz RC

@echo off
echo Serieller ISP auf dem Board
echo Setze ATmega8 Fuses auf 1 MHz interner RC-Oszillator
d:\winavr\bin\avrdude -v -p atmega8 -c ponyser -P com1 -U lfuse:w:0xC1:m -U hfuse:w:0xD9:m

ATmega8 12 MHz Quarz

Wenn das Board nach Anleitung aufgebaut wurde, d.h. in Q2 der 12,000 MHz Quarz eingesetzt wurde, kann man den ATmega8 auf max. 12 MHz einstellen:

@echo off
echo Serieller ISP auf dem Board
echo Setze ATmega8 Fuses auf 12 MHz Quarz
d:\winavr\bin\avrdude -v -p atmega8 -c ponyser -P com1 -U lfuse:w:0x2F:m -U hfuse:w:0xD9:m

Programm ins Flash-ROM schreiben

Im folgenden wird angenommen, dass die zu programmierende Datei im iHEX-Format unter dem Namen atmega8.hex im aktuellen Verzeichnis befindet.

Parallelport ISP Typ STK200

@echo off
echo Parallelport ISP Typ STK200
echo Programmiere Atmega8 Flash-ROM mit Datei atmega8.hex
d:\winavr\bin\avrdude -p atmega8 -c stk200 -P lpt1 -e -U flash:w:atmega8.hex

Serieller ISP auf dem Board

@echo off
echo Serieller ISP auf dem Board
echo Programmiere Atmega8 Flash-ROM mit Datei atmega8.hex
d:\winavr\bin\avrdude -p atmega8 -c ponyser -P com1 -e -U flash:w:atmega8.hex

Beispielprogramme

Blinky

Die beiden LED LED1 und LED2 auf dem Board sollen im 1s Takt wechselweise An und Aus gehen.

Die beiden On-board-LEDs sind active-high geschaltet, d.h. wenn am Pin des AVR ein logische 1 (HIGH Pegel) ausgegeben wird, leuchtet die LED. Wird eine logische 0 (LOW Pegel) ausgegeben, leuchtet die LED nicht. Das ist andersrum als im AVR Tutorial.

Beschaltung des Tasters und der LEDs

<c> /*

   Atmega8
   Pollin Funk-AVR-Evaluationsboard v1.1
   Project -> Configuration Options in AVR Studio:
   Frequency:    1000000 bzw. 12000000
   Optimization: -Os
  • /
  1. include <avr/io.h>
  2. include <util/delay.h>

// LEDs sind active-high geschaltet

  1. define LED_AN(LED) (PORTD |= (1<<(LED)))
  2. define LED_AUS(LED) (PORTD &= ~(1<<(LED)))
  3. define LED_TOGGLE(LED) (PORTD ^= (1<<(LED)))
  4. define LED1 PD6
  5. define LED2 PD5
  6. define TASTER PB1

int main(void) {

 DDRB &= ~(1<<TASTER);          // Port B: Eingang für Taster
 DDRD |= (1<<LED1) | (1<<LED2); // Port D: Ausgang für LED1 und LED2
 // Anfangseinstellung
 LED_AN(LED1);
 LED_AUS(LED2);
 while(1)
 {
   _delay_ms(1000);  // Wert 1000 erlaubt ab avr-libc 1.6
   LED_TOGGLE(LED1);
   LED_TOGGLE(LED2);
 }

} </c>

Tasty

Wenn der On-board-Taster TASTER1 nicht gedrückt ist (Ruhezustand), soll die LED1 leuchten und LED2 soll nicht leuchten. Solange der User den Taster TASTER1 gedrückt hält, soll sich der Zustand der LEDs umkehren.

Der On-board-Taster TASTER1 ist ebenfalls active-high (siehe AVR-GCC-Tutorial) geschaltet, d.h. wenn der Taster geschlossen ist, liegt am Pin PB1 des AVR eine logische 1 (HIGH Pegel) an. Ist der Taster offen, liegt eine eine logische 0 (LOW Pegel) an. Das ist andersrum als im AVR Tutorial.

<c>

/*

   Atmega8
   Externer Quarz-Oszillator: 12 MHz
   Pollin Funk-AVR-Evaluationsboard v1.1
  • /
  1. include <avr/io.h>
  2. include <util/delay.h>

// LEDs sind high-active geschaltet

  1. define LED_AN(LED) (PORTD |= (1<<(LED)))
  2. define LED_AUS(LED) (PORTD &= ~(1<<(LED)))
  3. define LED_TOGGLE(LED) (PORTD ^= (1<<(LED)))
  4. define LED1 PD6
  5. define LED2 PD5

// TASTER ist high-active geschaltet

  1. define TASTER PB1
  2. define TASTER_GEDRUECKT() (PINB & (1<<TASTER))

int main(void) {

 DDRB &= ~(1<<TASTER);	         // Port B: Eingang für Taster
 DDRD |= (1<<LED1) | (1<<LED2); // Port D: Ausgang für LED1 und LED2
 while(1)
 {
   if (!TASTER_GEDRUECKT())
   {
     // Taster ist nicht (!) gedrückt
     LED_AN(LED1);
     LED_AUS(LED2);
   }
   else
   {
     // Taster ist gedrückt
     LED_AUS(LED1);
     LED_AN(LED2);
   }
 }

}

</c>

2-Bit Zähler

Jeder Tastendruck auf TASTER1 soll eine Variable um Eins hochzählen. Der Inhalt der unteren beiden Bits der Zählvariable soll mit den beiden LEDs angezeigt werden.

Das Hochzählen darf nur erfolgen, wenn ein Wechsel von Offen nach Geschlossen stattfindet. Das Programm muss also berücksichtigen, ob ein Wechsel von "Taster offen" zu "Taster geschlossen" stattfindet und ob der Taster in einer Position gehalten wird.

Mit diesem Beispiel kann man grob feststellen, ob der TASTER1 auf dem Board zum Prellen neigt, d.h. wenn sich der Zählerstand nicht wie gewollt pro Tastendruck um Eins erhöht und ob deshalb eine spezielle Routine zur Entprellung erforderlich ist.

Mein Board zeigt bei diesem Programm keine Neigung zum Prellen. Die auf dem Board vorhandene Hardwareentprellung über einen Tiefpassfilter mit C17 330 nF zwischen Taster und GND erfüllt hier ihren Zweck.

<c>

/*

   Atmega8
   Externer Quarz-Oszillator: 12 MHz
   Pollin Funk-AVR-Evaluationsboard v1.1
  • /
  1. include <avr/io.h>
  2. include <util/delay.h>
  3. include <inttypes.h>

// LEDs sind high-active geschaltet

  1. define LED_AN(LED) (PORTD |= (1<<(LED)))
  2. define LED_AUS(LED) (PORTD &= ~(1<<(LED)))
  3. define LED_TOGGLE(LED) (PORTD ^= (1<<(LED)))
  4. define LED1 PD6
  5. define LED2 PD5

// TASTER ist high-active geschaltet

  1. define TASTER PB1
  2. define TASTER_GEDRUECKT() (PINB & (1<<TASTER))
  3. define TASTE_AUF 0
  4. define TASTE_ZU 1

void ausgabe(uint8_t wert) {

 if (wert & (1<<0)) // Bit 0
   LED_AN(LED1);
 else
   LED_AUS(LED1);
 if (wert & (1<<1)) // Bit 1
   LED_AN(LED2);
 else
   LED_AUS(LED2);

}

int main(void) {

 uint8_t alter_tastenzustand = TASTE_AUF;
 uint8_t zaehler = 0;
 DDRB &= ~(1<<TASTER);			// Port B: Eingang für Taster
 DDRD |= (1<<LED1) | (1<<LED2);	// Port D: Ausgang für LED1 und LED2
 while(1)
 {
   ausgabe(zaehler);
   if (TASTER_GEDRUECKT() && (alter_tastenzustand == TASTE_AUF))
   {
     // Wechsel von OFFEN nach GESCHLOSSEN
     zaehler++;
     alter_tastenzustand = TASTE_ZU;
   }
   if (!TASTER_GEDRUECKT())
     alter_tastenzustand = TASTE_AUF;
 }

}

</c>

8-Bit Zähler mit RS232-Anschluss

Das Beispiel 2-Bit Zähler soll jetzt ausgebaut werden. Im Detail soll der 8-Bit Zählerstand über RS232 auf einen PC ausgegeben werden. Ausserdem soll "ferngesteuert" eine Veränderung des Zählerstands vom PC aus möglich sein.

Auf der PC-Seite wird die Kommunikation mit einem Terminalprogramm z.B. HyperTerm gemacht, so dass hier keine Programmierung notwendig ist. Lediglich die Einstellung der RS232-Schnittstelle (z.B. COM1, 9600/8/N/1) muss passen. Auf der Atmega8-Seite sind zunächst wenige Grundfunktionen für die RS232-Kommunikation zu schreiben.

Die AVR-Grundfunktionen für die RS232-Kommunikation sind eine Funktion für die Initialisierung der µC-eigenen UART-Schnittstelle (Bsp.: UART_init) und eine Funktion für das Senden eines Zeichens (Bsp.: UART_putchar) sowie je eine Funktion für das Warten auf ein Zeichens (Bsp.: UART_getchar) bzw. eine nicht-wartende Funktion um festzustellen, ob ein Zeichen des PCs an der UART-Schnittstelle des µC anliegt (Bsp. UART_peekchar).

Als Programmablauf wurde die UART-Kommunikation mit der technisch relativ einfachen Methode Polling eingerichtet, d.h. das Programm fragt selbst möglichst regelmässig und oft genug für einen sinnvolle Kommunikation die UART Schnittstelle ab, ob Zeichen empfangen wurden oder gesendet werden können. Hier erklärt sich auch der Zweck für die nicht-wartende Funktion UART_peekchar() - wenn kein Zeichen vom PC über RS232 anliegt, soll der µC mit dem bekannten Programmablauf weitermachen, damit die Taster-Eingaben ausgewertet werden. Die technisch meistens vorteilhaftere Alternative wäre die Interruptmethode, d.h. das Hauptprogramm wird automatisch genau dann unterbrochen, wenn ein Zeichen empfangen wurde oder gesendet werden kann und der µC verzweigt in spezielle Grundfunktionen, die sog. Interrupthandler, die sich um die Kommunikation kümmern. Nach dem Abarbeiten der Unterbrechung geht es dann im eigentlichen Hauptprogramm weiter. Ein UART_peekchar-Trick ist bei dieser Programmierweise nicht nötig. Die Programmierweise mit UART-Interrupts könnte Teil eines künftigen Beispiels sein.

Die Grundfunktionen werden von einer allgemeinen Funktion (Bsp.: rs232) verwendet, um den Zählerstand an den PC auszugeben bzw. um Eingaben auf dem PC in einen neuen Zählerstand umzusetzen. Da die Funktion rs232 den Zählerstand ändern soll, wird ihr die Adresse der Zählervariable (&zaehler) übergeben, d.h. es wird hier mit Zeigern (Pointern) gearbeitet.

Per RS232 können komfortabel mehr Informationen ausgegeben werden als über die beiden LEDs. Im Beispiel wird der Zählerstand in drei verschiedenen Zahlensystemen ausgegeben und es wird der Zustand der beiden LEDs übermittelt. Der Clou hinsichtlich Bedienung des Zählers ist, dass per RS232 auch komfortabel mehr Bedienmöglichkeiten eingerichtet werden können als mit dem einfachen Taster: Zusätzlich zum Hochzählen (Plus-Taste) kann z.B. runtergezählt werden (Minus-Taste) oder es kann direkt eine bis zu dreistellige Zahlenfolge eingegeben werden, die zum Setzen des Zählerstands verwendet wird.

Bei der Initialisierung der UART wurden die Einstellungen 9600 Baud, 8 Datenbits, Keine Parity (No Parity) und 1 Stopbit gewählt. 8/N/1 ist ein gängiger Wert für RS232. Bei der Auswahl der Baudrate muss darauf geachtet werden, dass mit der gegebenen Taktrate auf dem Board (hier 12 MHz) nicht jede denkbare RS232-Baudrate gleich gut geeignet ist. Das Register zur Einstellung der Baudrate im µC kann nur mit ganzen Zahlen gefüllt werden, die dann benutzt werden, um durch Teilen die Taktrate der UART aus dem Takt der CPU abzuleiten. Bei einer CPU Taktrate von 12 MHz ergeben folgende Soll-Baudraten (in Baud) die angegebenen Einstellungen des Baudratenregisters (UBRR) und die Ist-Baudraten sowie die Abweichungen zwischen Soll- und Ist-Baudrate in Prozent: Die Formeln zur Berechnung stehen im Atmega8 Datenblatt im Kapitel UART.


Soll-Baudrate UBRR
(bei U2X=0)
Ist-Baudrate
(bei U2X=0)
Abweichung [%]
(bei U2X=0)
UBRR
(bei U2X=1)
Ist-Baudrate
(bei U2X=1)
Abweichung [%]
(bei U2X=1)
300 2499 300 0,0 - - -
1200 624 1200 0,0 1249 1200 0,0
2400 311 2403 0,1 624 2400 0,0
4800 155 4807 0,1 311 4807 0,1
9600 77 9615 0,2 155 9615 0,2
14,4K 51 14423 0,2 103 14423 0,2
19,2K 38 19230 0,2 77 19230 0,2
28,8K 25 28846 0,2 51 28846 0,2
38,4K 18 37473 2,8 38 38461 0,2
57,6K 12 57692 0,2 25 57692 0,2
76,8K 8 83333 8,5 16 78947 2,8
115,2K 5 125000 8,5 12 115384 0,2
230,4K 2 250000 8,5 5 250000 8,5
250K 2 250000 0,0 5 250000 0,0


Bestimmte Baudraten können ohne Abweichung oder mit tolerierbar kleiner Abweichung (max. 0,2%) eingestellt werden, während andere Baudraten immer zu grosse Abweichungen für eine fehlerfreie Kommunikation ergeben. Man kann obige Tabelle (von denen es ähnliche im Datenblatt für andere Taktraten gibt) verwenden oder man kann die UBRR Einstellung im Programmcode berechnen lassen (s. Programmcode unten). Im AVR-GCC-Tutorial ist zusätzlich ganz komfortabel eine Berechnung der Abweichung und eine Warnung bei zu grossen Werten angegeben, so dass man seine Wunschbaudrate in Richtung geringe Abweichung optimieren kann. Im folgenden Code wurde die gängige Baudrate 9600 Baud eingestellt.

Eine letzte grössere Änderung gegenüber dem 2-Bit-Zähler fällt an der Stelle der Taster-Eingabe auf: Jetzt mit der genaueren RS232-Ausgabe wurden Prelleffekte als unregelmässige Sprünge im Zählerstand beobachtet. Deshalb wurde Programmcode für eine einfache Entprellung nach dem Warteschleifenverfahren hinzugefügt. Wenn das Makro ENTPRELLUNG mit 0 definiert ist, entfällt die Entprellung, d.h. das Programm reagiert wie beim 2-Bit Zähler auf den Taster. Um die Entprellung zu aktivieren, wird das Makro ENTPRELLUNG z.B. mit 10, 25 oder 30 definiert und neu kompiliert und geflasht. Die Zahl im Makro gibt eine Wartezeit an, nach der der Zustand des Tasters nochmal geprüft wird. Hier kann man etwas experimentieren, was ein geeigneter Wert wäre.

<c>

/*

   Pollin Funk-AVR-Evaluationsboard v1.1
   Atmega8
   Externer Quarz-Oszillator: 12 MHz
   Optimierung: -Os
   RS232-Einstellungen:
   9600 Baud, 8 Bits, No Parity, 1 Stopbit
  • /
  1. include <avr/io.h>
  2. include <util/delay.h>
  3. include <stdlib.h>

// LEDs sind high-active geschaltet

  1. define LED_AN(LED) (PORTD |= (1<<(LED)))
  2. define LED_AUS(LED) (PORTD &= ~(1<<(LED)))
  3. define LED_TOGGLE(LED) (PORTD ^= (1<<(LED)))
  4. define LED1 PD6
  5. define LED2 PD5

// TASTER ist high-active geschaltet

  1. define TASTER PB1
  2. define TASTER_GEDRUECKT() (PINB & (1<<TASTER))
  3. define TASTE_AUF 0
  4. define TASTE_ZU 1

/* in Millisekunden */

  1. define ENTPRELLUNG 10

void UART_init(void) {

   // Baudrate setzen
   // 9600 Baud bei F_CPU 12 MHz
   // (Baudratenfehler = +0,2%)
   UBRRH = (uint8_t) ((F_CPU / (16 * 9600L) - 1) >> 8);
   UBRRL = (uint8_t)  (F_CPU / (16 * 9600L) - 1);
   //      Empfangen,   Senden erlauben
   UCSRB = (1<<RXEN) | (1<<TXEN);
   // Frame Format:              8 Bits,                  No Parity,          1 Stopbit
   UCSRC = (1<<URSEL) | ((1<<UCSZ1) | (1<<UCSZ0)) | ((0<<UPM1) | (0<<UPM0)) | (0<<USBS);

}

// Auf Zeichen im UART Eingang prüfen uint8_t UART_peekchar(void) {

   // sofort zurückkehren und Zustand melden
   return UCSRA & (1<<RXC);

}

// Auf Zeichen im UART Eingang warten uint8_t UART_getchar(void) {

   // Warte bis UART Eingang voll
   while ( !(UCSRA & (1<<RXC)) )
       ;
   return UDR;

}

// Zeichen in UART Ausgang geben void UART_putchar(char z) {

   // Warte bis UART Ausgang frei
   while ( !(UCSRA & (1<<UDRE)) )
       ;
   UDR = z;

}

// Zeichenkette (String) in UART Ausgang geben void UART_puts(char *s) {

   while ( *s )
   {
       UART_putchar(*s);
       s++;
   }

}

  1. define EINGABE_START 0
  2. define EINGABE_ABBRUCH EINGABE_START
  3. define EINGABE_MAX 3
  4. define FORMFEED '\014'


void rs232(uint8_t *wert) {

   static int16_t alter_wert = -1;
   uint8_t anzahl_ziffern;
   uint8_t zeichen;
   char puffer[23];
   // Eingabe über RS232
   // Ohne Warten prüfen, ob Zeichen an UART vorhanden
   if ( UART_peekchar() )
   {
       // Zeichen ist da.
       // Aktuellen Wert ausgeben
       // Neue Seite im Terminal anfordern.
       // Bewirkt ein Löschen des Bildschirms
       UART_putchar(FORMFEED);
       // Einleitender Text, was im folgenden ausgegeben wird
       UART_puts("Alter Z\204hler = "); // \204 ist ä in Oktalschreibweise
       // Zahl in Ziffernfolge umwandeln. Hier zuerst in Dezimalziffern
       itoa((int) *wert, puffer, 10);
       UART_puts(puffer);
       // Dann die gleiche Zahl in Hexadezimalziffern
       UART_puts(" 0x");
       itoa((int) *wert, puffer, 16);
       UART_puts(puffer);
       // Dann die gleiche Zahl in Binärziffern
       UART_puts(" 0b");
       itoa((int) *wert, puffer, 2);
       UART_puts(puffer);
       // Schliesslich noch der Zustand der LEDs auf dem Board senden
       UART_puts( (*wert & 2) ? " LED2-An " : " LED2-Aus");
       UART_puts( (*wert & 1) ? " LED1-An" : " LED1-Aus");
       // Zeilenabschluss zur Formatierung der Anzeige
       UART_puts("\r\n");
       // Neuen Wert eingeben
       // Initialisierung
       UART_puts("Eingabe> ");
       anzahl_ziffern = EINGABE_START;
       // Eingabeschleife
       do
       {
           // 1. bereits Zeichen am UART Eingang abholen
           // in weiteren Schleifen auf neue Zeichen warten
           zeichen = UART_getchar();
           // Zeichen auswerten
           if ( (zeichen == '-') && (anzahl_ziffern == EINGABE_START) )
           {
               // MINUS
               UART_puts(" -1");
               UART_puts("\r\n");
               *wert -= 1;
               break;
           }
           else if ( (zeichen == '+') && (anzahl_ziffern == EINGABE_START) )
           {
               // PLUS
               UART_puts(" +1");
               UART_puts("\r\n");
               *wert += 1;
               break;
           }
           else if ( (zeichen == '\n') || (zeichen == '\r') )
           {
               // ENTER oder RETURN: Abschluss der Eingabe
               break;
           }
           else if ( (zeichen >= '0') && (zeichen <= '9') )
           {
               // Eingabe einer Ziffer
               UART_putchar(zeichen); // Echo
               puffer[anzahl_ziffern++] = zeichen;
               puffer[anzahl_ziffern] = 0;
           }
           else
           {
               // Sonstiges Zeichen ist für uns illegal
               anzahl_ziffern = EINGABE_ABBRUCH;
               UART_puts(" Abbruch.");
               UART_puts("\r\n");
               break;
           }
       }
       while ( anzahl_ziffern < EINGABE_MAX );
       // Eingabe prüfen
       if ( anzahl_ziffern != EINGABE_ABBRUCH )
       {
           // Ziffern in Zahl umwandeln
           int i = atoi(puffer);
           // z.B. erlaubter Wertebereich sei 0 bis 255
           if ( (i >= 0) && (i < 256) )
           {
               UART_puts(" Ok.");
               UART_puts("\r\n");
               *wert = (uint8_t) i;
           }
           else
               UART_puts(" Ung\201ltig.\r\n"); // \201 ist ü in Oktalschreibweise
       }
   }
   // Ausgabe auf RS232 (s.oben)
   // bei nur Änderung des Wertes gegenüber der letzten Ausgabe
   //
   // alter_wert wurde mit -1 initialisiert. Das ist ausserhalb
   // des Wertebereichs von *wert, D.h. spätere Abfrage, ob
   // (alter_wert != *wert) ist, ist beim ersten Durchlauf wie
   // gewünscht wahr,
   //
   if (alter_wert != *wert)
   {
       alter_wert = *wert;
       UART_puts("Z\204hler = ");
       itoa((int) *wert, puffer, 10);
       UART_puts(puffer);
       UART_puts(" 0x");
       itoa((int) *wert, puffer, 16);
       UART_puts(puffer);
       UART_puts(" 0b");
       itoa((int) *wert, puffer, 2);
       UART_puts(puffer);
       UART_puts( (*wert & 2) ? " LED2-An " : " LED2-Aus");
       UART_puts( (*wert & 1) ? " LED1-An" : " LED1-Aus");
       UART_puts("\r\n");
   }

}

void ausgabe_led(uint8_t wert) {

   if (wert & (1<<0)) // Bit 0
       LED_AN(LED1);
   else
       LED_AUS(LED1);
   if (wert & (1<<1)) // Bit 1
       LED_AN(LED2);
   else
       LED_AUS(LED2);

}

int main(void) {

   uint8_t alter_tastenzustand = TASTE_AUF;
   uint8_t zaehler = 0;
   DDRB &= ~(1<<TASTER);           // Port B: Eingang für Taster
   DDRD |= (1<<LED1) | (1<<LED2);  // Port D: Ausgang für LED1 und LED2
   UART_init();
   UART_putchar(FORMFEED);
   while (1)
   {
       rs232(&zaehler);
       ausgabe_led(zaehler);
       if ( TASTER_GEDRUECKT() )
       {
  1. if ENTPRELLUNG
           _delay_ms(ENTPRELLUNG);
           if ( TASTER_GEDRUECKT() )
  1. endif /* ENTPRELLUNG */
           {
               // Wechsel von OFFEN nach GESCHLOSSEN?
               if ( alter_tastenzustand == TASTE_AUF )
               {
                   zaehler++;
                   alter_tastenzustand = TASTE_ZU;
               }
           }
       }
       if ( !TASTER_GEDRUECKT() )
       {
  1. if ENTPRELLUNG
           _delay_ms(ENTPRELLUNG);
           if ( !TASTER_GEDRUECKT() )
  1. endif /* ENTPRELLUNG */
               alter_tastenzustand = TASTE_AUF;
       }
   }

}

</c>

Zähler mit LCD-Anzeige

Als nächstes soll noch eine LCD-Anzeige installiert werden.

In meiner Bastelkiste lag noch ein Text-LCD mit dem LCD-Controller HD44780. Erfreulicherweise gibt es für diesen LCD-Controller jede Menge fertigen Beispielcode zur Ansteuerung im Internet u.a. im AVR-GCC-Tutorial.

Um den Beispielcode in der Form einzubinden, wie er im Tutorial zu finden ist und um das mittlerweise angewachsene Projekt übersichtlicher zu machen, werden zusammenhängende Codeteile in Unterdateien ausgelagert. Im folgenden Bild ist zu sehen, wie die einzelnen Dateien heissen und wie sie im AVR Studio eingebunden werden.


PFA Projekt ZLCD.png


Die Datei zaehler_lcd.c enthält die main-Funktion und die neue Ausgabefunktion ausgabe_lcd und die bereits bekannte Funktion ausgabe_led:

<c>

/*

   Pollin Funk-AVR-Evaluationsboard v1.1
   Atmega8
   Externer Quarz-Oszillator: 12 MHz
   Optimierung: -Os
   RS232-Einstellungen:
   9600 Baud, 8 Bits, No Parity, 1 Stopbit
  • /
  1. include <avr/io.h>
  2. include <util/delay.h>
  3. include <stdlib.h>
  1. include "funkhw.h"
  2. include "rs232.h"
  3. include "lcd-routines.h"

/* in Millisekunden */

  1. define ENTPRELLUNG 10

void ausgabe_lcd(uint8_t wert) {

   static int16_t alter_wert = -1;
   char puffer[23];
   if (wert != alter_wert)
   {
       // Ausgabe nur bei Änderungen
       alter_wert = wert;
       itoa((int) wert, puffer, 10);
       lcd_clear();
       lcd_string("Zaehler = ");
       lcd_string(puffer);
   }

}

void ausgabe_led(uint8_t wert) {

   if (wert & (1<<0)) // Bit 0
       LED_AN(LED1);
   else
       LED_AUS(LED1);
   if (wert & (1<<1)) // Bit 1
       LED_AN(LED2);
   else
       LED_AUS(LED2);

}

int main(void) {

   uint8_t alter_tastenzustand = TASTE_AUF;
   uint8_t zaehler = 0;
   DDRB &= ~(1<<TASTER);           // Port B: Eingang für Taster
   DDRD |= (1<<LED1) | (1<<LED2);  // Port D: Ausgang für LED1 und LED2
   UART_init();
   UART_putchar(FORMFEED);
   lcd_init();
   while (1)
   {
       rs232(&zaehler);			// RS232 Eingabe und Ausgabe
       ausgabe_lcd(zaehler); // LCD Ausgabe
       ausgabe_led(zaehler); // LED Ausgabe
       if ( TASTER_GEDRUECKT() )
       {
  1. if ENTPRELLUNG
           _delay_ms(ENTPRELLUNG);
           if ( TASTER_GEDRUECKT() )
  1. endif /* ENTPRELLUNG */
           {
               // Wechsel von OFFEN nach GESCHLOSSEN?
               if ( alter_tastenzustand == TASTE_AUF )
               {
                   zaehler++;
                   alter_tastenzustand = TASTE_ZU;
               }
           }
       }
       if ( !TASTER_GEDRUECKT() )
       {
  1. if ENTPRELLUNG
           _delay_ms(ENTPRELLUNG);
           if ( !TASTER_GEDRUECKT() )
  1. endif /* ENTPRELLUNG */
               alter_tastenzustand = TASTE_AUF;
       }
   }

}

</c>

Die Datei lcd_routines.c enthält die Grundfunktionen für die LCD-Ansteuerung aus dem AVR-GCC-Tutorial.

In der Funktion lcd_enable musste das Timing leicht angepasst werden, weil bei meinem Display mit der 1 µs Pause Störungen aufgetreten waren.

<c>

// Ansteuerung eines HD44780 kompatiblen LCD im 4-Bit-Interfacemodus // http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial // // Die Pinbelegung ist über defines in lcd-routines.h einstellbar

  1. include <avr/io.h>
  2. include "lcd-routines.h"
  3. include <util/delay.h>

// sendet ein Datenbyte an das LCD

void lcd_data(unsigned char temp1) {

   unsigned char temp2 = temp1;
   LCD_PORT |= (1<<LCD_RS);        // RS auf 1 setzen
   temp1 = temp1 >> 4;
   temp1 = temp1 & 0x0F;
   LCD_PORT &= 0xF0;
   LCD_PORT |= temp1;               // setzen
   lcd_enable();
   temp2 = temp2 & 0x0F;
   LCD_PORT &= 0xF0;
   LCD_PORT |= temp2;               // setzen
   lcd_enable();
   _delay_us(42);

}

// sendet einen Befehl an das LCD

void lcd_command(unsigned char temp1) {

   unsigned char temp2 = temp1;
   LCD_PORT &= ~(1<<LCD_RS);        // RS auf 0 setzen
   temp1 = temp1 >> 4;              // oberes Nibble holen
   temp1 = temp1 & 0x0F;            // maskieren
   LCD_PORT &= 0xF0;
   LCD_PORT |= temp1;               // setzen
   lcd_enable();
   temp2 = temp2 & 0x0F;            // unteres Nibble holen und maskieren
   LCD_PORT &= 0xF0;
   LCD_PORT |= temp2;               // setzen
   lcd_enable();
   _delay_us(42);

}

// erzeugt den Enable-Puls void lcd_enable(void) {

   // Bei Problemen ggf. Pause gemäß Datenblatt des LCD Controllers einfügen
   // http://www.mikrocontroller.net/topic/81974#685882
   LCD_PORT |= (1<<LCD_EN);
  1. if 1
   _delay_us(4); // kurze Pause, Original aus dem Tutorial ist bei meinem Display zu kurz!
  1. else
   _delay_us(1); // kurze Pause
  1. endif
   // Bei Problemen ggf. Pause gemäß Datenblatt des LCD Controllers verlängern
   // http://www.mikrocontroller.net/topic/80900
   LCD_PORT &= ~(1<<LCD_EN);

}

// Initialisierung: // Muss ganz am Anfang des Programms aufgerufen werden.

void lcd_init(void) {

   LCD_DDR = LCD_DDR | 0x0F | (1<<LCD_RS) | (1<<LCD_EN);   // Port auf Ausgang schalten
   // muss 3mal hintereinander gesendet werden zur Initialisierung
   _delay_ms(15);
   LCD_PORT &= 0xF0;
   LCD_PORT |= 0x03;
   LCD_PORT &= ~(1<<LCD_RS);      // RS auf 0
   lcd_enable();
   _delay_ms(5);
   lcd_enable();
   _delay_ms(1);
   lcd_enable();
   _delay_ms(1);
   // 4 Bit Modus aktivieren
   LCD_PORT &= 0xF0;
   LCD_PORT |= 0x02;
   lcd_enable();
   _delay_ms(1);
   // 4Bit / 2 Zeilen / 5x7
   lcd_command(0x28);
   // Display ein / Cursor aus / kein Blinken
   lcd_command(0x0C);
   // inkrement / kein Scrollen
   lcd_command(0x06);
   lcd_clear();

}

// Sendet den Befehl zur Löschung des Displays

void lcd_clear(void) {

   lcd_command(CLEAR_DISPLAY);
   _delay_ms(5);

}

// Sendet den Befehl: Cursor Home

void lcd_home(void) {

   lcd_command(CURSOR_HOME);
   _delay_ms(5);

}

// setzt den Cursor in Zeile y (1..4) Spalte x (0..15)

void set_cursor(uint8_t x, uint8_t y) {

   uint8_t tmp;
   switch (y)
   {
   case 1:
       tmp=0x80+0x00+x;
       break;    // 1. Zeile
   case 2:
       tmp=0x80+0x40+x;
       break;    // 2. Zeile
   case 3:
       tmp=0x80+0x10+x;
       break;    // 3. Zeile
   case 4:
       tmp=0x80+0x50+x;
       break;    // 4. Zeile
   }
   lcd_command(tmp);

}

// Schreibt einen String auf das LCD

void lcd_string(char *data) {

   while (*data)
   {
       lcd_data(*data);
       data++;
   }

}

</c>

Die Includedatei lcd_routines.h enthält die Leitungszuordnung für die LCD-Ansteuerung und die Prototypen für die LCD Funktionen.

Hier wurden die Signalleitungen von PORTD auf PORTC geändert. Und die Definition von F_CPU wurde mit #if/#endif auskommentiert, weil dieser Wert bereits mit den Projekt Configuration Options im AVR Studio auf 12 MHz gesetzt ist.

<c>

// Ansteuerung eines HD44780 kompatiblen LCD im 4-Bit-Interfacemodus // http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial // void lcd_data(unsigned char temp1); void lcd_string(char *data); void lcd_command(unsigned char temp1); void lcd_enable(void); void lcd_init(void); void lcd_home(void); void lcd_clear(void); void set_cursor(uint8_t x, uint8_t y);

  1. if 0

// Hier die verwendete Taktfrequenz in Hz eintragen, wichtig!

  1. define F_CPU 8000000
  2. endif

// LCD Befehle

  1. define CLEAR_DISPLAY 0x01
  2. define CURSOR_HOME 0x02

// Pinbelegung für das LCD, an verwendete Pins anpassen

  1. if 1

// Änderung des LCD Anschlusses // Pollin Funk AVR Evaluationsboard v1.1 // DB4 bis DB7 des LCD sind mit PC0 bis PC3 des AVR verbunden

  1. define LCD_PORT PORTC
  2. define LCD_DDR DDRC
  3. define LCD_EN PC5
  4. define LCD_RS PC4
  5. define LCD_DB7 PC3
  6. define LCD_DB6 PC2
  7. define LCD_DB5 PC1
  8. define LCD_DB4 PC0
  9. else
  10. define LCD_PORT PORTD
  11. define LCD_DDR DDRD
  12. define LCD_RS PD4
  13. define LCD_EN PD5

// DB4 bis DB7 des LCD sind mit PD0 bis PD3 des AVR verbunden

  1. endif

</c>

Die Includedatei funkhw.h enthält die gemeinsamen, boardspezifischen Port-Definitionen, die in mehreren Unterdateien benötigt werden:

<c>

/*

   Pollin Funk-AVR-Evaluationsboard v1.1
   Atmega8
  • /

// LEDs sind high-active geschaltet

  1. define LED_AN(LED) (PORTD |= (1<<(LED)))
  2. define LED_AUS(LED) (PORTD &= ~(1<<(LED)))
  3. define LED_TOGGLE(LED) (PORTD ^= (1<<(LED)))
  4. define LED1 PD6
  5. define LED2 PD5

// TASTER ist high-active geschaltet

  1. define TASTER PB1
  2. define TASTER_GEDRUECKT() (PINB & (1<<TASTER))
  3. define TASTE_AUF 0
  4. define TASTE_ZU 1

</c>

Die Includedatei rs232.h enthält die Prototypen für die Funktionen in rs232.c:

<c>

  1. ifndef FORMFEED
  2. define FORMFEED '\014'
  3. endif

void UART_init(void);

// Auf Zeichen im UART Eingang prüfen uint8_t UART_peekchar(void);

// Auf Zeichen im UART Eingang warten uint8_t UART_getchar(void);

// Zeichen in UART Ausgang geben void UART_putchar(char z);

// Zeichenkette (String) in UART Ausgang geben void UART_puts(char *s);

void rs232(uint8_t *wert);

</c>

Und zum Schluss noch die Datei rs232.c mit den ungeänderten UART-Grundfunktionen und der rs232-Funktion.

<c>

/*

   Pollin Funk-AVR-Evaluationsboard v1.1
   Atmega8
   Externer Quarz-Oszillator: 12 MHz
   Optimierung: -Os
   RS232-Einstellungen:
   9600 Baud, 8 Bits, No Parity, 1 Stopbit
  • /
  1. include <avr/io.h>
  2. include <util/delay.h>
  3. include <stdlib.h>
  1. include "funkhw.h"
  2. include "rs232.h"
  1. define EINGABE_START 0
  2. define EINGABE_ABBRUCH EINGABE_START
  3. define EINGABE_MAX 3

void UART_init(void) {

   // Bausrate setzen
   // 9600 Baud bei F_CPU 12 MHz
   // (Baudratenfehler = +0,2%)
   UBRRH = (uint8_t) ((F_CPU / (16 * 9600L) - 1) >> 8);
   UBRRL = (uint8_t)  (F_CPU / (16 * 9600L) - 1);
   //      Empfangen,   Senden erlauben
   UCSRB = (1<<RXEN) | (1<<TXEN);
   // Frame Format:              8 Bits,                  No Parity,          1 Stopbit
   UCSRC = (1<<URSEL) | ((1<<UCSZ1) | (1<<UCSZ0)) | ((0<<UPM1) | (0<<UPM0)) | (0<<USBS);

}

// Auf Zeichen im UART Eingang prüfen uint8_t UART_peekchar(void) {

   // sofort zurückkehren und Zustand melden
   return UCSRA & (1<<RXC);

}

// Auf Zeichen im UART Eingang warten uint8_t UART_getchar(void) {

   // Warte bis UART Eingang voll
   while ( !(UCSRA & (1<<RXC)) )
       ;
   return UDR;

}

// Zeichen in UART Ausgang geben void UART_putchar(char z) {

   // Warte bis UART Ausgang frei
   while ( !(UCSRA & (1<<UDRE)) )
       ;
   UDR = z;

}

// Zeichenkette (String) in UART Ausgang geben void UART_puts(char *s) {

   while ( *s )
   {
       UART_putchar(*s);
       s++;
   }

}

void rs232(uint8_t *wert) {

   static int16_t alter_wert = -1;
   uint8_t anzahl_ziffern;
   uint8_t zeichen;
   char puffer[23];
   // Eingabe über RS232
   // Ohne Warten prüfen, ob Zeichen an UART vorhanden
   if ( UART_peekchar() )
   {
       // Zeichen ist da.
       // Aktuellen Wert ausgeben
       // Neue Seite im Terminal anfordern.
       // Bewirkt ein Löschen des Bildschirms
       UART_putchar(FORMFEED);
       // Einleitender Text, was im folgenden ausgegeben wird
       UART_puts("Alter Z\204hler = "); // \204 ist ä in Oktalschreibweise
       // Zahl in Ziffernfolge umwandeln. Hier zuerst in Dezimalziffern
       itoa((int) *wert, puffer, 10);
       UART_puts(puffer);
       // Dann die gleiche Zahl in Hexadezimalziffern
       UART_puts(" 0x");
       itoa((int) *wert, puffer, 16);
       UART_puts(puffer);
       // Dann die gleiche Zahl in Binärziffern
       UART_puts(" 0b");
       itoa((int) *wert, puffer, 2);
       UART_puts(puffer);
       // Schliesslich noch der Zustand der LEDs auf dem Board senden
       UART_puts( (*wert & 2) ? " LED2-An " : " LED2-Aus");
       UART_puts( (*wert & 1) ? " LED1-An" : " LED1-Aus");
       // Zeilenabschluss zur Formatierung der Anzeige
       UART_puts("\r\n");
       // Neuen Wert eingeben
       // Initialisierung
       UART_puts("Eingabe> ");
       anzahl_ziffern = EINGABE_START;
       // Eingabeschleife
       do
       {
           // 1. bereits Zeichen am UART Eingang abholen
           // in weiteren Schleifen auf neue Zeichen warten
           zeichen = UART_getchar();
           // Zeichen auswerten
           if ( (zeichen == '-') && (anzahl_ziffern == EINGABE_START) )
           {
               // MINUS
               UART_puts(" -1");
               UART_puts("\r\n");
               *wert -= 1;
               break;
           }
           else if ( (zeichen == '+') && (anzahl_ziffern == EINGABE_START) )
           {
               // PLUS
               UART_puts(" +1");
               UART_puts("\r\n");
               *wert += 1;
               break;
           }
           else if ( (zeichen == '\n') || (zeichen == '\r') )
           {
               // ENTER oder RETURN: Abschluss der Eingabe
               break;
           }
           else if ( (zeichen >= '0') && (zeichen <= '9') )
           {
               // Eingabe einer Ziffer
               UART_putchar(zeichen); // Echo
               puffer[anzahl_ziffern++] = zeichen;
               puffer[anzahl_ziffern] = 0;
           }
           else
           {
               // Sonstiges Zeichen ist für uns illegal
               anzahl_ziffern = EINGABE_ABBRUCH;
               UART_puts(" Abbruch.");
               UART_puts("\r\n");
               break;
           }
       }
       while ( anzahl_ziffern < EINGABE_MAX );
       // Eingabe prüfen
       if ( anzahl_ziffern != EINGABE_ABBRUCH )
       {
           // Ziffern in Zahl umwandeln
           int i = atoi(puffer);
           // z.B. erlaubter Wertebereich sei 0 bis 255
           if ( (i >= 0) && (i < 256) )
           {
               UART_puts(" Ok.");
               UART_puts("\r\n");
               *wert = (uint8_t) i;
           }
           else
               UART_puts(" Ung\201ltig.\r\n"); // \201 ist ü in Oktalschreibweise
       }
   }
   // Ausgabe auf RS232 (s.oben)
   // bei nur Änderung des Wertes gegenüber der letzten Ausgabe
   //
   // alter_wert wurde mit -1 initialisiert. Das ist ausserhalb
   // des Wertebereichs von *wert, D.h. spätere Abfrage, ob
   // (alter_wert != *wert) ist, ist beim ersten Durchlauf wie
   // gewünscht wahr,
   //
   if (alter_wert != *wert)
   {
       alter_wert = *wert;
       UART_puts("Z\204hler = ");
       itoa((int) *wert, puffer, 10);
       UART_puts(puffer);
       UART_puts(" 0x");
       itoa((int) *wert, puffer, 16);
       UART_puts(puffer);
       UART_puts(" 0b");
       itoa((int) *wert, puffer, 2);
       UART_puts(puffer);
       UART_puts( (*wert & 2) ? " LED2-An " : " LED2-Aus");
       UART_puts( (*wert & 1) ? " LED1-An" : " LED1-Aus");
       UART_puts("\r\n");
   }

}

</c>

Eine Uhr

Nachdem jetzt ein paar Eingabe und Ausgaberoutinen vorhanden sind, sollen jetzt die weiteren Innereien des ATmega8 erkundet werden. Als erstes kommt der Timer und damit die erste Interrupt-Programmierung an die Reihe. Und was liegt dem Thema näher, als eine Uhr zu programmieren...

Zunächst der Aufbau des Projektes:

  • PFA_uhr.c - Das Hauptprogramm
  • PFA_funkhw.h - Definitionen für das PFA ;-) Board
  • PFA_rs232.c - Erweiterte rs232-Funktionen
  • PFA_rs232.h - Prototypen zu den rs232-Funktionen
  • PFA_uart.c - UART Grundfunktionen (aus ehemaligem rs232.c herausgezogen)
  • PFA_uart.h - Prototypen zu den UART Grundfunktionen
  • lcd-routines.c - Unverändert s.o.
  • lcd-routines.h - Unverändert s.o.

PFA_uhr.c - Das Hauptprogramm

Drei Funktionen werden für die Uhr benötigt. Selbstverständlich eine Funktion zum Anzeigen der Uhrzeit auf dem LCD (Bsp.: uhrzeit_lcd). Und eine Funktion zum Starten der Uhr (Bsp.: uhr_init).

Sowie eine TickTack-Funktion, die regelmässig die Uhrzeit erhöht und zwar egal was das Programm sonst macht (Tasteneingabe, RS232 Ausgabe...)! Diese letzte Funktion heisst ISR(TIMER0_OVF_vect) und ist eine sog. Interrupt Service Routine für den TIMER0 OVerFlow Interrupt. Die Namen solcher ISR Funktionen sind nicht frei wählbar, sondern den einzelnen Interrupts fest zugeordnet. Wie die ISRs heissen, kann man in der Dokumentation zum jeweiligen AVR nachlesen.

Die Auswahl von TIMER0 ist willkürlich. Der Atmega8 hat insgesamt drei Timer, die unterschiedliche Fähigkeiten haben. TIMER0 ist davon der mit der geringsten Ausstattung, aber genau richtig, um damit eine Uhr zu machen und die anderen Timer für anderes freizuhalten.

TIMER0 ist ein 8-Bit Timer, d.h. kann so einstellt werden, dass er ab einem Startwert TCNT0 jedesmal um eins hochzählt, wenn er von seiner Taktquelle angesprochen wird.

Die Taktquelle für den TIMER0 kann die Taktquelle des AVR sein oder eine durch den sog. Vorteiler (Prescaler) 8, 64, 256 oder 1024 geteilte Taktfrequenz. Welcher Takt verwendet werden soll, wird im Register TCCR0 eingestellt.

Wenn der Endwert 255 (8-Bit!) überschritten wird und er wieder bei 0 beginnt, kann eine Überlaufunterbrechung (Overflow Interrupt) ausgelöst werden, wenn das vom Programm aus im Register TIMSK so eingerichtet wurde.

Damit beim ersten Einschalten des Timers nicht sofort ein Interrupt ausgelöst wird, löscht man vor dem Einschalten meistens das betreffende sog. Interruptflag (hier im Register TIFR). Das Interruptflag ist innerhalb der CPU dafür entscheidend, ob beim nächsten abzuarbeitenden Maschinenbefehl das Hauptprogramm ausgeführt wird oder ob in die ISR verzweigt wird.

Das Einschalten selbst ist ein zweistufiger Prozess: zunächst stellt man in dem Register TIMSK ein, welchen Interrupt man erlauben will. Anschliessend werden mit der Funktion sei, alle (d.h. global) erlaubten Interrupts auch von der CPU abgeprüft. Die Gegenfunktion zu sei, d.h. Interrupts global sperren wäre die Funktion cli, die in diesem Beispiel aber nicht benötigt wird.

<c>

// // Pollin Funk-AVR-Evaluationsboard v1.1 // PFA_uhr.c // v 1.0 //

/*

   Atmega8
   Externer Quarz-Oszillator: 12 MHz
   Optimierung: -Os
   RS232-Einstellungen:
   9600 Baud, 8 Bits, No Parity, 1 Stopbit
  • /
  1. include <avr/io.h>
  2. include <util/delay.h>
  3. include <stdlib.h>
  4. include <avr/interrupt.h>
  1. include "PFA_funkhw.h"
  2. include "PFA_rs232.h"
  3. include "lcd-routines.h"

/* in Millisekunden */

  1. define ENTPRELLUNG 10

volatile uint8_t stunden; volatile uint8_t minuten; volatile uint8_t sekunden;


ISR(TIMER0_OVF_vect) {

   static uint8_t ticks = 0;
   ticks += 1;
   if ( ticks == 183 )
   {
       // Letzter Zeitabschnitt kürzer, um auf 1s zu kommen
       TCNT0 = 256 - 27;
   }
   else if ( ticks == 184 )
   {
       // 1 Sekunde ist um.
       sekunden += 1;
       if ( sekunden == 60 )
       {
           sekunden = 0;
           minuten += 1;
       }
       if ( minuten == 60 )
       {
           minuten = 0;
           stunden += 1;
       }
       if ( stunden == 24 )
           stunden = 0;
       // nächste Runde
       ticks = 0;
   }

}


void uhr_init(void) {

   // Rrescaler 256
   TCCR0 = (1<<CS02) | (0<<CS01) | (0<<CS00);
   // Zählregister
   TCNT0 = 0;
   // Overflow-Flag löschen
   TIFR |= (1<<TOV0);
   // Timer0 Overflow enable
   TIMSK |= (1<<TOIE0);
   // Interrupts global einschalten
   sei();

}


void uhrzeit_lcd(void) {

   static int16_t alter_wert = -1;
   char puffer[4]; // max. 3 Ziffern (uint8_t) plus 1 Nullbyte
   if ( sekunden != alter_wert )
   {
       // Ausgabe nur bei Änderungen
       alter_wert = sekunden;
       lcd_clear();
       itoa((int) stunden+100, puffer, 10);
       lcd_string(&puffer[1]);
       lcd_string(" : ");
       itoa((int) minuten+100, puffer, 10);
       lcd_string(&puffer[1]);
       lcd_string(" : ");
       itoa((int) sekunden+100, puffer, 10);
       lcd_string(&puffer[1]);
   }

}


void ausgabe_led(uint8_t wert) {

   if (wert & (1<<0)) // Bit 0
       LED_AN(LED1);
   else
       LED_AUS(LED1);
   if (wert & (1<<1)) // Bit 1
       LED_AN(LED2);
   else
       LED_AUS(LED2);

}


int main(void) {

   uint8_t alter_tastenzustand = TASTE_AUF;
   uint8_t zaehler = 0;
   DDRB &= ~(1<<TASTER);           // Port B: Eingang für Taster
   DDRD |= (1<<LED1) | (1<<LED2);  // Port D: Ausgang für LED1 und LED2
   UART_init();
   UART_putchar(FORMFEED);
   lcd_init();
   uhr_init();
   while (1)
   {
       // Bekannte Uhrzeit auf LCD ausgeben
       uhrzeit_lcd();
       // ggf. Kommando von TASTER1 einlesen
       if ( TASTER_GEDRUECKT() )
       {
  1. if ENTPRELLUNG
           _delay_ms(ENTPRELLUNG);
           if ( TASTER_GEDRUECKT() )
  1. endif /* ENTPRELLUNG */
           {
               // Wechsel von OFFEN nach GESCHLOSSEN?
               if ( alter_tastenzustand == TASTE_AUF )
               {
                   zaehler++;
                   alter_tastenzustand = TASTE_ZU;
               }
           }
       }
       if ( !TASTER_GEDRUECKT() )
       {
  1. if ENTPRELLUNG
           _delay_ms(ENTPRELLUNG);
           if ( !TASTER_GEDRUECKT() )
  1. endif /* ENTPRELLUNG */
               alter_tastenzustand = TASTE_AUF;
       }
       // Feedback TASTER1 auf LEDs
       ausgabe_led(zaehler); // LED Ausgabe
       // Je nach TASTER1-Eingabe Uhrzeit per RS232 ändern oder anzeigen
       switch (zaehler % 4)
       {
       case 3:
           rs232_io("Stunden = ", &stunden, 0, 23);
           break;
       case 2:
           rs232_io("Minuten = ", &minuten, 0, 59);
           break;
       case 1:
           rs232_io("Sekunden = ", &sekunden, 0, 59);
           break;
       case 0:
       default:
           rs232_uhrzeit_zeigen(stunden, minuten, sekunden);
           break;
       }
   }

}

// EOF PFA_uhr.c

</c>

PFA_funkhw.h - Definitionen für das PFA ;-) Board <c>

// // Pollin Funk-AVR-Evaluationsboard v1.1 // PFA_funkhw.h // v 1.0 //

/*

   Atmega8
  • /

// LEDs sind high-active geschaltet

  1. define LED_AN(LED) (PORTD |= (1<<(LED)))
  2. define LED_AUS(LED) (PORTD &= ~(1<<(LED)))
  3. define LED_TOGGLE(LED) (PORTD ^= (1<<(LED)))
  4. define LED1 PD6
  5. define LED2 PD5

// TASTER ist high-active geschaltet

  1. define TASTER PB1
  2. define TASTER_GEDRUECKT() (PINB & (1<<TASTER))
  3. define TASTE_AUF 0
  4. define TASTE_ZU 1

// EOF PFA_funkhw.h

</c>

PFA_rs232.c - Erweiterte rs232-Funktionen <c>

// // Pollin Funk-AVR-Evaluationsboard v1.1 // PFA_rs232.c // v 1.0 //

  1. include "PFA_rs232.h"


static void wert_ausgeben(char *s, uint8_t wert) {

   char puffer[4]; // uint8_t hat max. 3 Dezimalstellen plus 1 Nullbyte = 4 Bytes
   // Einleitender Text, was im folgenden ausgegeben wird
   UART_puts(s);
   // Zahl in Ziffernfolge umwandeln. Hier zuerst in Dezimalziffern
   itoa((int) wert, puffer, 10);
   UART_puts(puffer);
   // Zeilenabschluss zur Formatierung der Anzeige
   UART_puts("\r\n");

}


void rs232_io(char *s, uint8_t *wert, uint8_t min, uint8_t max) {

   static int16_t alter_wert = -1;
   uint8_t anzahl_ziffern;
   uint8_t zeichen;
   char puffer[EINGABE_MAX];
   // Eingabe über RS232
   // Ohne Warten prüfen, ob Zeichen an UART vorhanden
   if ( UART_peekchar() )
   {
       // Zeichen ist da.
       // Aktuellen Wert ausgeben
       // Neue Seite im Terminal anfordern.
       // Bewirkt ein Löschen des Bildschirms
       UART_putchar(FORMFEED);
       wert_ausgeben(s, *wert);
       // Neuen Wert eingeben
       // Initialisierung
       UART_puts("Eingabe> ");
       anzahl_ziffern = EINGABE_START;
       // Eingabeschleife
       do
       {
           // 1. bereits Zeichen am UART Eingang abholen
           // in weiteren Schleifen auf neue Zeichen warten
           zeichen = UART_getchar();
           // Zeichen auswerten
           if ( (zeichen == '-') && (anzahl_ziffern == EINGABE_START) )
           {
               // MINUS
               UART_puts(" -1");
               UART_puts("\r\n");
               if (*wert > min)
                   *wert -= 1;
               break;
           }
           else if ( (zeichen == '+') && (anzahl_ziffern == EINGABE_START) )
           {
               // PLUS
               UART_puts(" +1");
               UART_puts("\r\n");
               if (*wert < max)
                   *wert += 1;
               break;
           }
           else if ( (zeichen == '\n') || (zeichen == '\r') )
           {
               // ENTER oder RETURN: Abschluss der Eingabe
               break;
           }
           else if ( (zeichen >= '0') && (zeichen <= '9') )
           {
               // Eingabe einer Ziffer
               UART_putchar(zeichen); // Echo
               puffer[anzahl_ziffern++] = zeichen;
               puffer[anzahl_ziffern] = 0;
           }
           else
           {
               // Sonstiges Zeichen ist für uns illegal
               anzahl_ziffern = EINGABE_ABBRUCH;
               UART_puts(" Abbruch.");
               UART_puts("\r\n");
               break;
           }
       }
       while ( anzahl_ziffern < EINGABE_MAX );
       // Eingabe prüfen
       if ( anzahl_ziffern != EINGABE_ABBRUCH )
       {
           // Ziffern in Zahl umwandeln
           int i = atoi(puffer);
           // z.B. erlaubter Wertebereich
           if ( (i >= min) && (i <= max) )
           {
               UART_puts(" Ok.");
               UART_puts("\r\n");
               *wert = (uint8_t) i;
           }
           else
               UART_puts(" Ung\201ltig.\r\n"); // \201 ist ü in Oktalschreibweise
       }
   }
   // Ausgabe auf RS232 (s.oben)
   // bei nur Änderung des Wertes gegenüber der letzten Ausgabe
   //
   // alter_wert wurde mit -1 initialisiert. Das ist ausserhalb
   // des Wertebereichs von *wert, D.h. spätere Abfrage, ob
   // (alter_wert != *wert) ist, ist beim ersten Durchlauf wie
   // gewünscht wahr,
   //
   if (alter_wert != *wert)
   {
       alter_wert = *wert;
       wert_ausgeben(s, *wert);
   }

}


void rs232_uhrzeit_zeigen(uint8_t stunden, uint8_t minuten, uint8_t sekunden) {

   static int16_t alte_sekunden = -1;
   char puffer[23];
   if ( alte_sekunden != sekunden )
   {
       // Neue Seite im Terminal anfordern.
       // Bewirkt ein Löschen des Bildschirms
       UART_putchar(FORMFEED);
       // Zahl in Ziffernfolge umwandeln. Hier zuerst in Dezimalziffern
       itoa((int) stunden+100, puffer, 10);
       UART_puts(&puffer[1]);
       UART_puts(" : ");
       // Zahl in Ziffernfolge umwandeln. Hier zuerst in Dezimalziffern
       itoa((int) minuten+100, puffer, 10);
       UART_puts(&puffer[1]);
       UART_puts(" : ");
       // Zahl in Ziffernfolge umwandeln. Hier zuerst in Dezimalziffern
       itoa((int) sekunden+100, puffer, 10);
       UART_puts(&puffer[1]);
       // Zeilenabschluss zur Formatierung der Anzeige
       UART_puts("\r\n");
       alte_sekunden = sekunden;
   }

}


</c>

PFA_rs232.h - Prototypen zu den rs232-Funktionen <c>

// // Pollin Funk-AVR-Evaluationsboard // PFA_uart.h // v 1.0 //

  1. include <avr/io.h>
  2. include <stdlib.h>
  3. include "PFA_uart.h"
  1. ifndef FORMFEED
  2. define FORMFEED '\014'
  3. endif
  1. define EINGABE_START 0
  2. define EINGABE_ABBRUCH EINGABE_START
  3. define EINGABE_MAX 3

void rs232_io(char * s, uint8_t *wert, uint8_t min, uint8_t max); void rs232_uhrzeit_zeigen(uint8_t stunden, uint8_t minuten, uint8_t sekunden);

// EOF PFA_uart.h

</c>

PFA_uart.c - UART Grundfunktionen (aus ehemaligem rs232.c herausgezogen) <c>

// // Pollin Funk-AVR-Evaluationsboard v1.1 // PFA_uart.c // v 1.0 //

  1. include "PFA_uart.h"

/*

   Atmega8
   Externer Quarz-Oszillator: 12 MHz
   Optimierung: -Os
   RS232-Einstellungen:
   9600 Baud, 8 Bits, No Parity, 1 Stopbit
  • /

void UART_init(void) {

   // Bausrate setzen
   // 9600 Baud bei F_CPU 12 MHz
   // (Baudratenfehler = +0,2%)
   UBRRH = (uint8_t) ((F_CPU / (16 * 9600L) - 1) >> 8);
   UBRRL = (uint8_t)  (F_CPU / (16 * 9600L) - 1);
   //      Empfangen,   Senden erlauben
   UCSRB = (1<<RXEN) | (1<<TXEN);
   // Frame Format:              8 Bits,                  No Parity,          1 Stopbit
   UCSRC = (1<<URSEL) | ((1<<UCSZ1) | (1<<UCSZ0)) | ((0<<UPM1) | (0<<UPM0)) | (0<<USBS);

}


// Auf Zeichen im UART Eingang prüfen uint8_t UART_peekchar(void) {

   // sofort zurückkehren und Zustand melden
   return UCSRA & (1<<RXC);

}


// Auf Zeichen im UART Eingang warten uint8_t UART_getchar(void) {

   // Warte bis UART Eingang voll
   while ( !(UCSRA & (1<<RXC)) )
       ;
   return UDR;

}


// Zeichen in UART Ausgang geben void UART_putchar(char z) {

   // Warte bis UART Ausgang frei
   while ( !(UCSRA & (1<<UDRE)) )
       ;
   UDR = z;

}


// Zeichenkette (String) in UART Ausgang geben void UART_puts(char *s) {

   while ( *s )
   {
       UART_putchar(*s);
       s++;
   }

}

// EOF PFA_uart.c

</c>

PFA_uart.h - Prototypen zu den UART Grundfunktionen <c>

// // Pollin Funk-AVR-Evaluationsboard // PFA_uart.h //

  1. include <avr/io.h>

void UART_init(void);

// Auf Zeichen im UART Eingang prüfen uint8_t UART_peekchar(void);

// Auf Zeichen im UART Eingang warten uint8_t UART_getchar(void);

// Zeichen in UART Ausgang geben void UART_putchar(char z);

// Zeichenkette (String) in UART Ausgang geben void UART_puts(char *s);

// EOF PFA_uart.h

</c>

(Wird fortgesetzt)

Forenbeiträge

Weblinks