Hier geht es zur Hauptseite und zum Impressum

Auf dieser Seite finden Sie Programme für den Mikrocontroller ATtiny45 und den ATmega32.
Die Reihenfolge ist umgekehrt, d. h. die früheren und einfachen Programme finden Sie unten, die aktuellen und schwierigeren Programme oben.
Übersicht:
Tipps und Tricks
Testprogramm für Oszilloskop
Steuerung des Mikrocontrollers und Summers mit Licht
Steuerung des Mikrocontrollers mit Licht
Aufladen eines Kondensators
Ein einfaches Programm mit Zeigern
Eine einfache Lichtschranke
Der Mikrocontroller misst die Lichtstärke
Der Mikrocontroller zählt Tastendrücke
Man verwendet kein goto, aber...
Eine einfache Stoppuhr mit 7-Segment-Anzeige
Der Mikrocontroller spielt ein Lied und zeigt die Noten an
Der Mikrocontroller spielt ein Lied, aber diesmal richtig!
Eine Tonleiter mit Timer 0
Zwei 7-Segment-Anzeigen zeigen eine Spannung an
Zwei 7-Segment-Anzeigen im Multiplexbetrieb
Die 7-Segment-Anzeige zeigt Zahlen
Ansteuerung einer 7-Segment-Anzeige
Erzeugung von bestimmten Frequenzen
Schlafmodus für den ATmega32
Die Lichterkette als 16-Bit-Zähler
PWM mit Timer0 und logarithmischer Ausgabe
PWM mit Timer0
Timer1 Overflow Interrupt
"Auf auf zum fröhlichen Jagen" mit ATmega32
Der laufende "Tropfen"
"Zufallszahlen" mit AD-Wandlung erzeugen und in EEPROM schreiben
Programm "Lichterkette stellt Zufallszahlen dar"
Programm "Lichterkette stellt Ergebnis der AD-Wandlung dar"
Programm "Lichterkette mit frei laufender AD-Wandlung"
Programm "Lichterkette mit AD-Wandlung"
Programm "Lichterkette"
Ein merkwürdiger Effekt
Programm "Ampel"
Programm "Komparator"
Programm "Sirene" in Module zerlegt
Programm "Sirene"
Morsen
Ansteuerung von 3 LED mit 120 Grad Phasenverschiebung, verbessert
Ansteuerung von 3 LED mit 120 Grad Phasenverschiebung
Der Mikrocontroller schläft und wird durch einen Hund geweckt
Der Mikrocontroller geht nochmal schlafen
Der Mikrocontroller geht schlafen
"Auf auf zum fröhlichen Jagen" mit ADC
Zufallszahlen und C-Dur-Tonleiter
Zufallszahlen und Watchdog Timer
Zufallszahlen und Melodie
Zufallszahlen mit AD-Wandlung erzeugen und in EEPROM schreiben
Zufallszahlen mit AD-Wandlung erzeugen
Frequenz eines Piezo mit einem Poti steuern
Dimmen einer LED mit Poti
Poti als Schalter
Analog-Digital-Wandlung
Grundlagen der Analog-Digital-Wandlung
"Atmung"
Interrupt
Echo
ein Lied mit LED
ein Lied
Tonleiter
ein erstes Assemblerprogramm

Tipps und Tricks


Im Folgenden werden einige oft verwendete Programmiertechniken in C dargestellt.

Ein Bit in einem Register auf 1 setzen

Oft muss in einem Register ein bestimmtes Bit auf 1 gesetzt werden. Dabei sollen die anderen Bits des Registers unverändert bleiben.
Angenommen, wir wollen im Register PORTB das Bit PB3 auf 1 setzen. Der C-Code dazu sieht so aus:

PORTB |= 1<<PB3;
Was bedeutet das?
Der Operator << ist ein Bitschiebeoperator. Er bedeutet hier, dass die 1 um 3 Stellen nach links geschoben wird. 1<<PB3 ist eine Bitmaske. Die sieht dann so aus:
0 0 0 0 1 0 0 0
PB7 PB6 PB5 PB4 PB3 PB2 PB1 PB0
PORTB |= 1<<PB3; ist eine verkürzte Schreibweise für PORTB = PORTB | 1<<PB3;
| ist der Operator für die ODER-Verknüpfung. Das bedeutet, dass alle Bits im Register PORTB, die entsprechend in der Bitmaske Null sind, so bleiben, wie sie sind. Alle entsprechenden Bits, die Eins sind, werden auf 1 gesetzt.

Ein Bit in einem Register auf 0 setzen

Oft muss in einem Register ein bestimmtes Bit auf 0 gesetzt, das heißt gelöscht werden. Dabei sollen die anderen Bits des Registers unverändert bleiben.
Angenommen, wir wollen im Register PORTB das Bit PB4 auf 0 setzen. Der C-Code dazu sieht so aus:

PORTB &= ~(1<<PB4);
1<<PB4 ist wieder unsere Bitmaske. Sie sieht nun so aus:
0 0 0 1 0 0 0 0
PB7 PB6 PB5 PB4 PB3 PB2 PB1 PB0
Was bedeutet die Tilde ~? Sie bedeutet Invertierung, d. h. die ganze Bitmaske wird invertiert. Die Bitmaske sieht nun so aus:
1 1 1 0 1 1 1 1
PB7 PB6 PB5 PB4 PB3 PB2 PB1 PB0
Nun wird mit dem Operator & eine UND-Verknüpfung vorgenommen. Das heißt, alle Bits des Registers PORTB, deren Entsprechung in der Bitmaske eine Null ist, werden auf Null gesetzt, d. h. gelöscht.
Alle Bits, deren Entsprechung in der Bitmaske eine Eins ist, bleiben unverändert.

Ein Bit toggeln

Toggeln heißt, das Bit wird umgeschaltet. Wenn es vorher 1 war, wird es 0 und umgedreht.
Angenommen, wir wollen im Register PORTB das Bit PB0 toggeln. Der C-Code dazu sieht so aus:

PORTB ^= 1<<PB0;
Hier kommt die Exklusiv-ODER-Funktion zum Einsatz, auch XOR genannt. Die Wahrheitstabelle einer XOR-Verknüpfung sieht so aus:
A B A^B
0 0 0
0 1 1
1 0 1
1 1 0
Aus der Wahrheitstabelle ist zu ersehen, dass das Ergebnis 0 ist, wenn beide Eingänge gleich sind. Wenn beide Eingänge unterschiedlich sind, ist dagegen das Ergebnis 1.
Nun können wir die XOR-Verknüpfung auf unser PORTB-Register betrachten. Wenn das Bit in der Bitmaske 0 ist, bleibt das Bit im PORTB-Register erhalten, wenn es 1 ist, wird das entsprechende Bit im Register getoggelt, d. h. invertiert.
Die folgende Tabelle zeigt unsere Bitmaske, und ein Beispiel einer Belegung von PORTB vor und nach der XOR-Verknüpfung.
Bitmaske 0 0 0 0 0 0 0 1
PORTB vorher 0 1 0 1 0 1 1 1
PORTB nachher 0 1 0 1 0 1 1 0

Den Pull-Up-Widerstand eines Bits einschalten

Wenn ein Bit als Eingang geschaltet wurde, kann man den internen Pull-Up-Widerstand des Bits aktivieren. Das bewirkt, dass das Bit durch den Controller selbst auf High gezogen wird. Dann kann man es durch einen Taster auf 0 schalten.
Angenommen, wir wollen den Pull-Up-Widerstand des Bits PB2 des Ports B einschalten. Der C-Code dazu sieht so aus:

PORTB |= 1<<PB2;

Testprogramm für Oszilloskop



#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

uint16_t zeit;

void pause(uint16_t zeit)
{
    for(uint16_t i=0; i<zeit; i++)
        _delay_us(1);
}

int main(void)
{
    DDRD |= 1<<PD6;   //PD6 als Ausgang definieren
    zeit=1;
    while(1)
    {
        if (zeit < 1000)
        {
            zeit++;
            PORTD |= 1<<PD6;
            pause(zeit);
            PORTD &= ~(1<<PD6);
            pause(zeit);
        }
        else
            zeit = 1;         
    }
}

Steuerung des Mikrocontrollers und Summers mit Licht


Hier ist die Schaltung:

Der Summer gibt ein gleichmäßiges Signal von sich. Wenn der lichtabhängige Widerstand angeleuchtet wird, ändert sich die Frequenz des Summers.

#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

