Sie sind auf Seite 1von 50

Mikrocontroller Programmierung

Atmel Evaluationsboard 2.0 Mikrocontroller: Atmel AVR ATmega16 Programmiersprache: C

0. Bitmanipulation 1. Ein- und Ausgnge des Controllers 2. Der UART 2.1 Die drei Bestandteile des UART 2.2 Die UART Register 2.3 Beispielprogramm: Kommunikation ber UART 3. Programmieren mit Interrupts 4. Timer/Counter 4.1 Der Vorteiler (Prescaler) 4.2 Betriebsmodi 4.3 Registerberblick beim T/C0 4.4 Beispiel Timer0 4.5 Registerberblick beim T/C1 4.6 Pulsweitenmodulator (PWM) 4.7 Beispiele PWM

S. 3 S. 5 S. 7 S. 7 S. 7 S. 10 S.12 S.14 S.14 S.15 S.16 S.17 S.18 S.20 S.22

5. LCD-Ansteuerung 5.1 Text Displays 5.2 Anschlubelegung fr 80 Zeichen Displays

S.26 S.26 S.26 1

5.3 Anschluss des LCD an Mikrocontroller 5.4 Befehlsbersicht 5.5 Beispiel fr eine einfache Ausgabe auf das LCD

S.27 S.28 S.29

6. Analog to Digital Converter 6.1 Elektrische Verkabelung 6.2 Betriebsmodi 6.3 ADC Register 6.4 Beispielprogramm: ADC mit Wertausgabe ber UART und LCD 7. Daten ins EEPROM speichern / auslesen 7.1 Einzelne Bytes lesen/schreiben 7.2 EEPROM auslesen 7.3 Daten ins EEPROM speichern

S.34 S.34 S.36 S.36 S.40 S.44 S.44 S.46 S.48

0. Bitmanipulation

Bitoperatoren

>> << | & ~ ^ usw.

Rechts schieben Links schieben (Bsp: a<<b ist das gleiche wie a * 2^b) ODER / OR Disjunktion UND / AND Konjunktion NICHT / NOT Negation XOR / Exklusiv-ODER Antivalenz

Bitmanipulation wird u.a. zum Setzen und Lschen von Bits eines Registers bentigt. Wahrheitstabellen OR AND NOT XOR

Beispiele: Register "Byte" mit 8 Bits 1. Linksverschiebung (1<<5): 0000 0001 dieses Bit wird 5 Stellen nach links verschoben 0010 0000 2. Kurzschreibweisen Byte = Byte | (1<<0) Byte = Byte & (1<<0) Byte = Byte & ~( (1<<1) | (1<<0) ) Byte = Byte ^ ( (1<<0) ) usw.

//Kurzschreibweise: Byte |= (1<<0) //Kurzschreibweise: Byte &= (1<<0) //Kurzschreibweise: Byte &= ~( (1<<1)|(1<<0) ) //Kurzschreibweise: Byte ^= (1<<0)

Nachfolgend werden die Kurzschreibweisen verwendet!

3. ODER Byte: 0010 0000 (1<<5): 0001 0000 Byte |= (1<<5) 3

Rechnung: 0010 0000 //Byte | 0001 0000 //(1<<5) = 0011 0000 //Ergebnis Bit5 wird gesetzt

4. UND Byte: (1<<4):

1101 1010 0001 0000

Byte &= (1<<4) Rechnung: 1101 1010 //Byte & 0000 1000 //(1<<4 = 0000 1000 //Ergebnis z.B. fr das Prfen einer Aussage (wahr/falsch), ob ein bestimmtes Bit gesetzt ist

5. UND, NICHT Byte: 1101 1010 ~(1<<4): 1110 1111 ~(1<<6): 1011 1111 Byte &= ~( (1<<4) | (1<<6) ) Rechnung: 0001 0000 //(1<<4) | 0100 0000 //(1<<6) = 0101 0000 ~(0101 0000) = 1010 1111

//= ~( (1<<4) | (1<<6 )

1101 1010 //Byte & 1010 1111 //~( (1<<4) | (1<<6 ) = 1000 1010 //Ergebnis Bit4 und Bit6 werden gelscht

6. XOR Byte: (1<<0):

0111 1010 0000 0001

Byte ^= (1<<0)

Rechnung: 0111 1010 ^ 0000 0001 = 0111 1011

//Byte //(1<<0) //Ergebnis

0111 1011 ^ 0000 0001 = 0111 1010 Bit0 kann z.B. abwechselnd gesetzt und gelscht werden

1. Ein- und Ausgnge des Controllers


Die Anschlsse eines AVR-Controllers ("Beinchen") werden zu Blcken zusammengefasst, einen solchen Block bezeichnet man als Port. Beim ATmega16 hat jeder Port 8 Anschlsse, bei kleineren AVRs knnen einem Port auch weniger als 8 Anschlsse zugeordnet sein. Diese knnen als Ein- oder Ausgang geschaltet werden. Eingang Pin auf 0 (nicht gesetzt) Ausgang Pin auf 1 (gesetzt) Beispiel: Initialisierungsfunktion - Pins PD5 und PD6 an PORTD als Ausgang schalten

//---------------------------------------------------------------------------// Init_IO // // -> -// <- -//---------------------------------------------------------------------------void Init_IO(void) { // Ports DDRA = 0x00; PORTA = 0x00; DDRB = 0x00; PORTB = 0x00; DDRC = 0x00; PORTC = 0x00; DDRD = 0x60; PORTD = 0x20; //Ein-, Augnge definieren //Bits 7 6 5 4 3 2 1 0 //Binr 0 1 1 0 0 0 0 0 //Hexadezimal 6 0 // => Ausgnge definieren mit DDRD = 0x60 // => alle anderen Eingang //Ausgnge sind deaktiviert //Bits 7 6 5 4 //Binr 0 0 0 0 //Hexadezimal 0 }

3 2 1 0 0 0 0 0 0

Die Ausgnge lassen sich ber PORTx aktivieren, zb. PORTD = 0x20, dann ist Ausgang PD5 aktiviert und liegt somit an der Eingangsspannung VCC (hier: 5V). Bei Pins, die mittels DDRx auf Eingang geschaltet wurden, knnen ber PORTx die internen Pull-Up Widerstnde aktiviert oder deaktiviert werden (1 = aktiv).

Beispiel: Hauptprogramm (p:\Mitarbeiter\......\HalloWelt1)


//---------------------------------------------------------------------// // Hauptprogramm // //---------------------------------------------------------------------int main(void) { int i, j; // I/O initialisieren Init_IO();

// Hauptschleife while(1) { for(j=0; j<10; j++) { for(i=0; i<32000; i++) nop(); } //dient lediglich als Warteschleife PORTD |= (1<<5); PORTD |= (1<<6); //LEDs an Ausgngen PD5 und PD6 werden an VCC gelegt for(j=0; j<10; j++) { for(i=0; i<32000; i++) nop(); } PORTD &= ~(1<<5); PORTD &= ~(1<<6); //LEDs an Ausgngen PD5 und PD6 werden von VCC genommen } return(1); }

Die Hauptschleife while(1){...} ist eine Programmschleife, welche kontinuierlich wiederkehrende Befehle enthlt. In diesem Beispiel werden die LEDs ein und wieder ausgeschaltet. Der Controller durchluft die Schleife immer wieder. Eine solche Schleife ist notwendig, da es auf dem Controller kein Betriebssystem gibt, das nach Beendigung des Programmes die Kontrolle bernehmen knnte. Ohne diese Schleife wre der Zustand des Controllers nach dem Programmende undefiniert. Das rteurn(1) dient lediglich der Vollstndigkeit des Programms, wird jedoch nie erreicht! 6

2. Der UART
(UART = Universal Asynchrone Receiver / Transmitter) ber den UART wird ein Datenstrom zwischen PC und Controller aufgebaut Enthlt Startbit, 5-9 Datenbits, Paritybit, 1-2 Stopbits 2 Pins am Controller bentigt: - TXD (Transmit Data) - RCD (Receive Data)

2.1 Die drei Bestandteile des UART Der Taktgenerator besteht aus dem Baudratengenerator und einer Synchronisationslogik fr externen Taktinput, gebraucht beim Einsatz des synchronen Slave Modus. Der Transmitter besteht aus einem einzelnen Schreibpuffer, einem seriellen Schieberegister, dem Parittsgenerator und einer Kontrollogik, um verschiedene Frame-Formate zu behandeln. Der Receiver enthlt sogenannte Clock und Data Recovery Units, Parittschecker, Schieberegister, eine Kontrollogik und zwei Empfangspuffer. 2.2 UART Register UBRR: UART Baud Rate Register Der UART besitzt einen eigenen Baudratengenerator. In diesem Register mssen wir dem UART mitteilen, wie schnell wir kommunizieren mchten. Der Wert, der in dieses Register geschrieben werden muss, errechnet sich nach folgender Formel:
UBRR = Taktfreque nz 1 Baudrate 16 UBRR = Taktfreque nz 1 Baudrate 8