int main(void)
{
    DDRD |= 1<<PD6;                   //PD6 als Ausgang schalten, 
                                            //dort sitzt der Buzzer
    ADCSRA |= 1<<ADEN;                //enable ADC
    ADCSRA |= (1<<ADPS1)|(1<<ADPS0);  //Prescaler für Analog-Digital-Wandlung
    uint16_t zahl=0;

    void ton(uint16_t zahl)
    {
        for(uint16_t i=0; i<zahl; i++)
            _delay_us(1);
        PORTD |= 1<<PD6;               //PD6 auf High schalten

        for(uint16_t i=0; i<zahl; i++)
		    _delay_us(1);
        PORTD &= ~(1<<PD6);            //PD6 auf Low schalten
    }
	
    while(1)
    {
        ADCSRA |= 1<<ADSC;        //AD-Wandlung auslösen
        while(ADCSRA&(1<<ADSC));  //Warten auf Ende der Wandlung
        zahl = ADCL + (ADCH<<8);  //Ergebnis der Wandlung in zahl laden
        ton(zahl);                      //Aufruf der Funktion ton
    }
}

Steuerung des Mikrocontrollers mit Licht


Hier ist die Schaltung:

Wenn der lichtabhängige Widerstand angeleuchtet wird, blinkt die LED schnell, ansonsten langsam.

#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

int main(void)
{
    DDRB |= 1<<PB0;
    ADCSRA |= 1<<ADEN;                //enable ADC
    ADCSRA |= (1<<ADPS1)|(1<<ADPS0);  //Prescaler
    uint16_t zahl=0;
    while(1)
    {
        ADCSRA |= 1<<ADSC;        //AD-Wandlung auslösen
        while(ADCSRA&(1<<ADSC));  //Warten auf Ende der Wandlung
        zahl = ADCL + (ADCH<<8);
        if (zahl<600)             //den Wert von zahl individuell probieren
        {
            for(int k=0; k<5; k++)
            {
                PORTB |= 1<<PB0;    //schnelles Blinken
                _delay_ms(200);
                PORTB &= ~(1<<PB0);
                _delay_ms(200);
            }
        }
        else
        {
            PORTB |= 1<<PB0;         //langsames Blinken
            _delay_ms(1000);
            PORTB &= ~(1<<PB0);
            _delay_ms(1000);
        }
    }
}

Ein Kondensator wird aufgeladen


Hier ist die Schaltung:

Es wird beim Einschalten ein Kondensator aufgeladen. Die Spannungswerte werden in den EEPROM geschrieben.
Anschließend kann der EEPROM-Inhalt in eine csv-Datei ausgelesen werden. (Comma separated Values)

// Die Spannungswerte beim Start werden in den EEPROM geschrieben.
// Es können 1024 Bytes in den EEPROM gespeichert werden.

#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

unsigned char zahl, wert;

// Die folgende Funktion dient zum Schreiben des EEPROMs
// aus dem Datenblatt des ATmega32
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);
}

int main(void)
{
    DDRB |= 1<<PB0;
    DDRD |= 1<<PD3;
    PORTD |= 1<<PD3;
    ADCSRA |= (1<<ADEN);    //Enable ADC, d. h. die AD-Wandlung wird aktiviert
    // Wir verwenden ADC0 als Eingang
	
    ADMUX |= (1<<ADLAR); //wir wollen nur 255-Bit-Auflösung, daher left-adjusted

    ADCSRA |= (1<<ADPS1)|(1<<ADPS0);  //Einstellung des Prescalers für ADC
    // Der Prescaler muss so gewählt werden, dass sich der Takt
    // für den AD-Wandler zwischen 50 kHz und 200 kHz ergibt.
    // Hier: 1 MHz/8 = 125 kHz --> OK!

    for (unsigned int i = 0; i<1020; i++) //1020 Schleifendurchläufe für 1020 EEPROM-Bytes
    {
        zahl=wert=0;
        ADCSRA |= (1<<ADSC);     //Start der Wandlung
        while(ADCSRA&(1<<ADSC)); //Warten auf Ende der Wandlung, sie dauert 13 Takte
        wert = ADCH;             //ADCL muss ebenfalls gelesen werden, da sonst keine
                                 //neue Wandlung gestartet wird
        EEPROM_write(i, wert);   //wert in den EEPROM schreiben
    }

    while(1)
    {
        PORTB |= 1<<PB0; //Blinken zeigt Ende des Programms an
        _delay_ms(1000);
        PORTB &= ~(1<<PB0);
        _delay_ms(1000);
    }
}

//EEPROM-Lesen und Erzeugen einer csv-Datei mit avrdude (comma separated values)
//avrdude -c usbtiny -p m32 -U eeprom:r:datei.csv:d
//Es wird der EEPROM ausgelesen, read, in datei.csv, dezimal

Ein einfaches Programm mit Zeigern



/*
 * ATmega32_Swap.c
 * Das Programm arbeitet mit Zeigern.
 * Die Funktion swap bewirkt das Hin- und Herschalten zwischen 
 * zwei Variablen.
 *
 * Created: 13.05.2017 20:31:31
 *  Author: Holger Freydank
 */ 

#define F_CPU 1000000UL
#include <avr/io.h>
#include <stdint.h>
#include <avr/interrupt.h>

int a=73, b=54;   //Codierung der 7-Segment-Anzeige
                  //globale Variablen

void swap (int *px, int *py)   
{
    int tmp;
    tmp = *px;
    *px = *py;
    *py = tmp;
}

ISR(INT2_vect)
{
    swap(&a, &b);
}

int main(void)
{
    PORTB |= 1<<PB2;    //Pull-up Widerstand an PB2 einschalten
    DDRC = 0xFF;              //Port C als Ausgang definieren
    GICR |= 1<<INT2;    //Interrupt durch INT2 ermöglichen
                              //ISC2 im Register MCUCSR ist 0
                              //  -> fallende Flanke von INT2 löst Interrupt aus
    sei();              //Interrupt ermöglichen
    while(1)
    {
        PORTC = a;      //7-Segment-Anzeige
    }
}

Eine einfache Lichtschranke


Hier ist die Schaltung:

Das Programm:

#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

int anwert, auswert, zahl;

void alarm(void)
{
    for(int i = 0; i<500; i++)  //akustisches Signal
    {
        PORTB ^= 1<<PB0;
        _delay_ms(5);
    }
}

int getADC(void)
{
    ADCSRA |= (1<<ADSC);     //AD-Wandlung auslösen
    while(ADCSRA&(1<<ADSC)); //Warten auf das Ergebnis der AD-Wandlung
    zahl = ADCL + (ADCH<<8); //Ergebnis der Wandlung in zahl laden
    return zahl;
}

int main(void)
{
    DDRB |= 1<<PB0;                  //Buzzer
    DDRA |= 1<<PA2;                  //LED
    ADCSRA |= (1<<ADEN);             //Enable ADC, AD-Wandlung wird aktiviert
    ADCSRA |= (1<<ADPS1)|(1<<ADPS0); //Einstellung des Prescalers für AD-Wandlung
    while(1)
    {
        PORTA |= 1<<PA2;             //LED einschalten
        _delay_ms(100);
        anwert = getADC();
        _delay_ms(1000);
        PORTA &= ~(1<<PA2);          //LED ausschalten
        _delay_ms(100);
        auswert = getADC();
        _delay_ms(1000);
        if((auswert-anwert) < 20)
            alarm();
    }
}

Der Mikrocontroller misst die Lichtstärke


Hier ist die Schaltung:

Das Programm:

#define rechts 0xDF      //Ansteuerung der rechten 7-Segment-Anzeige (Einer)
#define links 0xBF       //Ansteuerung der linken 7-Segment-Anzeige (Zehner)
#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

int main(void)
{
    int pc[10]={63,6,91,79,102,109,125,7,127,111}; //Codierung der 7-Segment-Anzeigen
    //pc bedeutet Port C
    DDRC = 0xFF;    //Port C als Ausgang definieren für die 7-Segment-Anzeige
    DDRD = 0x60;    //Port D schaltet zwischen den beiden 7-Segment-Anzeigen um
    ADCSRA |= (1<<ADEN);             //Enable ADC, AD-Wandlung wird aktiviert
    ADCSRA |= (1<<ADPS1)|(1<<ADPS0); //Einstellung des Prescalers für AD-Wandlung
    uint32_t zahl = 0; //Zahl muss groß genug sein, um den Wert 1024 * 99 aufzunehmen!
    uint32_t l=0, r=0, wert;
    while(1)
    {
        wert=0;
        for(int i=0; i<8; i++)
        {
            ADCSRA |= (1<<ADSC);     //AD-Wandlung auslösen
            while(ADCSRA&(1<<ADSC)); //Warten auf das Ergebnis der AD-Wandlung
            zahl = ADCL + (ADCH<<8); //Ergebnis der Wandlung in zahl laden
            zahl = 99 * zahl;
            zahl >>= 10;             //zahl durch 1024 teilen
            wert += zahl;
        }
        wert >>= 3;        //Division durch 8
        l= wert / 10;            //linke Anzeige
        r= wert % 10;            //rechte Anzeige
        for(int k=0; k<5; k++)
        {
            PORTD = rechts;    //rechte Anzeige wird angesteuert
            PORTC = pc[r];
            _delay_ms(10);     //10 ms Verzögerung ist ausreichend
                               //für flackerfreie Anzeige
            PORTD = links;     //linke Anzeige wird angesteuert
            PORTC = pc[l];
            _delay_ms(10);
        }
    }
}
Mithilfe eines Photowiderstandes wird die Lichtstärke gemessen und über die beiden Siebensegmentanzeigen angezeigt. Je dunkler es ist, umso größer ist die angezeigte Zahl. Bei Tageslicht wird etwa 5 bis 10 angezeigt. Bei Dunkelheit wird ein Wert bis 99 angezeigt.