U2X = 1

U2X = 0

Wir verwenden einen Quarz mit 16MHz als Taktfrequenz, U2X ist standardmig 1.

Einstellungen des Registers mit den bei printed systems gngigen Baudraten

UDR: UART Data Register. Hier werden Daten zwischen UART und CPU bertragen. Da der UART im Vollduplexbetrieb gleichzeitig empfangen und senden kann, handelt es sich hier physikalisch um 2 Register, die aber ber die gleiche I/O-Adresse angesprochen werden. Je nachdem, ob ein Lese- oder ein Schreibzugriff auf den UART erfolgt wird automatisch das richtige UDR angesprochen.

UCSRA: UART Control and Status Register A Das UCSRA ist ein Statusregister des USART. Hier kann man erfahren, welche Operationen zur Laufzeit durchgefhrt werden. Das UCSRA Register visuell mit den dazugehrigen Informationen sieht folgendermaen aus:

RXC - UART Receive Complete Das RXC Bit wird gesetzt, wenn ein empfangenes Zeichen vom Empfangsschieberegister in das UDR bertragen wurde. Wenn das UDR ausgelesen wird, wird das Bit automatisch gelscht. Wenn das Zeichen nicht gelesen wird, bevor ein weiteres komplett empfangen wurde, dann tritt ein berlauf ein. TXC - USART Transmit Complete Das TXC Bit wird gesetzt, wenn ein Zeichen im Sendeschieberegister vollstndig gesendet wurde und es auch kein weiteres Zeichen im UDR mehr steht. Die Kommunikation ist demnach zu Ende. Wichtig ist das TXC Bit vor allem bei Halbduplex-Verbindungen, wenn nach dem Senden auf Empfang von Daten umgeschaltet werden soll. Das TXC Bit wird nicht automatisch gelscht, auer dass der entsprechende Interrupthandler gestartet wird. UDRE - USART Data Register Empty Das UDRE Bit wird gesetzt, sofern ein Zeichen vom UDR in das Sendeschieberegister geschrieben wurde und wieder ein neues Zeichen aufgenommen werden kann. Wenn ein Zeichen in das Sendedatenregister geschrieben wurde, dann wird das UDRE Bit automatisch gelscht. U2X - Double the USART Transmission Speed Nur im asynchronen Modus gltig, im synchronen Modus sollte es auf 0 gesetzt sein.Wenn das U2X Bit auf 1 gesetzt ist, wird der Divisor der Baudrate von 16 auf 8 gesetzt, damit wird die Transferrate verdoppelt.

UCSRB: USART Control and Status Register B Hier kann eingestellt werden, wie und mit welchen Funktionen der USART verwendet werden soll.

RXEN - Receiver Enable Wenn das RXEN Bit gesetzt ist, dann arbeitet der Empfnger des USART, d.h. vor jedem Empfang sollte dieses Bit gesetzt sein. Der entsprechende Pin des AVR kann in dem Fall als normaler I/O-Pin verwendet werden, wenn das RXEN Bit nicht gesetzt ist. TXEN - Transmitter Enable Wenn das TXEN Bit gesetzt ist, dann arbeitet der Sender des USART, d.h. vor jedem Senden sollte dieses Bit gesetzt sein. Der entsprechende Pin des AVR kann in dem Fall als normaler I/O-Pin verwendet werden, wenn das TXEN Bit nicht gesetzt ist.

UCSRC: USART Control and Status Register C Hier werden weitere Einstellungen durchgefhrt (z.B. die Modi Synchron/Asynchron).

UCSZ1 und UCSZ0 - Charakter Size Mit den Bits UCSZ1 und UCSZ0 und dem UCSZ2 Bit aus dem UCSRB, lsst sich die Anzahl der Datenbits in einem Frame einstellen, die der Empfnger und der Sender nutzen.

2.3 Beispielprogramm: Kommunikation ber UART (p:\Mitarbeiter\......\Beispielprogramme\UART) UART initialisieren Vor jeder Kommunikation muss der USART initialisiert werden. Baudrate setzen Frame Format festlegen (z.B. 8 Bit fr character) Empfnger und/oder Sender aktivieren Die Baudrate, einer der 4 Operationsmodi und das Frame Format mssen gesetzt sein, bevor jegliche bertragung erfolgen kann. Das folgende Beispiel zeigt einen C Code, in dem der Asynchronous Mode eingestellt wird, ein festes Frameformat gewhlt, und die Baudrate als Parameter bergeben wird.

//------------------------------------------------------------------------//INIT_UART // // --> uiBR: Wert fr UART Baud Rate Register (UBRR) // (beachte: U2X hier gesetzt => UBRR korekt whlen) // // ui steht immer fr unsigned int // (immer den Typ der Variable mit im Variablennamen mit // angeben!) // // BR fr Baudrate // // <-- -//-------------------------------------------------------------------------