Der Mikrocontroller zählt Tastendrücke


Hier ist die Schaltung:

Bei Betätigen von Taster 1 zaehlt der Mikrocontroller die Siebensegmentanzeigen hoch, bei Betätigen von Taster 2 wird auf Null zurückgestellt. Das Ganze erfolgt über zwei Interrupts.
Das Programm:

#define rechts 0xDF      //Ansteuerung der rechten 7-Segment-Anzeige (Einer)
#define links 0xBF       //Ansteuerung der linken 7-Segment-Anzeige (Zehner)
#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>
#include <avr/interrupt.h>

uint8_t zahl = 0;       //die darzustellende Zahl
int pc[10]={63,6,91,79,102,109,125,7,127,111}; //Codierung der 7-Segment-Anzeigen

ISR(INT2_vect)
{
    zahl++;     
}

ISR(INT1_vect)
{
    zahl=0;
}

int main(void)
{
    DDRC = 0xFF;     //Port C als Ausgang definieren für die 7-Segment-Anzeige
    DDRD = 0x60;     //Port D schaltet zwischen den beiden 7-Segment-Anzeigen um
                     //und ermöglicht INT1.
    uint8_t l=0, r=0;
    PORTB |= 1<<PB2;   //Pull-Up-Widerstand an PB2 (INT2) einschalten
    PORTD |= 1<<PD3;   //Pull-Up-Widerstand an PD3 (INT1) einschalten
    GICR |= 1<<INT2;   //Interrupt durch INT2 ermöglichen
                            //ISC2 im Register MCUCSR ist 0 --> fallende Flanke von INT2
                            //löst Interrupt aus
    GICR |= 1<<INT1;   //Interrupt durch INT1 ermöglichen
    MCUCR |= 1<<ISC11; //fallende Flanke von INT1 löst Interrupt aus
    sei();                   //Interrupts freigeben
    while(1)
    {
        l= zahl / 10;            //linke Anzeige
        r= zahl % 10;            //rechte Anzeige
        for(int k=0; k<5; k++)
        {
            PORTD = rechts;    //rechte Anzeige wird angesteuert
            PORTC = pc[r];
            _delay_ms(10);     //10 ms Verzögerung ist ausreichend
                               //für flackerfreie Anzeige
            if(l==0)
                PORTD = 0xff;  //wenn zahl < 10 bleibt die linke Anzeige dunkel
            else				   
                PORTD = links;  //linke Anzeige wird angesteuert
            PORTC = pc[l];
            _delay_ms(10);
        }
    }
}

Man verwendet kein goto, aber...


...manchmal lässt es sich nicht vermeiden. Oder doch? Mein Problem war folgendes: ich wollte mit einem Taster zwischen zwei Codeabschnitten umschalten, d. h. bei jedem Tastendruck sollte auf den jeweils anderen Codeabschnitt gesprungen werden. Die beiden Codeabschnitte sind der Einfachheit halber schnelles und langsames Blinken einer LED. (Später will ich zwischen zwei Liedern wechseln). Klar, dass man hier mit Interrupt arbeiten muss. In der Interruptroutine muss dann die Umschaltung erfolgen. Erster Gedanke: mit goto arbeiten. Goto geht aber nicht, weil damit nur Sprünge innerhalb einer Funktion möglich sind. Ich wollte aber von der Interruptroutine in die Main-Funktion springen.
Im Internet stieß ich auf die Header-Datei setjmp.h, die solche Sprünge über Funktionsgrenzen ermöglicht.
setjmp.h beinhaltet zwei Funktionen, setjmp() und longjmp(). setjmp() stellt das Sprungziel bereit und ermöglicht das Retten des Stacks für den Sprung. longjmp() vollführt dann den Sprung zu dem angegebenen Sprungziel.
Das Programm funktioniert folgendermaßen: zunächst muss die Header-Datei setjmp.h eingebunden werden. Dann werden zwei Variablen vom Typ jmp_buf vereinbart, die beiden Sprungziele. In der Main-Funktion markieren setjmp(lied1) und setjmp(lied2) die beiden Anfänge der Codeabschnitte, d. h. die beiden Sprungziele. Nun kann man mit longjmp(lied1, 1) und longjmp(lied2, 2) aus der Interruptroutine zu den beiden Codeabschnitten springen. Die 1 und die 2 in der Parameterliste von longjmp haben in meinem Programm keine Bedeutung, es muss aber jeweils ein int-Wert mit in der Parameterliste angegeben werden, sonst meckert der Compiler.
Wichtig ist noch, dass die beiden Sprungziele in der main-Funktion mindestens einmal durchlaufen werden müssen, sonst erfolgt kein Sprung.
Das ganze Programm:

#define F_CPU 1000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>
#include <setjmp.h>

jmp_buf lied1,lied2;
volatile int dst = 1;

ISR(INT2_vect)
{
    if(dst==1)				//Sprung zum jeweils anderen Codeabschnitt ("Lied")
        longjmp(lied2, 2);
    if(dst==2)
        longjmp(lied1, 1);
}

int main(void)
{
    cli();
    DDRA = 0xFF;           //Port A als Ausgang definieren
    PORTB |= (1<<PB2);     //Pull-up-Widerstand einschalten an PB2
    GICR |= (1<<INT2);     // General Interrupt Control register
    MCUCSR &= ~(1<<ISC2);  // fallende Flanke von INT2 löst Interrupt aus
    sei();
    while(1)
    {
        //Lied1
        setjmp(lied1);    //schnelles Blinken, die LED hängt an PA6
        dst = 1;
        for (int i=0; i<8; i++)
        {
            PORTA |= (1<<PA6);
            _delay_ms(200);
            PORTA &= ~(1<<PA6);
            _delay_ms(200);
        }
        //Lied2
        setjmp(lied2);   //langsames Blinken
        dst = 2;
        for(int i=0;i<8;i++)
        {
            PORTA |= (1<<PA6);
            _delay_ms(1000);
            PORTA &= ~(1<<PA6);
            _delay_ms(1000);
        }
    }
}

Eine einfache Stoppuhr mit 7-Segment-Anzeige


Mit zwei 7-Segment-Anzeigen wird eine einfache Stoppuhr aufgebaut. Als Steuerelement dient ein Taster. Einmal drücken: die Uhr läuft los, wieder drücken: die Stoppuhr zeigt die Zeit an, ein drittes Mal drücken: die Stoppuhr wird wieder auf Null gestellt. Die Steuerung erfolgt über Interrupt. Die Stoppuhr zeigt fast genau die Sekunden an.
Dieses Programm wurde im Wesentlichen von
25mmhg geschrieben. Folgen Sie ihm auf Twitter!

#define rechts 0xDF
#define links 0xBF
#define F_CPU 1000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>

volatile uint8_t run = 0;

ISR(INT2_vect)
{
    run++;
    if (run == 3) run = 0;
}

int main(void)
{
    cli();
    int pc[10]={63,6,91,79,102,109,125,7,127,111}; //Codierung der 7-Segment-Anzeigen
    int wertlinks = 0;
    int wertrechts = 0;
	
    //pc bedeutet Port C
    DDRC = 0xFF;    //Port C als Ausgang definieren für die 7-Segment-Anzeige
    DDRD = 0x60;    //Port D schaltet zwischen den beiden 7-Segment-Anzeigen um
    PORTB |= (1<<PB2);    //Pull-up-Widerstand einschalten
    GICR |= (1<<INT2);     // General Interrupt Control register
    MCUCSR &= ~(1<<ISC2);  // fallende Flanke von INT2 löst Interrupt aus
    sei();
    while(1)
    {
        if(run == 0)
        {
            wertlinks = 0;
            wertrechts = 0;
            PORTD = rechts;  //rechte Anzeige
            PORTC = pc[wertrechts];
            _delay_ms(5);
            PORTD = links;  //linke Anzeige
            PORTC = pc[wertlinks];
            _delay_ms(5);
        }
        //if (!(PINB &(1<<PB2)))         //Taster gedrückt
        else if(run == 1)
        {
            for(int j=0; j<10; j++)
            {
                if(run != 1) break;
                for(int i=0; i<10; i++)
                {
                    if(run != 1) break;
                    for(int k=0; k<100; k++)
                    {
                        PORTD = rechts;  //rechte Anzeige
                        PORTC = pc[i];
                        _delay_ms(5);
                        PORTD = links;   //linke Anzeige
                        PORTC = pc[j];
                        _delay_ms(5);
                        wertrechts = i;
                        wertlinks  = j;
                    }
                }
            }
        }
        else
        {
            PORTD = rechts;  //rechte Anzeige
            PORTC = pc[wertrechts];
            _delay_ms(5);
            PORTD = links;  //linke Anzeige
            PORTC = pc[wertlinks];
            _delay_ms(5);
        }
    }
}

Der Mikrocontroller spielt ein Lied und zeigt die Noten an


Das Programm ist fast das gleiche, wie eben. Das folgende Bild zeigt die Verschaltung mit der Siebensegmentanzeige:

Es werden die Noten mit der Siebensegmentanzeige angezeigt.

//Das Programm spielt das Lied "Auf, auf zum fröhlichen Jagen".
//Die Töne werden mit dem Timer0 erzeugt.
//Die Töne werden mit einer 7-Segment-Anzeige angezeigt.

#define F_CPU 1000000UL
#define notec 239  //dies sind die Werte des Output Compare Registers
#define noted 213  //für die einzelnen Noten
#define notee 190
#define notef 179
#define noteg 159
#define notea 142
#define noteb 134
#define noteC 119

#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

void ton(uint16_t periode, uint8_t dauer)  //Funktion spielt einen Ton
{
    TCCR0 &= ~(1<<CS01);  //Timer kurz abschalten
    _delay_ms(10);        //wegen zweier benachbarter Töne mit gleicher Tonhöhe
    TCCR0 |= 1<<CS01;     //Timer einschalten
    OCR0 = periode;
    if (periode==239) PORTC = 88;  //Codierung der 7-Segment-Anzeige
    if (periode==213) PORTC = 94;
    if (periode==190) PORTC = 121;
    if (periode==179) PORTC = 113;
    if (periode==159) PORTC = 125;
    if (periode==142) PORTC = 119;
    if (periode==134) PORTC = 124;
    if (periode==119) PORTC = 57;
	
    for(int i=0; i<dauer; i++)
    _delay_ms(100);   //_delay_ms braucht als Argument eine Konstante
    //deswegen die for-Schleife
}

void pause(uint8_t dauer) //Funktion macht eine Pause
{
    TCCR0 &= ~(1<<CS01);  //Timer abschalten
    PORTC = 0;
    for(int i=0; i<dauer; i++)   //siehe oben
    _delay_ms(100);
}

int main(void)
{
    DDRC = 0xFF;        //Port C als Ausgang schalten
    DDRB |= 1<<PB3;     //OC0 als Ausgang schalten
    TCCR0 |= 1<<COM00;  //toggle OC0 on compare match
    TCCR0 |= 1<<CS01;   //prescaler 8
    TCCR0 |= 1<<WGM01;  //clear timer on compare match
	
    while(1)
    {
		//gegliedert nach Takten
        //1 Zeile ist jeweils 1 Takt
        //notec,1 bedeutet z. B. die Note c als Sechzehntelnote gespielt
        //noteb,2 bedeutet z. B. die Note b als Achtelnote gespielt
        ton(notec,4);
        ton(notef,6); ton(notec,2); ton(notef,4); ton(notea,2);ton(notea,2);
        ton(notef,8); ton(notec,4); ton(notef,2); ton(notea,2);
        ton(noteg,4); ton(notec,4); ton(notec,4); ton(noted,2);ton(notee,2);
        ton(notef,8); pause(4);     ton(notec,4);
        ton(notef,6); ton(notec,2); ton(notef,4); ton(notea,2);ton(notea,2);
        ton(notef,8); ton(notec,4); ton(notef,2); ton(notea,2);
        ton(noteg,4); ton(notec,4); ton(notec,4); ton(noted,2);ton(notee,2);
		
        ton(notef,8); pause(4);     ton(notee,3); ton(notef,1);
        ton(noteg,4); ton(notec,4); ton(notec,4); ton(notef,2);ton(noteg,2);
        ton(notea,8); ton(notef,4); ton(notea,4);
        ton(noteb,4); ton(notea,4); ton(noteg,4); ton(notef,4);
        ton(noteg,12); ton(notec,4);
        ton(notef,6); ton(notec,2); ton(notef,4); ton(notea,4);
        ton(notef,8); ton(notec,4); ton(notef,2); ton(notea,2);
        ton(noteg,4); ton(notec,4); ton(notec,4); ton(noted,2); ton(notee,2);
        ton(notef,8); pause(4);     ton(notee,3); ton(notef,1);
        ton(noteg,8); ton(notec,4); ton(noteC,4);
        ton(notea,8); ton(notef,4); ton(notea,4);
        ton(noteg,4); ton(noteC,4); ton(noteg,4); ton(noteC,4);
        ton(notef,4); ton(notef,3); ton(notea,1); ton(noteg,8);
        ton(notef,8); ton(notec,4); ton(notea,4);
        ton(notef,8); ton(notec,4); ton(notea,4);
        ton(noteg,4); ton(noteC,4); ton(noteg,4); ton(notea,3); ton(noteg,1);
        ton(notef,12);
    }
}

Der Mikrocontroller spielt ein Lied, aber diesmal richtig!


Weiter
unten haben wir schon einmal ein Lied gespielt, aber auf eine andere Weise. Diesmal setzen wir den Timer 0 ein. Damit kann man relativ genau eine bestimmte Frequenz erzeugen.

#define F_CPU 1000000UL
#define notec 239  //dies sind die Werte des Output Compare Registers
#define noted 213  //für die einzelnen Noten
#define notee 190
#define notef 179
#define noteg 159
#define notea 142
#define noteb 134
#define noteC 119

#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

void ton(uint16_t periode, uint8_t dauer)  //Funktion spielt einen Ton
{
	TCCR0 &= ~(1<<CS01);  //Timer kurz abschalten
	_delay_ms(10);              //wegen zweier benachbarter Töne mit gleicher Tonhöhe
	TCCR0 |= 1<<CS01;     //Timer einschalten
	OCR0 = periode;    
	for(int i=0; i<dauer; i++)
		_delay_ms(100);   //_delay_ms braucht als Argument eine Konstante
		                  //deswegen die for-Schleife
}

void pause(uint8_t dauer) //Funktion macht eine Pause
{
	TCCR0 &= ~(1<<CS01);  //Timer abschalten
	for(int i=0; i<dauer; i++)   //siehe oben
		_delay_ms(100);
}

int main(void)
{
	DDRB |= 1<<PB3;     //OC0 als Ausgang schalten
	TCCR0 |= 1<<COM00;  //toggle OC0 on compare match
	TCCR0 |= 1<<CS01;   //prescaler 8
	TCCR0 |= 1<<WGM01;  //clear timer on compare match
	
    while(1)
    {
		//gegliedert nach Takten
		//1 Zeile ist jeweils 1 Takt
		//notec,1 bedeutet z. B. die Note c als Sechzehntelnote gespielt
		//noteb,2 bedeutet z. B. die Note b als Achtelnote gespielt
		ton(notec,4);
		ton(notef,6); ton(notec,2); ton(notef,4); ton(notea,2);ton(notea,2);
		ton(notef,8); ton(notec,4); ton(notef,2); ton(notea,2);
		ton(noteg,4); ton(notec,4); ton(notec,4); ton(noted,2);ton(notee,2);
		ton(notef,8); pause(4);     ton(notec,4);
		ton(notef,6); ton(notec,2); ton(notef,4); ton(notea,2);ton(notea,2);
		ton(notef,8); ton(notec,4); ton(notef,2); ton(notea,2);
		ton(noteg,4); ton(notec,4); ton(notec,4); ton(noted,2);ton(notee,2);
		        
		ton(notef,8); pause(4);     ton(notee,3); ton(notef,1);
		ton(noteg,4); ton(notec,4); ton(notec,4); ton(notef,2);ton(noteg,2);
		ton(notea,8); ton(notef,4); ton(notea,4);
		ton(noteb,4); ton(notea,4); ton(noteg,4); ton(notef,4);
		ton(noteg,12); ton(notec,4);
		ton(notef,6); ton(notec,2); ton(notef,4); ton(notea,4);
		ton(notef,8); ton(notec,4); ton(notef,2); ton(notea,2);
		ton(noteg,4); ton(notec,4); ton(notec,4); ton(noted,2); ton(notee,2);
		ton(notef,8); pause(4);     ton(notee,3); ton(notef,1);
		ton(noteg,8); ton(notec,4); ton(noteC,4);
		ton(notea,8); ton(notef,4); ton(notea,4);
		ton(noteg,4); ton(noteC,4); ton(noteg,4); ton(noteC,4);
		ton(notef,4); ton(notef,3); ton(notea,1); ton(noteg,8);
		ton(notef,8); ton(notec,4); ton(notea,4);
		ton(notef,8); ton(notec,4); ton(notea,4);
		ton(noteg,4); ton(noteC,4); ton(noteg,4); ton(notea,3); ton(noteg,1);
		ton(notef,12);
    }
}

Eine Tonleiter wird mit einem Timer erzeugt