void Init_UART(unsigned int uiBR) { UBRRH = (unsigned char)(uiBR>>8); UBRRL = (unsigned char)uiBR; //Set baud rate UCSRB = (1<<RXEN)|(1<<TXEN); //Enable receiver and transmitter UCSRC = (1<<URSEL)|(1<<USBS)|(3<<UCSZ0); //Set frame format: 8data, 2stop bit //2 Stop Bits sind sicherer als 1 UCSRA = (1<<U2X); }

Hauptprogramm Funktion:
ber die TX-Leitung des PCs werden mit dem Programm TJTerm Eingaben von der

Tastatur an die RX-Leitung des Controllers gesendet. 10

Die Daten werden im UDR (UART Data Register) des Controllers gespeichert. Das Register wird ausgelesen und die Eingabe verarbeitet (z.B. LED ein/aus) ber die TX-Leitung werden Daten an die RX-Leitung des PCs gesendet und im TJTerm

ausgegeben. Die Funktionen Init_IO() und UART:INIT() sind von oben bernommen.
int main(void) { char c; Init_IO(); /* Initialisierung der Ports */ unsigned int uiBR = 16; Init_UART(uiBR); /* Initialisierung UART UBRR = 16 ==> Baudrate = 115,2k U2X = 1 ==> Asynchronous Mode Double Speed Character Size: 8 Bit */ while(1) { if(UCSRA & (1<<RXC)) //Prfen ob Zeichen empfangen wurde //noch nicht gelesene Zeichen befinden sich im UDR //RXC ist dann gesetzt { c = UDR; switch(c) { case '1': PORTD |= (1<<PD5); //LED ein break; case '!': PORTD &= ~(1<<PD5); //LED aus break; case '2': PORTD |= (1<<PD5); break; case '"': PORTD &= ~(1<<PD5); break; case '3': PORTD |= (1<<PD5); break; case '': PORTD &= ~(1<<PD5); break; default: while(!(UCSRA & (1<<UDRE))); //ggf. warten bis Puffer (letzte bertragung) wieder frei //ist. Dies ist der Fall, wenn das Bit UDRE (UART Data //Register Empty) gesetzt ist. UDR = c; while(!(UCSRA & (1<<UDRE))); //Warten, bis Byte versendet ist break; } } } return(0); }

11

3. Programmieren mit Interrupts


Programmiert man mit Interrupts, so wird die Datei <avr/interrupt.h> eingebunden. Interrupts werden durch den Befehl sei() global aktiviert, mit cli() global deaktiviert. Da wir nur einen CPU haben, luft Hauptprogramm und Interrupt nicht parallel ab, sondern der Interrupt unterbricht immer wieder das Hauptprogramm, luft also nebenher. Viele Prozesse sind zeitabhngig. Bei einer Uhr z.B. wird jede Sekunde ein LCD-Display aktualisiert, dass die neue Uhrzeit anzeigt. Dabei wird die Hauptschleife immer wieder durchlaufen, bis bei einem Durchlauf wieder eine Sekunde hochgezhlt wurde. Es wird dann im Interrupt ein Flag gesetzt (z.B. Display=1). Im Hauptprogramm stt man auf dieses Flag, aktualisiert das Display, und setzt das Flag anschlieend wieder auf 0 (Display=0). Ein Timer-Interrupt sieht quelltextmig folgendermaen aus:
//-----------------------------------------------------------------------// INTERRUPTROUTINE // // veraltet: SIGNAL(siglabel) // hier: SIGNAL(SIG_OUTPUT_COMPARE0) // // neu: ISR(Vectorname) // hier: ISR(TIMER0_COMP_vect) // //-----------------------------------------------------------------------ISR(TIMER0_COMP_vect) { // Variablen hoch-/runterzhlen, Flags setzen usw. // jedoch KEINE greren Schleifen // KEIN ffnen und schlieen von Ports o..!!! }

Interrupts werden wie eine Funktion auerhalb des main-Programms definiert. Dieses Beispiel zeigt einen Output-Compare0-Interrupt. Die 0 steht fr Timer0 (8-Bit-Timer) und Output-Compare-Match bedeutet, dass ein Timer Register von 0 bis zu einem festgelegten Wert im Systemtakt hochgezhlt wird und dann den Interrupt auslst.

12

In nachfolgender Tabelle sehen wir weitere Mglichkeiten, wie man ein Interrupt noch auslsen kann (Datenblatt S.45):

Wie man mit Interrupts arbeitet, wird in den nachfolgenden Kapiteln in den Programmbeispielen dargestellt.

13

4. Timer/Counter
Die heutigen Mikrocontroller und insbesondere die RISC-AVRs sind fr viele Steuerungsaufgaben zu schnell. Wenn wir beispielsweise eine LED oder Lampe blinken lassen wollen, knnen wir selbstverstndlich nicht die CPU-Frequenz verwenden, da ja dann nichts mehr vom Blinken zu bemerken wre. Der hier verwendete ATmega 16 Controller verfgt ber zwei 8 Bit Timer/Counter (T/C0, T/C2) und ber einen 16 Bit Timer/Counter (T/C1). Funktionsprinzip: Mit jedem Takt wird ein eigenes Spezialregister TCNTx hoch gezhlt. Falls das Zhlregister berluft (vom hchsten Zhlwert zurck auf Null springt) oder ein eingestellter Wert erreicht wird, kann der Timer einen Interrupt auslsen. Als Taktquelle sind auch externe Signale verwendbar. Anwendungsmglichkeiten finden sich beim Zhlen von Ereignissen, beim zeitabhngigen oder periodischen Ausfhren von Programmteilen oder Messen von Zeitabstnden. T/C0 (p:\Mitarbeiter\......\Beispielprogramme\Timer - 8 Bit) Der Timer0 ist ein 8 Bit Timer. Er besitzt neben einem 10 Bit Vorteiler (Prescaler) einen Frequenzgenerator und Pulsweitenmodulator (PWM). Zudem untersttzt er das Zhlen von Taktflanken. Der CTC-Modus (Clear Timer on Compare Match) lscht bei Bedarf das Timerregister wenn es mit dem Vergleichsregister bereinstimmt. Er ist ein 8 Bit Timer ohne spezielle Funktionen und somit fr simplere Anwendungen gedacht. T/C1 (p:\Mitarbeiter\......\Beispielprogramme\Timer - PWM) (p:\Mitarbeiter\......\Beispielprogramme\Timer - PWM + 8 Bit) Der Timer1 ist ein 16 Bit Timer mit den gleichen Funktionen wie der Timer0. Darber hinaus verfgt er ber ein zustzliches unabhngiges Vergleichsregister. Des Weiteren beherrscht der Timer1 PWM mit variabler Periode und somit Pulsfrequenzmodulation. Er besitzt eine Input Capture Unit um externe Ereignisse zu zhlen oder um auf sie zu reagieren. T/C2 Der Timer2 ist ein 8 Bit Timer, der alle Funktionen des Timer0 besitzt, auer dem Zhlen von Taktflanken. Dafr verfgt er ber die Mglichkeit einen 32Khz Uhrenquarz als Taktquelle fr den Timer zu nutzen. Damit wird ein asynchroner Timer mglich, der unabhngig vom Takt ist. Dieser ist mindestens 4 mal langsamer und ermglicht so auch das Messen weitaus lngerer Zeitabstnde.

4.1 Der Vorteiler (Prescaler) Der Prescaler (eng. = Vorteiler) kann dazu genutzt werden, den Takt, der den Timern zugefhrt wird, zu verkleinern. U.a. kann man damit die Timer so konfigurieren, damit diese in den unterschiedlichsten Frequenzen takten. ber den Prescaler kann auch der Timer angehalten werden. Beispiel: Wenn wir mit einem CPU-Takt von 16 MHz arbeiten und den Vorteiler auf 256 einstellen, wird also der Timer mit einer Frequenz von 16 MHz / 256, also mit 62500 kHz betrieben. 14

Wenn also der Timer luft, so wird das Daten- bzw. Zhlregister (TCNTx) mit dieser Frequenz inkrementiert.

4.2 Betriebsmodi Normaler Modus: Der einfachste Betriebsmodus ist der normale Modus. Die Zhlrichtung des Timers ist immer aufsteigend, und irgendwann kommt es zu dem Interrupt Timer-Overflow. Im einfachsten Fall kann man diesen Modus in folgendem Diagramm darstellen:

CTC Modus (Clear Timer on Compare Match mode): Der CTC Modus ist eine Erweiterung des "Output-Compare"-Funktion. Der CTC Modus eignet sich besonders um einen mit konstanter Frequenz wiederkehrenden Interrupt zu erzeugen. Wie im normalen Modus zhlt der Timer hoch. Wenn der Wert im OCRx Register erreicht wird, wird zustzlich zum mglichen Interrupt der Zhler wieder auf 0 gesetzt. Man kann also die maximale Zhlergrenze selber definieren. Dieses Diagramm veranschaulicht den CTC Modus.

15

4.3 Registerberblick T/C0

T/C0 Control Register (TCCR0) ber die ersten 3 Bits dieses Registers wird der Prescaler eingestellt. Wie die Bits zu setzen sind, sieht man in folgender Tabelle:

TCNT0: T/C0 Zhlregister TCNT0 stellt den eigentlichen Zhler dar. Da es sich um ein 8 Bit Register handelt ist der niedrigste Zhlwert 0 und der hchste 255. berlauf (Overflow) bedeutet, dass der Zhler von 255 auf 0 zurckgesetzt wird. Hier kann dann z.B. ein Interrupt ausgelst werden. Fr das Zhlen bentigt der Counter eine gewisse Zeit. Bei 16MHz vergehen dabei bis zu einem Overflow 16s. Mit der Prescaler Einstellung TCCR0 |= (1<<CS02) ist die Zhlfrequenz 16MHz/256 = 62600kHz. Bis zum Overflow vergehen dann 4,096ms. Um andere Zeiten einzustellen lsst sich das Zhlregister auch nach jedem Overlow durch einen Interrupt vorladen. Ein Interrupt lsst sich auch dann auslsen, wenn der Zhlwert mit dem Wert im Vergleichsregister bereinstimmt (OCR0). OCR0: Output Compare Register Hier steht ein 8 Bit Wert drin, der bei bereinstimmung mit dem TCNT0 Wert einen Interrupt auslsen kann. Es gibt also u.a. Overflow Interrupts und Compare Interrupts

16

4.4 Beispiel Timer0 (Initialisierung, Interrupt) (p:\Mitarbeiter\......\Beispielprogramme\Timer - 8 Bit) Timer soll immer nach 10ms einen Interrupt auslsen. Um eine mglichst genaue Zeit zu erreichen, nehmen wir die grte mgliche Frequenz, also den kleinsten Vorteiler mit dem man auf 10ms kommen kann. Die Berechnung des Preloader bei Verwendung der Overflow-Interrupts. Die gesuchte Frequenz ist 100Hz. Die Quarzfrequenz betrgt 16MHz. 1. T[Quarz] = 1 / 16MHz = 62,5 ns 2. 62,5 ns * 256 = 16 s max. erreichbare Zeit ohne Prescaler ist gerade mal 16 s 10 ms sind nur mit einem Prescaler 1024 zu erreichen 3. 62,5 ns * 1024 = 64 s 4. Nun mssen wir den Vorladewert berechnen. 10 ms / 64 s = 156,25 Wir mssen also bei 255-156,25 = 98,75 beginnen zu zhlen. Also muss das Register TCNT0 mit 99 vorgeladen werden.
//------------------------------------------------------------------------// Initialisierung Timer0 // // --> -// // <-- -// //------------------------------------------------------------------------void init_timer_8bit(void) { TCCR0 |= (1<<CS02) | (0<<CS01) | (CS00); // Zhltakt 16MHz / 1024 TCNT0 = 99; // Zhlregister auf 99 vorgeladen // zhlt jetzt in jedem Durchgang von 99-255 // dies dauert T = 10ms TIMSK |= (1<<TOIE0); // Freigabe der T/C0 Interrupts sei(); // // // // } Interrupts aktivieren. Da immer T = 10ms sein soll, muss hier z.B. TCNT0 immer mit 99 vorgeladen werden ==> immer nach 10ms ein Overvlow Interrupt

Der Interrupt soll jede Sekunde die LEDs an PD5 und PD6 ein- bzw. ausschalten. Auerdem muss immer wieder das Zhlregister TCNT0 vorgeladen werden. Im folgenden sehen wir ein Beispiel, was in so einem Interrupt stehen knnte.
// Interrupt SIGNAL(SIG_OVERFLOW0) { g_uliWarten++;

17

if (g_uliWarten == 100) // Erst beim hundertsten Interrupt wird diese // Bedingung wahr sein. // Es vergehen 100*10ms = 1s { PORTD ^= (1<<PD5); PORTD ^= (1<<PD6); g_uliWarten = 0; // Die LEDs werden abwechselnd ein- und ausgeschaltet. } TCNT0 = 99; // Timer auf 99 vorladen }

Im main()-Programm passiert nicht mehr viel - es wird nur der Timer initialisiert. Er luft nach der Initialisierung permanent. Die Hauptschleife ist hier leer, muss aber da sein, damit der Controller weiterluft.
//------------------------------------------------------------------------// // Hauptprogramm // //------------------------------------------------------------------------int main(void) { DDRD = 0x60; // Pins mit LED als Ausgang gesetzt 0110 0000, // alle anderen Eingang. PORTD = 0x40; // Pull-Up-Widerstnde nicht aktiviert 0100 0000, // LED6 am Anfang an, LED5 am Anfang aus. init_timer_8bit();

while(1) {} return(0); }

4.5 Registerberblick T/C1

T/C1 Control Register A (TCCR1A)

Die Bits COM1A1 und COM1A0 fr Komparator A legen die Reaktion des dem Komparator A zugeordeten Ausgangspins OC1A (PD5) auf eine bereinstimmung der Compare Register (OCRx) und Zhlerstand (TCNTx) fest. Das selbe gilt entsprechen fr die Pins COM1B1 und COM1B0 (Ausgangspin OC1B). 18

Dieser Pin muss bei Verwendung als Compare-Funktionsausgang durch das Einschreiben einer log. 1 in das zugeordnete Datenrichtungsregister als Ausgang definiert werden (siehe S. 3).

Im Falle eines aktiven PWM Modes haben die Bits 4-7 in TCCR1A eine andere Bedeutung. Nheres dazu im Abschnitt Pulsweitenmodulation. PWM ist nicht aktiv, wenn die Bits WGM11 und WGM10 beide auf 0 sind!

TCCR1B : T/C1 Control Register A

Die Bits CS12, CS11, CS10 legen wie oben beschrieben den Prescaler fest. Ist das WGM12 Bit (frher: CTC1) gesetzt, wird T/C1 mit dem nchsten einer bereinstimmung von Zhlerstand und Vergleichsregister folgenden Systemtaktimpuls auf 0x0000 rckgesetzt. Im PWM Modus hat das Bit eine andere Bedeutung.

TCNT1: T/C1 Zhlregister

19

Das 16 Bit Zhlregister ist in zwei Bytes unterteilt, in TCNT1L und TCNT1H (Low Byte und High Byte). Das Low-Byte muss immer zuerst beschrieben und gelesen werden! Dies gilt fr alle 16-Bit Register. Mit einem C-Kompilierer lsst sich TCNT1 aber auch ganz normal als 16 Bit Register betrachten. Wenn man TCNT1 einen Wert zuweist, wenn der Timer schon luft, besteht die Gefahr, dass ein Compare-Match mit einem der beiden OCR1-Vergleichsregister verpasst wird!

Output Compare Register 1 A (OCR1A), Output Compare Register 1 B (OCR1B)

Das OCR1 enthlt einen 16 Bit Wert der stndig mit dem Zhlstand im TCNT1 verglichen wird. Bei einem gleichen Wert kann ein Output Compare Interrupt ausgelst werden. Hier gilt auch, das High-Byte zuerst zu schreiben. In C kann man es aber auch als 16 Bit Register behandeln.

4.6 Pulsweitenmodulator (PWM) Fr PWM Modus folgende Einstellungen in TCCR1A/TCCR1B:

20

Im PWM Modus arbeitet T/C1 als Auf-/Abwrtszhler, der zyklisch von 0x0000 bis 0xFFFF und wieder bis 0x0000 zhlt.

Beim nicht invertierenden PWM entspricht das Tastverhltnis dem Wert g = n / (2N 1) g: Tastverhltnis n: Wert im Output Compare Register N: PWM Auflsung fPWM = fT/C1 / (2N+1 - 2)

Die Frequenz fPWM errechnet sich zu:

21

Stimmt der Zhlstand in TCNT1 mit dem Wert im OCR1A bzw. OCR1B berein, so werden die Ausgangspins OC1A bzw. OC1B gesetzt oder rckgesetzt (=LEDs leuchten/gehen aus). Im Register TCCR1A lsst sich die Reaktion der Ausgangspins einstellen. Mit den Pins COM1A1, COM1A0 bzw. COM1B1 und COM1B0 lsst sich einstellen, ob es sich um einen invertierenden oder nicht invertierenden PWM handelt. Die Zusammenhnge sind im folgenden Diagramm kurz dargestellt:

4.7 Beispiele PWM Beispiel 1: Initialisierung 16 Bit Timer im Pulsweitenmodulations - Modus (PWM) (p:\Mitarbeiter\......\Beispielprogramme\Timer - PWM) Wir wollen den PWM so einstellen, dass er mit einer sehr hohen Frequenz luft. Deshalb wurde hier ein 8 Bit PWM gewhlt. Wie man in der Tabelle oben sieht, wird im 8 Bit Betrieb die grte PWM Frequenz erreicht. Deshalb betreiben wir den PWM auch ohne Vorteiler.
//------------------------------------------------------------------------//Initialisierung PWM // // --> -// // <-- -// //------------------------------------------------------------------------void init_PWM(void) { TCCR1A |= (1<<COM1A1) | (1<<WGM10); //nichtinvertierende PWM mit 8 Bit Auflsung TCCR1B |= (1<<CS10); //ohne Vorteiler ==> zhlt mit Systemtakt //PWM Frequenz = 16MHz / 510 = 31KHz TCNT1 = 0; //Zhlregister auf 0 gesetzt OCR1A = g_ulix; //Tastgrad g: g = g_ulix / (2^N - 1) //mit g_ulix: Wert in OCR1A, N: PWM Auflsung }

22

Die Zahl ulix steht fr "global variable, unsigned long integer x". Sie steht im Vergleichsregister OCR1A. Indem wir den Wert in diesem Register z.B. in einem Interrupt ndern, knnen wir das Tastverhltnis beeinflussen. Bei einem Tastverhltnis von "1" haben erhalten wir in unserem Fall bei 5V Ausgangsspannung einen Ausgangsspannungsmittelwert von 5V. Bei einem Tastverhltnis von "0,5" einen MW von 2,5V usw.... So knnen wir durch ndern von g_ulix z.B. die LEDs auf unserem Board dimmen. Durch die hohe Frequenz wird Flackern des Lmpchens vermieden.

Beispiel 2: LED Spannung auf und ab steuern per Tastendruck (p:\Mitarbeiter\......\Beispielprogramme\Timer - PWM)

Ziel ist es, durch verndern des Tastverhltnisses den Ausgangsspannungsmittelwert zu hoch und runter zu setzen, sodass wir die Helligkeit der LED variieren knnen.
//------------------------------------------------------------------------// //Hauptprogramm // //------------------------------------------------------------------------int main(void) { DDRD = 0x60; //Pins mit LED als Ausgang gesetzt 0110 0000, //alle anderen Eingang. PORTD =0x00; //Pull-Up-Widerstnde nicht aktiviert 0000 0000, //LEDs am Anfang aus char c; unsigned int uiUBRR = 16; Init_UART(uiUBRR); //Initialisierungsfunktion UART //UBRR = 16 ==> Baudrate = 115,2k //U2X gesetzt ==> Asynchronous Mode Double Speed

/* Pulsweitenmodulator */ init_PWM();

while(1) { if(UCSRA & (1<<RXC)) { c = UDR; switch(c) { case '+': if(g_ulix>245) g_ulix = 255; else g_ulix += 10; OCR1A = g_ulix; break;

23

case '-': if(g_ulix<10) g_ulix = 0; else g_ulix -= 10; OCR1A = g_ulix; break; default: break; } } } return(0); }

Der Timer luft mit der Initialisierungsfunktion init_PWM() los und luft dann immer weiter. Whrend der Timer immer bis aufwrts bis 255 zhlt und wieder abwrts bis 0, wird der Wert OCR1A durch drcken der Tasten "+" und "-" verstellt. Dadurch wird das Tastverhltnis gendert und man kann die LED heller und dunkler machen.

Beispiel 3: LED Spannung auf und ab steuern ber 8 Bit Timer (p:\Mitarbeiter\......\Beispielprogramme\Timer - PWM + 8 Bit)

In diesem Beispiel soll die Spannungssteuerung automatisch erfolgen. Sie soll in kleinen Schritten bis 5V erhht werden und wieder auf 0V usw. Wir knnen also auer den Tasten "+" und "-" fr eine manuelle Steuerung auch die Taste "a" fr eine automatische Steuerung verwenden. Der Wert im OCR1A soll dann in kurzen Zeitabstnden zyklisch hoch und runter gestellt werden. Dazu verwenden wir noch einen 8 Bit Timer, und zwar denselben den wir oben schon definiert haben. Er soll alle 10ms einen Interrupt auslsen, der den Wert im OCR1A verndert. im nachfolgenden Quelltext wird der Interruptbefehl dargestellt.

// Globale Variablen volatile unsigned long int g_ulix = 80; volatile unsigned char g_ucAblauf = 0; volatile int g_iVorzeichen = 1;

// ISR Timer0 Overflow SIGNAL(SIG_OVERFLOW0) { if(g_ucAblauf) { if(g_ulix<=8) g_iVorzeichen=1; if(g_ulix>=80) g_iVorzeichen=-1; g_ulix+=g_iVorzeichen; OCR1A = g_ulix; // Beim Programmstart ist g_ucAblauf auf 0 gesetzt, somit // wird bei den Interrupts nicht die if-Bedingung erfllt sein, // sondern nur TCNT0 auf 99 vorgeladen.

24

// // // //

Wenn die a gedrckt wird und die if-Bedingung erfllt ist, so wird der Wert von OCR1A zwischen 8 und 80 variiert. Dadurch schwankt ebenfalls der Ausgangsspannungsmittelwert und die LED blinkt!

} TCNT0 = 99; }