#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
	DDRB |= 1<<PB3;       //OC0 als Ausgang schalten
	TCCR0 |= 1<<COM00;    //Toggle OC0 on compare match
	TCCR0 |= 1<<CS01;     //Prescaler 8
	TCCR0 |= 1<<WGM01;    //Clear Timer on Compare Match (CTC)
    while(1)
    {
        OCR0 = 239;
        _delay_ms(1000);
        OCR0 = 213;
        _delay_ms(1000);
        OCR0 = 190;
        _delay_ms(1000);
        OCR0 = 179;
        _delay_ms(1000);
        OCR0 = 159;
        _delay_ms(1000);
        OCR0 = 142;
        _delay_ms(1000);
        OCR0 = 126;
        _delay_ms(1000);
        OCR0 = 119;
        _delay_ms(1000);
    }
}

Zwei 7-Segment-Anzeigen zeigen eine Spannung an


Diese Schaltung ist genau wie die vorherige, nur dass an ADC0 mittels eines Potis eine Spannung anliegt, die mit Analog-Digitalwandlung ausgewertet wird. Die beiden 7-Segment-Anzeigen zeigen den Relativwert der Spannnung an, also statt 0 bis 5 V 00 bis 99.

#define rechts 0xDF      //Ansteuerung der rechten 7-Segment-Anzeige (Einer)
#define links 0xBF       //Ansteuerung der linken 7-Segment-Anzeige (Zehner)
#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

int main(void)
{
   int pc[10]={63,6,91,79,102,109,125,7,127,111}; //Codierung der 7-Segment-Anzeigen
                                                  //pc bedeutet Port C
   DDRC = 0xFF;    //Port C als Ausgang definieren für die 7-Segment-Anzeige
   DDRD = 0x60;    //Port D schaltet zwischen den beiden 7-Segment-Anzeigen um
   ADCSRA |= (1<<ADEN);   //Enable ADC, AD-Wandlung wird aktiviert
   ADCSRA |= (1<<ADPS1)|(1<<ADPS0); //Einstellung des Prescalers für AD-Wandlung
   uint32_t zahl = 0; //Zahl muss groß genug sein, um den Wert 1024 * 99 aufzunehmen!
   uint32_t l=0, r=0;
   while(1)
   {
      ADCSRA |= (1<<ADSC);     //AD-Wandlung auslösen
      while(ADCSRA&(1<<ADSC)); //Warten auf das Ergebnis der AD-Wandlung
      zahl = ADCL + (ADCH<<8); //Ergebnis der Wandlung in zahl laden
      zahl = 99 * zahl / 1023;
      l= zahl / 10;            //linke Anzeige
      r= zahl % 10;            //rechte Anzeige
      for(int k=0; k<5; k++)
      {
         PORTD = rechts;    //rechte Anzeige wird angesteuert
         PORTC = pc[r];
         _delay_ms(10);     //10 ms Verzögerung ist ausreichend
                            //für flackerfreie Anzeige
         PORTD = links;     //linke Anzeige wird angesteuert
         PORTC = pc[l];
         _delay_ms(10);
       }
    }
}

Zwei 7-Segment-Anzeigen im Multiplexbetrieb


Das Programm bewirkt eine Ansteuerung zweier 7-Segment-Anzeigen im Multiplex-Betrieb. Das folgende Bild zeigt die Schaltung:

Es wird jeweils abwechselnd nur eine Anzeige angesteuert. Die Umschaltung erfolgt durch 2 Bits des Port D.
Das Umschalten erfolgt so schnell, dass es vom menschlichen Auge nicht wahrgenommen wird.
Beide Anzeigen zusammen zählen in einer Endlosschleife immer von 00 bis 99.
Die rechte Anzeige zählt die Einer, die linke die Zehner. Das wird durch 2 verschachtelte for-Schleifen bewirkt.

#define rechts 0xDF
#define links 0xBF
#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

int main(void)
{
	int pc[10]={63,6,91,79,102,109,125,7,127,111}; //Codierung der 7-Segment-Anzeigen
		                                           //pc bedeutet Port C
	DDRC = 0xFF;    //Port C als Ausgang definieren für die 7-Segment-Anzeige
	DDRD = 0x60;    //Port D schaltet zwischen den beiden 7-Segment-Anzeigen um
	while(1)
	{
		for(int j=0; j<10; j++)
			for(int i=0; i<10; i++)
			{
				for(int k=0; k<10; k++)
				{
					
					PORTD = rechts;  //rechte Anzeige
					PORTC = pc[i];
					_delay_ms(10);	 //10 ms Verzögerung ist ausreichend 
					                 //für flackerfreie Anzeige
					PORTD = links;   //linke Anzeige
					PORTC = pc[j];
					_delay_ms(10);
				}
			}
	}
}

Die 7-Segment-Anzeige zeigt Zahlen


Die 7-Segment-Anzeige wird so angesteuert, dass sie nacheinander die Zahlen von 0 bis 9 zeigt.

#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>
//Das Programm steuert eine 7-Segment-Anzeige an und 
//zeigt die Zahlen von 0 bis 9
int main(void)
{
	DDRC = 0xFF;    //Port C als Ausgang definieren für die 7Segment-Anzeige
	while(1)
	{
		PORTC=63;
		_delay_ms(500);
		PORTC=6;
		_delay_ms(500);
		PORTC=91;
		_delay_ms(500);
		PORTC=79;
		_delay_ms(500);
		PORTC=102;
		_delay_ms(500);
		PORTC=109;
		_delay_ms(500);
		PORTC=125;
		_delay_ms(500);
		PORTC=7;
		_delay_ms(500);
		PORTC=127;
		_delay_ms(500);
		PORTC=111;
		_delay_ms(500);
	}
}

Ansteuerung einer 7-Segment-Anzeige


Die 7-Segment-Anzeige wird so angesteuert, dass alle Segmente nacheinander leuchten. Es sind in Wirklichkeit 8 Segmente (mit dem Dezimalpunkt). Bei Drücken eines Tasters, der an PA0 gegen Masse angeschlossen ist, wird die Reihenfolge der Segmente umgekehrt.

#define F_CPU 1000000UL
#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>
int main(void)
{
	DDRC = 0xFF;    //Port C als Ausgang definieren für die 7Segment-Anzeige
    while(1)
    {                           //Taster an PA0 gegen Masse
		PORTA |= (1<<PA0);      // Pull-up-Widerstand einschalten
		if (PINA & (1<<PA0))
		{	//Normalzustand: Taster ist offen
			PORTC = 0x01;
			_delay_ms(50);     
			for(uint8_t i=0; i<7; i++)
			{
				PORTC <<= 1;      //Reihenfolge a,b,c,...
				_delay_ms(50);
			}
		}
		else
		{   //Taster ist geschlossen
			PORTC = 0x80;
			_delay_ms(50);
			for(uint8_t i=0; i<7; i++)
			{
				PORTC >>= 1;       //umgekehrte Reihenfolge: h,g,...
				_delay_ms(50);
			}
		}
    }
}

Wie erzeugt man eine bestimmte Frequenz?


Als erstes könnte man annehmen, man muss mit delay arbeiten. Im folgenden Programm wollen wir eine Frequnz von 1 KHz erzeugen. Dazu wird eine Pause von 500 Mikrosekunden gemacht.

#define F_CPU 1000000UL
#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>

int main(void)
{
    DDRB |= 1<<PB3;  // PB3 als Ausgang schalten
    while(1)
    {
		PORTB ^= 1<<PB3;  //Toggeln von PB0
		_delay_us(500);
    }
}
Die erzeugte Frequenz ist geringfügig kleiner als 1 kHz.
Eine andere Methode ist die Verwendung von Timern. Auch dazu ein Beispiel:

#define F_CPU 1000000UL
#include <avr/io.h>
#include <stdint.h>

int main(void)
{
	DDRB  |= 1<<PB3;   //OC0 als Ausgang schalten 
	TCCR0 |= 1<<WGM01; //Clear Timer on Compare Mode des Timers 0
	TCCR0 |= 1<<COM00; //Toggle OC0 on Compare Match
	TCCR0 |= 1<<CS00;  //no prescaling
	OCR0 = 255; // heraus kommt etwa eine Frequenz von 1953 Hz
	while(1)
	{
	}
}
Man kann so auch ein Programm als Hoertest schreiben, bei dem die Frequenz nach und nach erhöht wird. Das erreicht man durch Dekrementieren des Registers OCR0:

#define F_CPU 1000000UL
#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>

int main(void)
{
	DDRB  |= 1<<PB3;   //OC0 als Ausgang
	TCCR0 |= 1<<WGM01; //Clear Timer on Compare Mode des Timers 0
	TCCR0 |= 1<<COM00; //Toggle OC0 on Compare Match
	TCCR0 |= 1<<CS00;  //no Prescaling
	while(1)
	{ 
		OCR0=255;
		while(OCR0>0)
		{
			_delay_ms(200);
			OCR0--;
		}
	}
}

Schlafmodus für den ATmega32


Der ATmega32 wird schlafen gelegt. Durch die Betätigung des Tasters an INT2 wird der Controller aufgeweckt. In der Interruptserviceroutine wird einmal die Lichterkette durchlaufen, danach wird der Controller wieder schlafen gelegt.
Die C-Datei


Die Lichterkette als 16-Bit-Zähler


Die Lichterkette aus LEDs wird hier als 16-Bit-Zaehler verwendet.
Die C-Datei


PWM mit Timer0 und logarithmischer Ausgabe


Dieses Programm funktioniert wie das vorherige, nur wird das Pin OC0 logarithmisch angesteuert. Bei einer linearen Ansteuerung wie im vorigen Programm sieht man das "Pulsieren" der LED an OC0 nicht so gut. Die logarithmische Ansteuerung wird mit einem Feld erreicht, in dem Werte stehen, die exponentiell ansteigen.
Die C-Datei


PWM mit Timer0


Der Timer0 steuert eine Pulsweitenmodulation (PWM). Es wird das Pin OC0 (d. h. PortB 3) als Ausgang der PWM verwendet. Quasi parallel dazu wird das laufende Licht in der Lichterkette an PortC und PortD gesteuert. Der Timer0 läuft unabhängig davon und zählt aufwärts von 0 bis 255. Dabei wird der Wert des Timers mit dem des Output Compare Registers verglichen. Sind beide Werte gleich, wird der Ausgang OC0 auf Low gesetzt. Erreicht der Timer0 den Wert 255, wird er auf Null gesetzt und gleichzeitig OC0 auf High gesetzt. Somit kann eine Pulsweitenmodulation des Pins OC0 erreicht werden. Wenn der Timer0 den Höchstwert 255 erreicht, wird ein Overflow-Interrupt ausgelöst. In der zugehörigen Interrupt-Serviceroutine wird der Wert des Output Compare Registers angepasst, so dass eine "atmende" Animation am Pin OC0 zustandekommt. Zusätzlich wird der Prescaler 64 verwendet, damit die PWM nicht zu schnell wird.
Die C-Datei


Timer1 Overflow Interrupt


Ein einfaches Programm, das den Timer1 verwendet. Er wird mit Prescaler 256 getaktet. Der Timer1 ist ein 16-Bit-Timer, kann also 65536 Zustände darstellen. Die Taktrate des Mikrocontrollers beträgt 1 MHz. Daraus ergibt sich, dass etwa alle 16 Sekunden ein Interrupt ausgelöst wird. (256*65536 = 16777216)
Die C-Datei


"Auf auf zum fröhlichen Jagen" auf dem ATmega32


Der ATmega32 spielt das Lied "Auf auf zum fröhlichen Jagen". In der Lichterkette der LEDs wird jeweils der Ton, der gerade gespielt wird, angezeigt.
Die C-Datei


Der laufende "Tropfen"


Wie ein Tropfen, der unten dicker als oben ist, läuft ein Licht aus drei LEDs durch die LED-Kette. Dabei ist die vorderste LED am hellsten, die mittlere dunkler und die letzte noch dunkler. Die unterschiedliche Helligkeit wird mit Pulsweitenmodulation erreicht. Ich habe dafür vier Funktionen programmiert, je eine für die Ports D und C und zwei für den Übergang von Port D nach Port C.
Die C-Datei


"Zufallszahlen" mit AD-Wandlung erzeugen und in EEPROM schreiben


Mithilfe der AD-Wandlung werden wie beim vorigen Programm Zufallszahlen erzeugt. Sie werden in den EEPROM geschrieben. Der ATmega32 hat viermal so viel EEPROM-Speicherplatz wie der ATtiny45, nämlich 1024 Bytes.
Mit avrdude kann nun der EEPROM-Inhalt in eine csv-Datei geschrieben werden:
avrdude -c usbtiny -p m32 -U eeprom:r:datei.csv:d
Die einzelnen Parameter des avrdude-Befehls bedeuten:
Es wird der usbtiny-Programmer verwendet, p bedeutet part ATmega32, -U bedeutet eine Speicheroperation, es geht um den EEPROM, r bedeutet read (lesen), es folgt der Name der Datei, in die die Werte geschrieben werden, d bedeutet dezimal, d. h. das Format, in dem die Werte ausgelesen werden.
Zunächst habe ich den Eingang für die AD-Wandlung "in der Luft" hängen lassen. Bei der Auswertung stellt sich heraus, dass es sich keinesfalls um Zufallszahlen handelt. Es gibt Häufungen bei bestimmten Werten:

Sortiert man die Werte, ergibt sich folgendes Bild:

Daraufhin habe ich den Eingang für die AD-Wandlung an ein Poti gehängt. Das Ergebnis sieht so aus:

Auch hier noch das Bild bei sortierten Werten:

Die C-Datei


Programm "Lichterkette stellt Zufallszahlen dar"


Die Lichterkette stellt Zufallszahlen dar, die mit AD-Wandlung gewonnen wurden. Weiter oben ist ein ähnliches Programm beschrieben. Das Programm erzeugt mit AD-Wandlung zufällig Bits und fügt sie zu einer 16-Bit-Zahl zusammen.
Die C-Datei


Programm "Lichterkette stellt Ergebnis der AD-Wandlung dar"


Die Lichterkette stellt das Ergebnis der AD-Wandlungen als Kette von 10 LEDs dar (entsprechend der 10 Bits als Ergebnis der AD-Wandlung). Es werden dafür einfach die beiden Register, in denen das Ergebnis der AD-Wandlung liegt (ADCL und ADCH), in die PORT-Register von Port D und Port C kopiert.
Die C-Datei


Programm "Lichterkette mit frei laufender AD-Wandlung"


Wieder eine Lichterkette mit AD-Wandlung wie vorher, nur erzeugt der AD-Wandler ständig Wandlungen. Jedes Mal, wenn eine Wandlung erfolgt ist, wird ein Interrupt ausgelöst und die Variable "zeit" entsprechend der AD-Wandlung verändert. Dadurch kann eine Änderung der Geschwindigkeit des Lauflichtes zu jedem Zeitpunkt erfolgen und nicht nur am Anfang der Lichterkette wie im vorigen Programm. Weitere Erläuterungen sind in der C-Datei.
Die C-Datei


Programm "Lichterkette mit AD-Wandlung"


Dieses Programm funktioniert wie das vorherige, nur wird die Geschwindigkeit des Lauflichtes mit einem Potentiometer und AD-Wandlung eingestellt.
Die C-Datei


Programm "Lichterkette"


Ich habe mir eine Lichterkette aus 16 LEDs gebaut, die alle an den ATmega32 angeschlossen sind. Das folgende Bild zeigt die Schaltung:


Ein Licht läuft durch die Kette von 16 LEDs vorwärts und rückwärts. Bitschiebeoperatoren sorgen dafür, dass die richtigen Bits in PORTD und PORTC angesprochen werden.
Die C-Datei


Ein merkwürdiger Effekt


Beim Programmieren ist zu beachten, dass uint8_t-Variablen, die Null sind, beim weiteren Dekrementieren auf 255 springen. Betrachten Sie folgenden C-Code:
for(uint8_t i=15; i>=0; i--)
{
// Schleifenbefehle
}
Man könnte denken, wenn i gleich Null ist, wird die for-Schleife verlassen. Weit gefehlt, denn wenn i gleich Null ist, springt i beim nächsten Dekrementieren auf 255. Die Schleife wird erneut durchlaufen, da ja 255 größer als Null ist. Dadurch entsteht eine Endlosschleife, da i bis Null heruntergezählt wird und dann wieder auf 255 springt.


Programm "Ampelschaltung"


Ich habe mir einen ATmega32 zugelegt. Die folgenden Programme werden mit dem ATmega32 gebaut, der viel mehr Anschlüsse hat als der ATtiny45.
Dieses Programm simuliert die Schaltung einer Fußgängerampel. Bei Drücken des Tasters, der an PD2 hängt, wird die Umschaltung ausgelöst. Früher habe ich ein ähnliches Projekt mit einem Arduino geschrieben. Das folgende Bild zeigt das Steckbrett mit der Ampelschaltung:


Die C-Datei


Programm "Komparator"


Das Programm verwendet den analogen Komparator des Mikrocontrollers. Ein Spannungsteiler mit einer Spannung von etwa 2,5 V dient als negativer Eingang des Komparators. Der positive Eingang des Komparators wird mit dem Ausgang eines Potis beschaltet. Damit wird durch Drehen des Potis ein Umschalten des Komparatorausgangs erreicht. Normalerweise blinkt die LED an PB3 langsam. Wenn der Komparator umschaltet, wird ein Interrupt ausgelöst und die LED blinkt fünfmal schnell. Das folgende Bild zeigt die Beschaltung des Komparators:


Ohne die Beschaltung mit den (roten) Kapazitäten gibt es einen breiten Bereich des Potis um 2,5 V, in dem ständig Interrupts durch den Komparator ausgelöst werden. Mit den Kapazitäten werden nur in einem schmalen Bereich um 2,5 V des Potis Interrupts ausgelöst.
Die Idee mit den Kapazitäten stammt von
25mmhg.
Die C-Datei