//------------------------------------------------------------------------// // Hauptprogramm // //------------------------------------------------------------------------int main(void) { DDRD = 0x60; // Pins mit LED als Ausgang gesetzt 0110 0000, // alle anderen Eingang. PORTD = 0x00; // Pull-Up-Widerstnde nicht aktiviert 0000 0000, // LEDs am Anfang aus char c; unsigned int uiUBRR = 16; Init_UART(uiUBRR); // Initialisierungsfunktion UART // UBRR = 16 ==> Baudrate = 115,2k // U2X gesetzt ==> Asynchronous Mode Double Speed

/* Pulsweitenmodulator */ init_PWM(); /* 8 Bit Timer */ init_timer_8bit();

while(1) { if(UCSRA & (1<<RXC)) { c = UDR; switch(c) { case '+': if(g_ulix>245) g_ulix = 255; else g_ulix += 10; OCR1A = g_ulix; break; case '-': if(g_ulix<10) g_ulix = 0; else g_ulix -= 10; OCR1A = g_ulix; break; case 'a': if (g_ucAblauf) g_ucAblauf = 0; // Wird ein a gedrckt wenn die LED blinkt // (also g_ucAblauf = 1), so wird g_ucAblauf // auf 0 gesetzt. Bei dem Interrupt, das jede // 10ms ausgelst wird, ist die if-Bedingung // nun nicht mehr erfllt // ===>> Blinken wird gestoppt else g_ucAblauf = 1; // Wenn a gedrckt wird und g_ucAblauf = 0 // ist, so wird es jetzt auf 1 gesetzt. Bei // jedem Interrupt, der alle 10ms ausgelst // wird, ist jetzt die if-Bedingung erfllt

25

// und somit blinkt die LED // ===>> Blinken wird gestartet break; default: break; } } } return(0); }

5. LCD Ansteuerung
LCD ist eine Abkrzung und bedeutet Liquid Crystal Display. bersetzt bedeutet dies Flssigkristall-Anzeige. Flssigkristalle sind organische Verbindungen, die Eigenschaften von Flssigkeiten und Festkrpern besitzen. Zwischen zwei Glasplatten mit Polarisationsfiltern schwimmen die Flssigkristalle. Durch Anlegen einer Wechselspannung ndert sich die Polarisationsebene der Flssigkristalle und damit, ob das einfallende Licht reflektiert oder absorbiert wird. Bei einem LCD-Modul befindet sich neben dem LCD auch ein Controller zur Ansteuerung des LCDs. Text-Displays verfgen ber genormte 14 Anschluss Pins (LCD-Module mit Backlight ber 16 Pins). Lediglich bei der Backlight Versorgungsspannung und Polung kann es Unterschiede geben. Im Zweifelsfall hilft hier der Blick ins Datenblatt. Entweder sind die Anschlsse in einer Reihe (1 14(16)) oder zweireihig (2 7(8)) herausgefhrt.

5.1 Text-Displays Text-Displays kommen wegen der problemlosen Anbindung in Mikrocontroller Projekten wie Robotern am hufigsten zum Einsatz. Bei Text-LCDs kommen meistens der HD44780 von Hitachi oder ein kompatibler Controller zum Einsatz. Dieser Controller untersttzt Displays mit bis zu 80 Zeichen. Gngige Displaygren sind: 8 1, 8 2, 16 1, 16 2, 20 2, 20 4, 40 2 Zeichen Displays (Spalten Zeilen). Hat das Display mehr als 80 Zeichen, dann bentigt das Display 2 Controller und verhlt sich nach auen zur Ansteuerung, wie 2 Displays (zustzliche Enable Leitung) .

5.2 Anschlubelegung fr 80 Zeichen Displays Text-Displays verfgen ber genormte 14 Anschluss Pins (LCD-Module mit Backlight ber 16 Pins). Lediglich bei der Backlight Versorgungsspannung und Polung kann es Unterschiede geben. Im Zweifelsfall hilft hier der Blick ins Datenblatt. Entweder sind die Anschlsse in einer Reihe (1 14(16)) oder zweireihig (2 7(8)) herausgefhrt. 26

Nachdem die Steuersignale RS und R/W mindestens 140ns stabil anliegen, erzeugt der AVR an Pin 6 des Moduls den positiven bernahmeimpuls E (Enable) von mindestens 450ns Dauer, durch den die Datenbits DB0....DB7 in den Display-Controller HD44780 eingeschrieben werden. Dies geschieht im Quelltext dann mit Hilfe einer delay()-Funktion. 5.3 Anschluss des LCD an Mikrocontroller

Die Pins fr die Hintergrundbeleuchtung mssen mit einemVorwiderstand beschaltet werden (sieheDatenblatt) und kommen dann einfach an die 5V Spannung.

27

5.4 Befehlsbersicht

Anmerkungen: DD RAM: CG ROM: CG RAM: Display Data Ram. Hier werden die anzuzeigenden Daten geschrieben. Character Generator ROM Enthlt die Zeichen in Form von 5x8 oder 5x10 Punktmatrizen. Character Generator RAM Hier knnen acht benutzerdefinierte Zeichen 5x8 Pixel oder vier 5x10 Pixel abgelegt werden.

28

Diese Bitkombinationen werden an die Datenbits gesendet (im 4 Bit Modus als 2 Nibbles hintereinander). Zum Beispiel fr eine einfache Anzeige mssen zur Initialisierung die Befehle Clear Display, Entry Mode Set, Display On/Off Control und Function Set ausfhren.

5.5 Beispiel fr eine einfache Ausgabe auf das LCD In dem File lcd-routines.h sind ein paar brauchbare Konstanten definiert. Wie z.B. PORTD mit LCD_PORT definiert wird. Mchte man dann ein anderes Port verwenden, um z.B. die Pins PD0 (RXD) und PD1 (TXD) fr die UART-bertragung nutzen zu knnen, so kann man hier einfach PORTB als LCD_Port definieren und brauch am sonstigen Quelltext nichts weiter zu ndern. In lcd-routines.c ist aufgefhrt, wie es mit dem Senden von Daten und Befehlen sowie der Initialisierung funktionier. lcd-routines.h:
// Ansteuerung eines HD44780 kompatiblen LCD im 4-Bit-Interfacemodus // http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial // void lcd_data(char temp1); void lcd_string(char *data); void lcd_command(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); // Hier die verwendete Taktfrequenz in Hz eintragen, wichtig!

29

#define F_CPU 16000000 // LCD Befehle #define CLEAR_DISPLAY 0x01 #define CURSOR_HOME 0x02 // Pinbelegung fr das LCD, an verwendete Pins anpassen #define LCD_PORT PORTD #define LCD_DDR DDRD #define LCD_RS PD4 #define LCD_EN PD5 // DB4 bis DB7 des LCD sind mit PD0 bis PD3 des AVR verbunden

lcd-routines.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 #include <avr/io.h> #include "lcd-routines.h" #include <util/delay.h>

// sendet ein Datenbyte an das LCD void lcd_data(char cTemp1) { LCD_PORT |= (1<<LCD_RS); // RS gesetzt ==> Datenbyte => hier! // RS null ==> Befehl char cTemp2 = cTemp1; cTemp1 = cTemp1 >> 4; cTemp1 &= 0x0F; LCD_PORT &= 0xF0; LCD_PORT |= cTemp1; lcd_enable(); // cTemp1 = High Nibble // maskieren

cTemp2 &= 0x0F; LCD_PORT &= 0xF0; LCD_PORT |= cTemp2;

// cTemp2 = Low Nibble

lcd_enable(); _delay_us(42); // Ausfhrungszeit betrgt laut Datenblatt 40us }

// sendet einen Befehl an das LCD void lcd_command(char cTemp1)//ok { LCD_PORT &= ~(1<<LCD_RS); // RS gesetzt ==> Datenbyte // RS null ==> Befehl => hier! char cTemp2 = cTemp1; cTemp1 = cTemp1 >> 4; cTemp1 &= 0x0F; // cTemp1 = High Nibble // maskieren

30

LCD_PORT &= 0xF0; LCD_PORT |= cTemp1; lcd_enable();

cTemp2 &= 0x0F; LCD_PORT &= 0xF0; LCD_PORT |= cTemp2;

// cTemp2 = Low Nibble

lcd_enable(); _delay_us(42); // Ausfhrungszeit betrgt laut Datenblatt 40us }

// erzeugt den Enable-Puls void lcd_enable(void) { // _delay_us(1); // ggf. kurze Pause, da RS und R/W vor dem bernahmeimpuls // mind. 140ns stabil anliegen mssen. // Mann kann es auch weglassen, aber wenn es zu Problemen // kommt, dann doch lieber einfgen! LCD_PORT |= (1<<LCD_EN); _delay_us(1); // bernahmeimpuls muss mind 450ns anliegen. 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); // Pins PD0-PD3 sowie PD4 und PD5 als Ausgnge definieren. // muss 3mal hintereinander gesendet werden zur Initialisierung _delay_ms(15); LCD_PORT &= 0xF0; LCD_PORT |= 0x03; LCD_PORT &= ~(1<<LCD_RS); 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);

// RS auf 0

// Entry Mode Set lcd_command(0x06);

// // // //

0 0 0 0 0 1 1 0 ==> Erhhe Curser Position, Display schieben Diese Operationen werden whrend des Lesen/Schreiben durchgefhrt.

_delay_ms(1);

31

// Display On/Off Control lcd_command(0x0C); // 0 0 0 0 1 1 0 0 ==> Display on, mit // Curser, Blinken _delay_ms(1); // Cursor/Display shift lcd_command(0x10);

// // // //

0 0 0 1 0 0 0 0 Setzt Cursor Bewegung oder Display Bewegung (S/C), Bewegungsrichtung (R/L).

_delay_ms(1); // Function Set lcd_command(0x28); }

// 0 0 1 0 1 0 0 0 ==> 4 Bit, 2 Zeilen, // 5*7 Punkte

// Sendet den Befehl zur Lschung des Displays void lcd_clear(void) { lcd_command(CLEAR_DISPLAY); _delay_ms(2); // Laut Datenblatt 1.64ms Ausfhrungszeit }

// Sendet den Befehl: Cursor Home void lcd_home(void) { lcd_command(CURSOR_HOME); _delay_ms(2); // Laut Datenblatt 1.64ms Ausfhrungszeit }

// 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; case 2: tmp=0x80+0x40+x; case 3: tmp=0x80+0x10+x; case 4: tmp=0x80+0x50+x; default: return; } lcd_command(tmp); }

break; break; break; break;

// // // // //

1. Zeile 2. Zeile 3. Zeile 4. Zeile fr den Fall einer falschen Zeile

// Schreibt einen String auf das LCD void lcd_string(char *data) { while(*data) { lcd_data(*data); data++; } }

32

Ein kleines Hauptprogramm fr die Ausgabe von Daten knnte dann folgendermaen aussehen:
#include <avr/io.h> #include "lcd-routines.h" #include "main.h" int main(void) { lcd_init(); lcd_data('T'); lcd_data('e'); lcd_data('s'); lcd_data('t'); set_cursor(0,2); lcd_string("Guden"); while(1) { } return 0; }

33

6. Analog to Digital Converter


Im unseren Fall handelt es sich um einen ADC mit einer 10-bit-Auflsung enthalten, d.h. er konvertiert analoge Signale in ein digitales 10-bit-Signal. Bei einer Maximalspannung von beispielsweise 5V entspricht dies Schritten von ca. 0,005V. Der von uns verwendete ATmega16 verfgt ber 8 Kanle, d.h. es sind 8 analoge Eingnge verfgbar (ADC0...ADC7), wovon immer nur einer mit dem Analog Digital Wandler verbunden werden kann. Auf welchen Eingang geschaltet werden soll, lsst sich im Konfigurationsregister einstellen.

6.1 ADC Elektrische Verkabelung

Die ADC-Versorgungsspannung (AVCC) darf maximal um +/-0,3V von der Versorgung des Digitalteils (VCC) abweichen, jedoch nicht 5,5V berschreiten. Die externe Referenzspannung AREF darf nicht kleiner als die im Datenblatt unter ADC Characteristics als VREFmin angegebene Spannung (hier: ATmega16 2V ) und nicht grer als AVCC-0,2V sein. Die Spannungen an den Wandlereingngen mssen im Intervall GND VIN VREF liegen. Im Schaltbild ist AREF zwischen C3 und GND angeschlossen, da wir die interne Referenzspannung verwenden. Dies lsst sich ber Register alles kofigurieren. Der AVCC Pin muss ber ein LC-Netzwerk angeschlossen werden. Es verhindert Spannungsspitzen an dem Pin. Die brigen Kapazitten die zwischen Masse und den Pins geschaltet sind dienen der Abkopplung und somit der wirken somit Strungen entgegen. Die Gren der Kapazitten und der Induktivitt sind ebenfalls dem Datenblatt entnommen (S.213 Figure 106).

34

Die ADC Characteristics Table 122 aus dem Datenblatt zu unserem ATmega16 ist unten aufgefhrt:

35

6.2 Betriebsmodi Der ADC kann in zwei verschiedenen Betriebsarten verwendet werden.

Einfache Wandlung (Single Conversion)


Der ADC wandelt eine analoge Gre um und gibt diese zurck.

Frei laufend (Free Running)


Der ADC wandelt wie in einer Endlosschleife stndig die anliegende analoge Gre in digitale Werte um und gibt diese zurck. Die Rckgabewerte werden in das ADC Data Register geschrieben.

6.3 ADC Register Damit der ADC lauffhig ist, mssen einige Register korrekt initialisiert werden. ADMUX: ADC Multiplexer Select Register

REFS1, REFS0: Reference Selection Bits Das Ergebnis der Wandlung des ADC bezieht sich auf eine bestimmte Referenzspannung. Die Referenzspannung ist also gleich der maximal zu messenden Spannung. Wenn die Spannung hher ist (kleiner als Vcc+0.3V um den Port nicht zu beschdigen) gibt der AD Wandler das maximale Ergebnis. Mit den Bits REFS1 und REFS0 kann man einstellen, welche Referenzspannung verwendet werden soll.

Fall 00: Es wird die externe Spannung als Referenz verwendet die an Pin AREF anliegt In den Fllen 01 und 11 muss ein Entkopplungskondensator zwischen AREF Pin und Masse geschaltet werden, um ihn vor Strsignalen zu schtzen. Die internen Bezugswahlen knnen mglicherweise nicht verwendet werden, wenn eine uere Spannung auf den AREF Pin zugetroffen wird.

36

MUX4...MUX0: MUX2..MUX0: Die 8 verfgbaren Kanle werden mithilfe eines Multiplexer auf den ADC geschaltet, der dann die anliegende analoge Spannung in einen digitalten Wert wandelt. MUX4..MUX3: unipolar/differentiell/Verstrkung In unseren Beispielen sind diese Bits beide auf 0 gesetzt.

ADLAR: ADC Left Adjust Result Bestimmt die Ausrichtung des Ergebnisses in den Ergebnisregistern ADCH und ADCL (siehe unten)

ADCL, ADCH: ADC Data Register Hier wird das Ergebnis des 10-Bit Wertes nach jeder Konvertierung abgespeichert. Wichtig beim Auslesen ist, dass zuerst ADCL und danach ADCH ausgelesen wird. Beispiel:
unsigned int x; x = ADCL;

37

x += (ADCH<<8);

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 kann man ADLAR 1 setzen und hat dann die 8 hchstwertigsten Bits im Register ADCH.

ADCSRA: ADC Control an Status Register A Hier stellen wir ein, wie wir den ADC verwenden mchten.

ADEN: ADC Enable Wird dieses Bit gesetzt (also 1), dann wird der ADC aktiviert. Schreibt man eine 0 wird der ADC deaktiert. Wird der ADC whrend einer Wandlung deaktiviert, wird die Wandlung abgebrochen. ADSC: ADC Start Conversion Dieses Bit startet den Messvorgang, je nachdem in welchem Betriebsmodi der ADC luft. In der frei laufenden Betriebsart muss das Bit immer gesetzt sein, um die kontinuierliche Messung zu verwenden Wird dieses Bit nach der Aktivierung des ADC ber ADEN zum ersten Mal gesetzt, wird der ADC erst eine Initialisierungswandlung durchfhren und danach erst die eigentliche Wandlung. Das Schreiben einer 0 auf das Bit hat keinen Effekt. Das Bit bleibt nun so lange auf 1, bis die Umwandlung abgeschlossen ist, im Initialisierungsfall entsprechend bis die zweite Umwandlung erfolgt ist und geht danach auf 0.

38

ADATE: ADC Auto Trigger Enable Wird dieses Bit gesetzt, wird das Auto Triggering des ADC aktiviert. ADIF: ADC Interrupt Flag Dieses Bit wird auf 1 gesetzt, wenn eine Wandlung erfolgte und das Ergebnis nun verfgbar ist. Der ADC Complete Interrupt wird ausgelst, wenn das ADIE-Bit gesetzt ist und die Interrupts global aktiviert sind. Das Bit wird gelscht, wenn die entsprechende ISR abgearbeitet wird. Alternativ kann man es lschen, indem man eine 1 auf das Bit schreibt. ADIE: ADC Interrupt Enable Wird dieses Bit gesetzt und die globalen Interrupts sind aktiviert, wird der ADC Complete Interrupt aktiviert, auf den man dann in der entsprechenden ISR reagieren kann. ADPS2..ADPS0: ADC Prescaler Select Bit Diese Bits bestimmen den Teilungsfaktor zwischen der CPU-Frequenz und dem Eingangstakt des ADC. Zum Wandeln bentigt der ADC einen eigenen Takt, der auf der Taktfrequenz des Mikrocontrollers basiert. Eine Wandlung braucht normalerweise 13 Zyklen des ADC-Taktes. Allerdings ist die Taktfrequenz des Mikrocontrollers zu schnell und deswegen wird diese durch den Vorteiler verkleinert. Der resultierende Takt sollte beim AVR zwischen 50kHz und 200kHz liegen. Mit reduzierter Genauigkeit ist bis 1000 kHz mglich. Folgende Teilungsfaktoren sind verfgbar:

Bei unseren 16 MHz ist dann ein Teilungsfaktor von mind. 80 ntig. Wir nehmen also 128.

SFIOR: Special Function IO Register

39

In dieser Tabelle sind u.a. die Einstellmglichkeiten mit den Bits ADTS2..ADTS0 dargestellt. Dafr muss das ADATE Bit in ACSRA gesetzt sein! Wenn es nicht gesetzt ist, haben die ADTS-Bits keine Auswirkungen! Es kann zum Beispiel der freilaufende Modus aktiviert werden, sodass nach jeder AnalogDigital-Umsetzung direkt die nchste erfolgt. Ist eine Aufnahme der Werte in bestimmten Zeitabstnden erwnscht, so kann man dies ber ein Timer/Counter Interrupt auslsen. Bei jedem Interrupt wird dann einfach mit ADSC=1 (ADC Start Conversion Bit) ein Messwert aufgenommen.

6.4 Beispielprogramm: ADC mit Wertausgabe ber UART und LCD (p:\Mitarbeiter\......\Beispielprogramme\07 Analog Digital Converter)

Wir wollen einen analogen Spannungswert in eine 10-Bit-Zahl konvertieren Die Anzeige soll ber UART und ber LCD erfolgen. Dazu knnen wir die UART- und LCD-Initialisierung aus den vorhergehenden Programmen nutzen (Init_UART, lcd-routines).
#include #include #include #include #include #include #include <avr/io.h> // Register sind hier definiert, z.b. DDRA <stdlib.h> // fr itoa-Funktion "main.h" "lcd-routines.h" // kennen wir schon "UART.h" // kennen wir schon "Init_ADC.h" "timer0.h"

Initialisierungsfunktion des ADC:


#include <avr/io.h> #include "Init_ADC.h" //------------------------------------------------------------------------// Init_ADC

40

// // -> -// <- -//------------------------------------------------------------------------void Init_ADC(unsigned int mux) { ADMUX = mux; // Messkanal ADC0...ADC7 ADMUX |= (0<<REFS1)|(1<<REFS0); // Interne Referenzspannung auf AVCC festgelegt ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // ADC Enable // ADC Takt: 16MHz / 128 = 125kHz // 1. Umwandlung - Initialisierungswandlung // "zum warm werden" ADCSRA |= (1<<ADSC); while (ADCSRA & (1<<ADSC)) {} // Warten bis Umwandlung abgeschlossen ist }

Timer0
Soll bei jedem Overflow eine Variable hoch zhlen.
volatile int *g_status=0; volatile int ablauf=0; //------------------------------------------------------------------------// INTERRUPTROUTINE // // alt: SIGNAL(siglabel) // neu: ISR(Vectorname) // //------------------------------------------------------------------------ISR(TIMER0_OVF_vect) // veraltet: SIGNAL(SIG_OVERFLOW0) { *g_status = *g_status+1; }

//------------------------------------------------------------------------// Initialisierung Timer0 // // --> -// // <-- -// //------------------------------------------------------------------------void init_timer_8bit(int *status) { TCCR0 |= (1<<CS02) | (0<<CS01) | (1<<CS00); // Zhltakt 16MHz / 1024 = 15625 Hz TCNT0 = 0; // Zhlregister 0 // zhlt jetzt in jedem Durchgang von 0-255 // dies dauert 16.384ms // Die Variable 'status' im main() Wird durch den Zeiger // *g_status in der Interruptroutine hochgezhlt. Bei // jeder Erhhung um 1 vergehen 16.384ms.

TIMSK |= (1<<TOIE0); // Freigabe des T/C0 Overflow Interrupts g_status=status;

41

// Addressbergabe an g_status sei();

// Interrupts aktivieren }

Main Programm
//------------------------------------------------------------------------// // HAUPTPROGRAMM // // //------------------------------------------------------------------------int main(void) { double d_value=0; int status=0; char mittelwert[10]; int i;

Init_ADC(0); /* Initialisierung der Ports, Messkanal 1 wird ausgewhlt */

Init_UART(16); /* Initialisierung UART UBRR = 16 ==> Baudrate = 115,2k U2X = 1 ==> Asynchronous Mode Double Speed Character Size: 8 Bit */ init_timer_8bit(&status); // Variable status wird in der ISR alle 16.384ms hochgezhlt. lcd_init();

//-----------------------------------------------------------------// HAUPTSCHLEIFE // // Fnfmaliges Messen im Abstand von 50ms und anschlieende // Mittelwertbildung //-----------------------------------------------------------------while(1) { for (i=0; i<5; i++) { ADCSRA |= (1<<ADSC); // Mit ADSC=1 wird Konvertierung gestartet while (ADCSRA & (1<<ADSC)) {} // Warten, bis Konvertierung abgeschlossen ist d_value += ADCL; d_value += (ADCH<<8); // Wert aus dem Data Register auslesen

while(status<3) { itoa(status, mittelwert, 10); // Ziemlich sinnlos, aber ohne diese Zeile geht es // auch nicht ;-)

42

} status = 0; // Variable 'status' wird in der ISR alle 16.384ms // hochgezhlt. Nach 3-mal hochzhlen sind wir bei // ca. 50ms. }

d_value /= 5; // 5 Messungen wurden addiert => MW-Bildung bei Teilung // durch 5

gcvt(d_value, mittelwert); // MW wird in den String 'mittelwert' abgespeichert

d_value = 0; // Rcksetzen auf 0

lcd_clear(); lcd_string(mittelwert); // Ausgabe des MW auf das LCD Display

UART_Print(mittelwert); // Ausgabe des MW ber UART } return(0); }

43

7. EEPROM
Electrically Erasable Programmable Read-Only Memory wrtlich: elektrisch lschbarer, programmierbarer Nur-Lese-Speicher Der EEPROM Speicher besteht aus einer Feldeffekt-Transistorenmatrix, in der jeder Transistor ein Bit reprsentiert. Beim Programmiervorgang werden Ladungen gespeichert (Transistor schliet) und beim Lschvorgang werden Ladungen wieder entfernt. Es ist nur ein byteweises beschreiben und lschen mglich. Bei dem hier verwendeten ATmega16 ist jedes der 512 vorhandenen Bytes mindestens 100.000 Mal beschreib- bzw. lschbar. Die Bibliothek <avr/eeprom.h> muss bei der Programmierung mit EEPROM eingebunden werden! 7.1 Einzelne Bytes lesen/schreiben Um Bytes zu schreiben bzw. zu lesen, entnehmen wir die EEPROM-write- und die EEPROM-read-Funktionen aus dem Datenblatt S.21/22. (p:\Mitarbeiter\......\Beispielprogramme\08 EEPROM read + write)
//------------------------------------------------------------------------// EEPROM write- und read-Funktion aus dem Datenblatt (S.21/22) // // EEPROM_write: // -> 1. Adresse des Bytes welches beschrieben werden // soll (0-511!!!) // 2. 8-Bit Data welches auf das Byte im EEPROM // geschrieben // werden soll // <- -// EEPROM_read: // -> Adresse des Bytes welches gelesen werden soll // <- 8-Bit-Data, welches sich auf dem Byte befindet //------------------------------------------------------------------------void EEPROM_write(unsigned int uiAddress, unsigned char ucData) { /* Wait for completion of previous write */ while(EECR & (1<<EEWE)) ; /* Set up address and data registers */ EEAR = uiAddress; EEDR = ucData; /* Write logical one to EEMWE */ EECR |= (1<<EEMWE); /* Start eeprom write by setting EEWE */ EECR |= (1<<EEWE); } unsigned char EEPROM_read(unsigned int uiAddress) { /* Wait for completion of previous write */ while(EECR & (1<<EEWE)) ;

44

/* Set up address register */ EEAR = uiAddress; /* Start eeprom read by writing EERE */ EECR |= (1<<EERE); /* Return data from data register */ return EEDR; }

Nun soll ein Byte beschrieben und anschlieend wieder ausgelesen werden:
#include #include #include #include <avr/io.h> "main.h" "eeprom.h" // Hier die beiden Funktionen von oben reinschreiben! "uart.h"

int main(void) { unsigned char c; Init_UART(); while(1) { if(UCSRA&(1<<RXC)) { c=UDR; switch(c) { case 'a': EEPROM_write(1, 1); break; case 'b': EEPROM_write(1, 2); break; case 'c': EEPROM_write(1, 3); break; case 'r': while(!(UCSRA & (1<<UDRE))); //ggf. warten bis Puffer (letzte //bertragung) wieder frei ist. //Dies ist der Fall, wenn das Bit UDRE //UART Data Register Empty gesetzt ist. UDR = EEPROM_read(1); // Daten in UDR schreiben while(!(UCSRA & (1<<UDRE))); //Warten, bis Byte versendet ist break; default: break; } } } return 0; }

45

7.2 EEPROM auslesen Um den berblick zu behalten, was man wohin schreibt, ist es von groem Nutzen sich den Gesamten Speicherinhalt ber UART ausgeben zu lassen. Jedes Byte wird in eine Tabelle als Hexadezimalzahl eingetragen.
void EEPROM_Auslesen(void) { int i, j,k; unsigned int address; //************************************************* // EEPROM SPeicher Bytes 0-511 //************************************************* // Tabelle wird erstellt

// Spalten werden mit 0-9 beschriftet UARTPrint("\t\t "); for (i=0; i<10; i++) { UARTPrintDez(i); UARTPrint("\t "); } UARTPrint("\t\t \t\t\t\t\t"); for (i=0; i<10; i++) { UARTPrintDez(i); UARTPrint("\t "); } UARTPrint("\n\n"); // Werte des EEPROMs werden nacheinander ber UART ausgegeben address=0; for (i=0; i<26; i++) { UARTPrintDez(i); UARTPrint("\t"); if (i<10) UARTPrint(" "); for(j=0;j<10;j++) { UARTPrintHex08( EEPROM_read(address) ); address++; UARTPrint("\t"); if (j==9) { UARTPrint("\t\t\t\t\t"); UARTPrintDez(i+26); UARTPrint("\t"); address=address-10+260; for(k=0;k<10;k++) { if (address<512) UARTPrintHex08( EEPROM_read(address) ); address++; UARTPrint("\t");

46

} address=address-260; } } UARTPrint("\n"); } UARTPrint("\n\n\n\n\n\n\n\n\n\n"); }

Der Inhalt des EEPROMs sieht dann so aus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 .. 51 0 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF .. FF 1 09 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF .. FF 2 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF .. FF 3 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF .. 4 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF .. 5 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF .. 6 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF .. 7 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF .. 8 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF .. 9 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF .. Auf das Byte mit Adresse 1 wurde eine 9 gespeichert. Im Rest steht immer 0xFF drin.

Wird ein Messwert in einer while-Schleife immer wieder gemessen und im EEPROM agbespeichert, so "lebt" der Speicher lnger, wenn alle Speicherzellen dafr verwendet werden. Denn die Lebensdauer jeder Zelle wird vom Hersteller nur bis zu 100.000 Speicherbzw. Lschvorgngen garantiert. Mchte man jetzt den Messwert erneut abspeichern, dann soll dies auf das Byte mit der Adresse 2 erfolgen. Wir schauen also so lange im Speicher, bis wir ein Byte ungleich 0xFF finden. Auf dieses soll nun wieder 0xFF geschrieben werden; auf das nchste also auf Adresse 2 soll der Messwert geschrieben werden. 47

0 1 2 3 ..

0 FF FF FF FF ..

1 FF FF FF FF ..

2 10 FF FF FF ..

3 FF FF FF FF ..

4 FF FF FF FF ..

5 FF FF FF FF ..

6 FF FF FF FF ..

7 FF FF FF FF ..

8 FF FF FF FF ..

9 FF FF FF FF ..

Neuer Messwert eine Adresse weiter abgespeichert, Adresse vorher mit FF berschrieben.

7.3 Daten ins EEPROM speichern (p:\Mitarbeiter\......\Beispielprogramme\09 EEPROM Daten speichern + lesen) Als sehr ntzlich erweist es sich, bestimmt Adressen fest zu definieren:
#define #define EE_DUMMY EE_Start 0x000 0x003 // // // // Dummyelement (Adresse 0 sollte nicht genutzt werden) In diesem Beispiel die Startadresse des zu speichernden Vierer-Byte-Blocks.

Jetzt soll eine mehr als 1 Byte groe Variable in den Speicher geschrieben werden. Wir machen mal ein Beispiel mit einer 32 Bit Zahl. Wir mssen diese Zahl auf 4 Bytes speichern. Es knnte sich zum Beispiel um einen sehr groen Messwert handeln. Dieser soll nach jedem Messvorgang abgespeichert werden. Wie im Beispiel oben wollen wir alle verfgbaren Bytes dazu nutzen! Da das erste Byte nicht beschrieben werden soll und wir immer in 4-Byte-Blcken speichern, verwenden wir die Bytes 3-511. Hier die Funktion dazu: Die Variable g_Viererblock_Address erhlt vor der ersten Speicherung den Wert 0x03. Bei jedem 4-Byte-Wert, der gespeichert wird, erhht sich diese um +4. Ist das Ende des EEPROMs erreicht (Adresse 511), dann wird wieder das Byte mit Adresse 3 beschrieben.
Wird das Programm dann neu gestartet, so muss der EEPROM erstmal ausgelesen werden. Der erste Wert, der sich von 0xFFFFFFFF unterscheidet, ist unser Messwert. Die Adresse, bei der wir uns befinden, bergeben wir an g_Viererblock_Address. //------------------------------------------------------------------------// write_32bit_value: Speichert 32 Bit Wert ab (=4 Bytes fr // den Wert) // // -> unsigned long value: zu speichernder Wert // <- -//------------------------------------------------------------------------void write_32bit_value_(unsigned long value ) { unsigned char k; unsigned char tmp[4]; //g_Viererblock_Address: Start-Adresse des 4-Byte-Blocks

48

// // //

es ist eine globale Variable, damit man die Adresse bequem abrufen und im Hauptprogramm verndern kann.

if (g_Viererblock_Address>=508)

// Wenn ja, dann wieder die // ersten Bytes beschreiben und // die letzten des EEPROMs auf // 0xFF g_Viererblock_Address=EE_Start; // Viererblockadresse wieder // auf Anfang setzen

// beschreibt jetzt 4 Bytes! for( k = 0; k < 4; k++ ) { tmp[k] = (unsigned char) (value>>k*8); EEPROM_write( g_Viererblock_Address+3-k, tmp[k]); } g_Viererblock_Address+=4;

// Die letzen 4 Bytes lschen. // Wenn die vorherigen Bytes die letzten im Speicher waren // sollen sie gelscht werden. // In die Bytes 507 bis 511 jeweils 0xFF reinschreiben. for( k = 0; k < 4; k++ ) { EEPROM_write( 507+k, 0xFF); } }else{ // Wenn man sich irgendwo mitten im EEPROM // befindet, also nicht am Ende, dann ganz normal // den Wert abspeichern. for( k = 0; k < 4; k++ ) { tmp[k] = (unsigned char) (value>>k*8); EEPROM_write( g_Viererblock_Address+3-k, tmp[k]); } if (g_Viererblock_Address!=EE_Start) { for( k = 4; k >0; k-- ) { tmp[k] = (unsigned char) (value>>k*8); EEPROM_write( g_Viererblock_Address-k, 0xFF); } } g_Viererblock_Address+=4; } }

49

Und hier die Lese-Funktion:


//------------------------------------------------------------------------// read_32bit_value: liest 4 Bytes nacheinander aus dem EEPROM aus und wenn // der Wert != 0xFFFFFFFF, dann wird Zhlstand // zurckgegeben. // // Funktion wird einmalig beim Programmstart aufgerufen // // -> -// <- value: Gesamtzhlstand //------------------------------------------------------------------------unsigned long read_32bit_value_(void) { unsigned int k=0; unsigned long value=0xFFFFFFFF; g_Viererblock_Address=EE_Start;

// Es werden die nchsten 4 Bytes gelesen. // Wenn alle Bytes 0xFF sind, dann die nchsten 4 lesen usw. while (value==0xFFFFFFFF) { value = 0; for( k = 0; k<4; k++ ) { value|=((EEPROM_read(g_Viererblock_Address+(3-k))<<k*8)); } g_Viererblock_Address+=4; if (g_Viererblock_Address>511) break; }

if (g_Viererblock_Address>511) { g_Viererblock_Address=EE_Start; }

if (value==0xFFFFFFFF) return 0; else return value; }

50