Programm "Sirene" in Module zerlegt


Bisher haben wir ein Projekt immer mit einer einzigen C-Datei geschrieben. Bei umfangreichen Projekten ist es aber hilfreich, das Projekt in mehrere Module bzw. Dateien aufzuteilen. Wenn man zum Beispiel viele Funktionen hat, ist es günstig, für ein oder mehrere Funktionen eigene C- oder Headerdateien vorzusehen. Wenn an einer einzigen Funktion etwas verändert werden soll, muss dann nur die entsprechende h- oder C-Datei neu kompiliert werden und nicht das ganze Projekt. Als einfaches Beispiel habe ich das vorhergehende Projekt "Sirene" ausgewählt, das nun in Module geteilt werden soll. Wir zerlegen das Projekt in 3 Dateien:
Das ist das Hauptprogramm. Hier wird die funktion.h inkludiert.


Das hier unten ist ist die Headerdatei funktion.h
Sie enthält die Deklaration der Funktion pause. Durch die Präprozessor-Anweisungen #ifndef... wird verhindert, dass die Headerdatei unter Umständen mehrfach inkludiert wird.


Schließlich haben wir noch die Quellcodedatei funktion.c. Sie enthält die Definition der Funktion pause. Es müssen noch die Headerdateien delay.h (wegen _delay_us) und stdint.h (wegen uint16_t) inkludiert werden.


Um im Atmel Studio dem Projekt ein Modul hinzuzufügen, gehen wir auf den Solution Explorer (rechts), klicken mit der rechten Maustaste auf das Projekt und wählen aus dem Kontextmenü "Add -> New Item". Nun müssen wir auswählen, ob wir ein C File oder ein Include File dem Projekt hinzufügen möchten. (Ein Assembly File kommt für unsere Zwecke nicht in Frage). Wir können auch gleich einen Dateinamen festlegen.
Die folgende Übersicht zeigt, wie aus Headerdateien und Quellcodedateien eines Projektes durch den Präprozessor, Compiler und Linker schließlich eine Hex-Datei entsteht:



Programm "Sirene"


Das Programm erzeugt einen Sirenenton mit einem Piezo-Lautsprecher, der an PB3 hängt. Der an- und abschwellende Sirenenton wird abwechselnd schneller und wieder langsamer.
Die C-Datei


Programm "Morsen"


Das Programm erzeugt Morsezeichen, die über einen Piezo-Lautsprecher hörbar gemacht werden, der an PB3 hängt. Mit einem Poti, der an PB4 hängt, kann die Geschwindigkeit des Morsens (mithilfe der AD-Wandlung) eingestellt werden. Das Programm morst das Wort "pieschen" (ein Stadtteil von Dresden). Es kann in der C-Datei aber auch ein anderes Wort einfach als String angegeben werden.
Das Programm zeigt, wie man in C mit Strings umgehen kann. In einer Switch-Case-Anweisung sind die einzelnen Buchstaben des Morsealphabets kodiert, im vorliegenden Programm aber nur die Buchstaben, die in dem Wort "pieschen" vorkommen.
Die C-Datei


Ansteuerung von 3 LEDs mit 120 Grad Phasenverschiebung verbessert


Das vorherige Programm mit seinem aus Nullen und Einsen bestehendem Array schreit ja förmlich nach einer speicherplatzsparenden Implementierung mit einzelnen Bits statt mit int-Werten.
25mmhg gab mir den Tipp, wie man das Array mit Bits anlegt und auswertet.
Die C-Datei


Ansteuerung von 3 LEDs mit 120 Grad Phasenverschiebung


Mithilfe eines Feldes (Arrays) werden 3 LEDs in einer "atmenden" Animation angesteuert. Durch die Verwendung der Headerdatei pgmspace.h wird das Array nicht im sRAM angelegt (der dafür nicht ausreichend ist), sondern im Flash.
Die C-Datei


Der Mikrocontroller geht wieder schlafen und wird durch einen Hund geweckt


Diesmal wird der Mikrocontroller wieder in den Power-down-Modus versetzt, aus dem er durch einen Watchdogtimer-Überlauf geweckt wird.
Die C-Datei


Der Mikrocontroller geht nochmal schlafen


Eine verbesserte Variante des vorigen Programms stammt von
Johannes. Dabei wird beim Drücken des Tasters an PB2 ständig Interrupt ausgelöst. Das Setzen des Power Reduction Registers wie im vorigen Programm ist auch nicht nötig. Der Stromverbrauch im Power-Down-Modus beträgt etwa 0,1 Mikroampere.
Die C-Datei


Der Mikrocontroller geht schlafen


Oft ist es wünschenswert, mit einem Mikrocontroller möglichst wenig Strom zu verbrauchen, zum Beispiel in batteriebetriebenen Geräten. Es ist kein Zufall, dass das von den AVR-Controllern unterstützt wird. Dazu muss die Headerdatei sleep.h eingebunden werden. Es gibt mehrere Sleep-Modi.
Im ATtiny45 werden folgende drei Sleep-Modi unterstützt: In dem vorliegenden Programm habe ich mich für den Power-down-Modus entschieden, bei dem am wenigsten Strom verbraucht wird. Der Mikrocontroller wird in der Hauptschleife in den Schlaf-Modus versetzt. Durch einen Interrupt (Drücken eines Tasters) wird er aufgeweckt und die Interrupt-Routine wird abgearbeitet. Danach wird er wieder schlafen gelegt.
Die C-Datei


Programm "Auf auf zum fröhlichen Jagen" mit ADC


Das Programm spielt das Lied "Auf auf zum fröhlichen Jagen", wobei das Tempo mit einem Poti eingestellt werden kann. Das Poti wird mit Analog-Digital-Wandlung ausgewertet.
Die C-Datei


Programm "Zufällige Melodie"


Wie in den vorherigen Programmen werden mithilfe der AD-Wandlung Zufallszahlen erzeugt, nach denen Töne der C-Dur-Tonleiter gespielt werden.
Dabei wird das jeweils niederwertigste Bit des Ergebnisses einer AD-Wandlung genommen und aus 4 solchen Bits eine 4-Bit-Zahl zusammengesetzt. Damit kann eine (Zufalls-)Zahl zwischen 0 und 15 dargestellt werden. Nun wird damit eine von 16 Noten der C-Dur-Tonleiter gespielt.
Die C-Datei


Programm "Zufallszahlen und Watchdog Timer"


Dieses Programm arbeitet im Prinzip wie das vorherige, nur wird an Stelle des Timers 0 der Watchdog Timer verwendet. Dieser löst in regelmäßigen Abständen einen Interrupt aus. In der Interrupt-Routine wird eine kurze Melodie gespielt.
Die C-Datei


Programm "Zufallszahlen und Melodie"


Das Programm erzeugt wie die vorherigen Programme Zufallszahlen und mcht sie über einen Piezolautsprecher hörbar. Zusätzlich spielt es in regelmäßigen Abständen (ausgelöst durch den Overflow-Interrupt von Timer0) eine kurze Melodie.
Dazu wird der Timer 0 verwendet. Es ist ein 8-Bit-Timer, sein maximaler Wert beträgt 255. Weiterhin wird der maximale Prescaler verwendet, nämlich 1024. Der Timer zählt von 0 beginnend hoch bis zum Maximalwert von 255. Der Mikrocontroller hat eine Taktfrequenz von 8 MHz. Das bedeutet, dass der Timer etwa 30 mal in der Sekunde einen Overflow-Interrupt auslöst (8000000/255/1024). Das ist natürlich viel zu schnell, deshalb wird im Programm noch eine Variable verwendet, die in der Interruptroutine hochgezählt wird. Wenn die Variable gleich 600 ist, wird die Melodie gespielt, d. h. etwa alle 20 Sekunden (600/30).
Warum verwendet man überhaupt Timer? Sie haben den Vorteil, dass sie unabhängig von dem Programm, das auf dem Mikrocontroller läuft, arbeiten. Mit ihrer Hilfe lassen sich also regelmäßige Vorgänge auslösen.
Die C-Datei


Programm "Zufallszahlen in den EEPROM schreiben"


Um zu überprüfen, ob das vorherige Programm tatsächlich Zufallszahlen erzeugt, wurde dieses Programm dahingehend erweitert, dass es die erzeugten Zufallszahlen in den EEPROM schreibt.
Mit avrdude kann dann der EEPROM ausgelesen werden:
avrdude -c usbtiny -p t45 -U eeprom:r:datei.csv:d
Durch diesen avrdude-Befehl wird eine csv-Datei erzeugt (comma-separated values), die z. B. mit einem Tabellenkakulationsprogramm weiter analysiert werden kann.
Bei der Auswertung mit Works ergab sich folgende Verteilung der Werte:

Die y-Achse stellt die Zufallszahlen dar. Der maximale Wert ist 255.
Die C-Datei


Programm "Zufallszahlen mit AD-Wandlung erzeugen"


Mit dem Programm werden Zufallszahlen mithilfe der AD-Wandlung erzeugt.
Die Idee stammt von
25mmHg.
Dabei wird in einer Reihe von AD-Wandlungen jeweils das niedrigstwertige Bit (least significant bit oder lsb) ausgewertet und damit der Reihe nach eine Zahl zusammengebaut. Das niedrigstwertige Bit verändert sich am schnellsten und schwankt gewissermaßen statistisch hin und her.
Die C-Datei


Programm "Frequenz eines Piezo mit einem Poti steuern"


Das Programm ermöglicht, die Frequenz eines Piezo-Lautsprechers mit einem Poti zu verändern. Der Piezo ist an PB3 angeschlossen, das Poti an PB4. Dafür wird die Spannung des Potis mit Analog-Digital-Wandlung ausgewertet.
Die C-Datei


Programm "Dimmen einer LED mit Poti"


Das Programm ermöglicht das Dimmen einer LED mit einem Poti. Die LED ist an PB3 angeschlossen, das Poti an PB4. Das Dimmen wird durch Pulsweitenmodulation erreicht. Dafür wird die Spannung des Potis mit Analog-Digital-Wandlung ausgewertet.
Die C-Datei


Programm "Poti als Schalter"


Das Programm bewirkt, dass eine LED durch ein Poti ein- und ausgeschaltet wird. Hohe Spannung: einschalten, niedrige Spannung: ausschalten. Die LED ist an PB3 angeschlossen, das Poti an PB4. Das Ein- und Ausschalten wird durch eine Analog-Digital-Wandlung bewirkt.
Die C-Datei


Programm "Analog-Digital-Wandlung"


Das Programm bewirkt, dass die Geschwindigkeit des Blinkens einer LED mit einem Poti eingestellt werden kann. Die LED ist an PB3 angeschlossen, das Poti an PB4.
Die C-Datei


Was ist die Analog-Digital-Wandlung und wie wird sie programmiert?


Mit der Analog-Digital-Wandlung (Analog Digital Conversion oder ADC) wird eine Eingangsgröße (meist eine Spannung) in eine Zahl umgewandelt. Beim ATtiny45 wird mit einer Auflösung von 10 Bit gearbeitet. Damit kann man maximal die Zahl 1023 darstellen. Dabei wird die Eingangsspannung mit einer Referenzspannung verglichen. Beträgt die Referenzspannung zum Beispiel 5 Volt und die Eingangspannung 2,5 Volt, so wandelt die ADC die Eingangsspannung in die Zahl 512 um.
Wie wird die ADC nun programmiert? Zunächst muss die ADC aktiviert werden, indem das Enable-Bit ADEN im Register ADCSRA auf 1 gesetzt wird:
ADCSRA |= (1<<ADEN);
Nun muss dem Mikrocontroller gesagt werden, welcher Eingang und welche Referenzspannung für die ADC verwendet werden soll. Dazu muss man bestimmte Bits im ADC Multiplexer Selection Register ADMUX setzen. Hier hilft nur der Blick ins Datenblatt, das von atmel.com als PDF-Datei heruntergeladen werden kann.
Als Beispiel nehmen wir an, wir wollen das Pin PB4 als Eingang verwenden. Dann müssen wir folgenden Befehl schreiben:
ADMUX |= (1<<MUX1);
Die Referenzspannung wird über die drei Voltage Reference Selection Bits REFS0...REFS2 im Register ADMUX gesteuert. Hier wieder der Hinweis auf das Datenblatt, eine vollständige Erklärung würde hier zu weit führen. Wenn man die Betriebsspannung Vcc als Referenzspannung verwenden will, müssen die drei Bits REFS0 bis REFS2 null sein.
Nun muss noch der Prescaler für die ADC eingestellt werden, und zwar so, dass sich der Takt für den AD-Wandler zwischen 50 kHz und 200 kHz ergibt. Dazu wieder ein Beispiel: wir nehmen an, dass wir mit einer Taktfrequenz des Mikrocontrollers von 8 MHz arbeiten. Ein Prescaler von 64 würde einen Takt für den AD-Wandler von 8000/64 kHz ergeben, das heißt 125 kHz, was innerhalb des empfohlenen Bereichs liegt. Der entsprechende Befehl lautet:
ADCSRA |= (1<<ADPS1)|(1<<ADPS2);
Wir haben nun die Vorbereitungen für die ADC abgeschlossen. Nun können wir dem Mikrocontroller die eigentliche AD-Wandlung befehlen. Mit dem Befehl
ADCSRA |= (1<<ADSC);
wird eine AD-Wandlung ausgelöst. Die Wandlung dauert 13 AD-Wandler-Takte, d. h. etwa 200 Mikrosekunden. Nach dem Ende der Wandlung wird das Bit ADSC im Register ADCSRA automatisch auf null gesetzt. Solange muss gewartet werden, und zwar mit dem Befehl
while(ADCSRA&(1<<ADSC));
Das Ergebnis der Wandlung liegt nun in den Registern ADCL (die niederwertigen 8 Bit) und ADCH (die höherwertigen 2 Bit) und kann weiterverwendet werden.
Weiter unten sind einige Beispielprogramme für AD-Wandlung aufgeführt.


Programm "Atmung"


Das Programm bewirkt die "Atmung" zweier LEDs. Das bedeutet, dass die beiden LEDs mit Pulsweitenmodulation langsam angeschaltet und wieder ausgeschaltet werden.
Die C-Datei


Programm "Interrupt"


Das Programm bewirkt einen Interrupt beim Betätigen eines Tasters.
Der Interrupt wird ausgelöst beim Drücken und Loslassen des Tasters. Wenn der Interrupt ausgelöst wird, blinkt die LED fünfmal schnell, ansonsten langsam.
Die C-Datei


Programm "Echo"


Das Programm bewirkt das "Echo" eines Tastendruckes durch eine LED. Man drückt z. B. 2 Sekunden auf einen Taster. Dann leuchtet anschließend eine LED 2 Sekunden lang.
Die C-Datei


Ein Lied mit blinkender LED


Dieses Programm ist dem vorhergehenden sehr ähnlich, es blinkt zusätzlich noch eine LED zu jeder gespielten Note.
Die C-Datei


Der Mikrocontroller spielt ein Lied


Nun lassen wir den Mikrocontroller ein Lied spielen. Ich habe mir dafür das alte Volkslied "Horch was kommt von draußen rein" ausgesucht. Es ist in c-Dur und fängt mit einer c-Dur-Tonleiter an. Weitere Erläuterungen sind in der C-Datei.
Die C-Datei


Programm "Tonleiter"


Wie kann man durch den Mikrocontroller Töne erzeugen? Ich verwende einen ganz einfachen Piezo-Lautsprecher ("Buzzer"). Mein Ziel ist,diesen "Buzzer" eine C-Dur-Tonleiter spielen zu lassen. Diese wird folgendermaßen aufgebaut:

Zur Erzeugung der Töne muss der Mikrocontroller Rechteckschwingungen ausführen, und zwar mit der entsprechenden Periodendauer.
Die C-Datei


Ich habe eine denkbar einfache Anordnung verwendet, einen Programmer "usbtiny" und ein Steckbrett, darauf den ATtiny45, eine LED, einen Vorwiderstand und einen Taster.

Was mache ich mit meinem Mikrocontroller?

Sie haben einen USBTiny-Programmer, einen ATtiny45 Mikrocontroller, ein Steckbrett und eine LED. Wie geht es nun weiter? Gehen Sie auf www.atmel.com, laden Sie sich das Atmel-Studio herunter und installieren Sie es auf Ihrem PC. Nun können Sie ihre ersten kleinen Programme in C schreiben. Weiter unten finden Sie einige Beispiele. Der C-Compiler des Atmel-Studio erzeugt im Projektverzeichnis eine Hex-Datei. Diese Hex-Datei muss nun in den Flash-Speicher des Mikrocontrollers geschrieben werden.
Dazu brauchen Sie das Programm avrdude. Laden Sie es von der Seite savannah.nongnu.org/projects/avrdude herunter.
Für die Funktion von avrdude unter Windows brauchen Sie noch eine Konfigurationsdatei avrdude.conf, die Sie hier herunterladen können. Legen Sie diese Konfigurationsdatei im Ordner c:\windows\system32 ab.
Für den usbtiny-Programmer brauchen Sie noch einen Treiber, den sie bei Adafruit herunterladen können.
Das Programm avrdude wird von der Eingabeaufforderung (Kommandozeile) gestartet. Das folgende Bild zeigt die Kommandozeile für das Programmieren des Flash eines ATtiny45 mit dem Programmer usbtiny:


Ein erstes Assemblerprogramm


Das Programm lässt eine LED blinken. (Das "Hallo Welt!" der Mikrocontroller-Programmierung.)
Die ASM-Datei