Experimentieren mit dem Atmega8-Board Atmega8-Board des Projektlabors der TU Berlin Wahlpflichtkurs Info 9 des Lise-Meitner-Gymnasiums Falkensee Matthias Chaplar 2010 1 Inhaltsverzeichnis Inhaltsverzeichnis 1 Bevor wir starten 3 2 Programm- und Schaltungsbeispiele 2.1 LED blinkt . . . . . . . . . . . . . . . . 2.2 LED mit Taster ein- und ausschalten . . 2.3 Lichtsensor steuert Motor . . . . . . . . 2.4 AD-Wandler steuert LED-Anzeige . . . 2.5 Helligkeit einer LED mit PWM steuern . . . . . 4 4 5 6 6 8 3 Digitale Ein- und Ausgänge 3.1 Ausgänge bzw. Eingänge definieren . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Ausgänge schalten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 9 10 4 PWM - Pulsweitenmodulation 11 5 Analoge Eingänge 5.1 AD-Wandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 13 6 Tricks mit Bits - Bitmanipulation 6.1 Verschieben: << und >> . . 6.2 NOT - Negation . . . . . . . 6.3 AND - bitweises UND . . . . 6.4 OR - bitweises ODER . . . . 6.5 XOR - exclusiv ODER . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 15 15 16 16 16 7 C-Programme - ein paar Grundlagen 7.1 Variable . . . . . . . . . . . . . . . . . 7.2 Funktionen . . . . . . . . . . . . . . . 7.3 Steuerung des Programmablaufs . . . 7.3.1 Wahr und Falsch . . . . . . . . 7.3.2 Programmverzweigungen mit if 7.3.3 Programmschleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 17 18 19 19 20 20 8 Hardware 8.1 Leuchtdioden - LED’s . . . . . . . . . . . 8.2 Transistoren . . . . . . . . . . . . . . . . . 8.2.1 Transistoren als einfache Treiber . 8.2.2 Transistorbrücke steuert Motor an 8.3 Sensoren . . . . . . . . . . . . . . . . . . . 8.3.1 Kontakte - Schalter . . . . . . . . 8.3.2 Fototransistor - Lichtsensor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 22 22 23 23 24 24 26 9 Software 9.1 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.2 Das erste Programm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 28 28 . . . . . . . . . . . . . . . . . . . . 2 1 Bevor wir starten 1 Bevor wir starten Es gibt Dinge, die sollte man wissen, bevor man anfängt zu programmieren oder das Board mit einer Spannungsquelle verbindet. So reagieren manche Bauelemente auf Falschpolung empfindlich (und oft sehr schnell) - also zwei mal hinsehen, bevor man den Batteriepack anksteckt! Die Ausgänge können nur Ströme bis zu einer bestimmten Stärke liefern (siehe 3.2). Deshalb wird es an passender Stelle immer wieder Hinweise und Rechenbeispiele geben. Da wir hier einen Mikrocontroller programmieren, muss ab und zu auch etwas zu seinem Innenleben gesagt werden. Moderne Mikrocontroller wie unser Atmega8 sind so preiswert, weil sie sehr flexibel einsetzbar und deshalb in großen Stückzahlen zu produzieren sind. Diese Flexibilität zeigt sich bei der Vielzahl der integrierten elektronischen Bausteine (AD-Wandler, Timer etc.) und bei der Mehrfachbelegung der meisten Anschlüsse des Schaltkreises. Wenn ein Anschluss sowohl Eingang als auch Ausgang sein kann, dann muss das irgendwo festgehalten - gespeichert - werden. Dafür und für andere wichtige Informationen gibt es Speicherplätze -“Register” und die muss man kennen lernen. Im Folgenden werden ein paar einfache Experimente beschrieben; schrittweise wollen wir so Komponenten des Controllers und ihre Programmierung erfahren. Dabei wird oft auf ausführlichere Erklärungen im hinteren Teil verwiesen. Was brauchen wir: - Ein Experimentierboard, hier das des Schülerlabors der TU Berlin. - Einen Rechner mit serieller Schnittstelle (RS232) oder USB-zu-seriell-Umsetzer. (Viel Rechenpower ist nicht nötig, es darf ruhig ein älteres Modell sein). - Software. Wir arbeiten mit dem AVR-Studio des Controllerherstellers ATMEL, das benutzt den GNU-C-Compiler (Paket WIN-AVR). Die fertigen Programme brennen wir mit dem Programm PonyProg2000. (zur Installation siehe 9). - Dokumentationen. Die des Herstellers zum Controller Atmega8, Datenblätter der Hersteller zu anderen Bauteilen wie Transistoren, Sensoren und andere. 3 2 Programm- und Schaltungsbeispiele 2 Programm- und Schaltungsbeispiele Weiter unten (C-Programme - ein paar Grundlagen 7) wird der Aufbau eines C-Programmes etwas ausführlicher erklärt. 2.1 LED blinkt Eine an PB6 angeschlossene LED soll blinken. Zuerst muss man sich entscheiden, wie man die LED anschließt (siehe 3.2); wir schalten sie nach +Ub . Dann ist PB6 als Ausgang zu schalten und um einen definierten Anfangszustand herzustellen, setzen wir den Ausgang auf HIGH - also LED aus. Es folgt eine Programmschleife, in der der Ausgang umgeschaltet wird (toggle) und gewartet wird. Das Programm: 01) 02) 03) 04) 05) 06) 07) 08) 09) 10) 11) 12) #include <avr/io.h> #define F_CPU 1000000UL #include <util/delay.h> void main(){ DDRB |= (1 << PB6); PORTB |= (1 << PB6); while(1){ PORTB ^= (1 << PB6); _delay_ms(200); } } // // // // // PB6 ist Ausgang PB6 auf HIGH (LED aus) Endlosschleife toggle PB6 warte 0,2s Erlärungen: 01), 03) Einbinden von Dateien; hier avr/io.h für Definitionen von DDRB PB6 usw., sowie delay.h für die Funktion delay ms(). 02) Definieren einer Konstanten; wird hier benötigt, da die delay ms() einen konkreten Wert für F CPU erwartet. In dieser wird F CPU (wann immer es auftaucht) ersetzt durch 1000000 (das ist der werksseitig eingestellte Takt). 06), 07) Bits setzen (siehe 6.4), um den Anschluss als Ausgang zu schalten (ausführlich in 3). 08) Die auf while folgenden Anweisungen werden solange wiederholt, wie in der Klammer die Eins (entspricht wahr) gefunden wird. Mehr zu Schleifen in ??. 09) Bit umkehren, siehe 6.5. 10) Hier wird die Funktion delay ms() mit dem Parameterwert 200 aufgerufen. Mehr zu Funktionen in 7.2. Aufgabe: Baue und programmiere eine Ampel. 4 2 Programm- und Schaltungsbeispiele 2.2 LED mit Taster ein- und ausschalten Weiter hinten (siehe 8.3.1) findet sich mehr zu diesem Thema; hier ein einfaches Programmbeispiel: Ein von Eingang PC4 (Pull-up-Widerstand nicht vergessen!) nach GND gelegter Taster soll eine an PB6 angeschlossene LED bei jeder Betätigung des Tasters umschalten (also EIN falls AUS und umgekehrt): Das Schema verdeutlicht den Programmablauf: Konfigurieren des Controllers, ständiges Wiederholen der Schalterabfrage und der Reaktion darauf. Dabei ergibt sich eine Schwierigkeit: Der Controller arbeitet für uns viel zu schnell! Das Abfragen des Schalters und das Umschalten der LED gehen so schnell, das es Glückssache ist, in welchem Zustand sich die LED am Ende befindet. Man muß den Controller also ausbremsen, indem man wartet bis man wieder den Schalter abfragt. 01) 02) 03) 04) 05) 06) 07) 08) 09) 10) 11) 12) 13) 14) 15) #include <avr/io.h> #define F_CPU 1000000UL #include <util/delay.h> void main(){ DDRB |= (1 << PB6); // PB6 ist Ausgang DDRC &= ~(1 << PC4); // PC4 ist Eingang PORTC |= (1 << PC4); // Pull-up einschalten while(1){ // Endlosschleife if (bit_is_clear(PINC, PC4)){ // ist Taster an PC4 gedrückt? _delay_ms(50); // warten vor nochmaliger Abfrage if (bit_is_clear(PINC, PC4)) PORTB ^= (1 << PB6); // toggle PB6 } } } Aufgabe: Baue und programmiere eine Fußgängerampel. 5 2 Programm- und Schaltungsbeispiele 2.3 Lichtsensor steuert Motor Mit wenig Aufwand (ein Fototransistor und ein Widerstand) lässt sich ein Motor durch Licht schalten. Dazu übernimmt der Fototransistor die Rolle des Tasters und ein Leistungstransistor die eines Treibers für den Motor. Die Schaltung ist einfach, aber auch sehr von den Lichtverhältnissen und den Werten der Bauelemente (Fototransistor und R1 , siehe 8.3.2) abhängig. R2 begrenzt den Basisstrom des Leistungstransistors (mehr dazu: 8.2). Wesentlich flexibler, was Umweltbedingungen angeht ist der Einsatz eines ADWandlers zum Einlesen der am Fototransistor abfallenden Spannung - aber eben auch aufwändiger (siehe nächstes Beispiel). Das Programm ähnelt dem Letzten sehr: 01) #include <avr/io.h> 02) #define F_CPU 1000000UL 03) 04) void main(){ 05) DDRB |= (1 << PB6); 06) DDRC &= ~(1 << PC4); 07) 08) while(1){ 09) if (bit_is_clear(PINC, PC4)){ 10) PORTB |= (1 << PB6); 11) } 12) else PORTB &= ~(1 << PB6); 13) } 14) } // PB6 ist Ausgang // PC4 ist Eingang // Endlosschleife // hell genug? // Motor ein // Motor aus 2.4 AD-Wandler steuert LED-Anzeige Vier LED (an PC0 bis PC3) sollen abhängig von einer Spannung an PC4 aufleuchten, ähnlich einer Aussteuerungsanzeige. Die Spannung stellen wir mit einem Potenziometer ein; da die interne Referenzspannung verwendet werden soll, schränken wir den Regelbereich auf 0V bis V2cc ein. Erläuterungen zur AD-Wandler siehe 5.1. 6 2 Programm- und Schaltungsbeispiele 01) 02) 03) 04) 05) 06) 07) 08) 09) 10) 11) 12) 13) 14) 15) 16) 17) 18) 19) 20) 21) 22) 23) 24) 25) 26) 27) 28) 29) 30) 31) 32) 33) 34) 35) 36) 37) 38) 39) 40) 41) 42) // Beispiel ADC #include <avr/io.h> #define F_CPU 1000000UL #include <util/delay.h> #include <inttypes.h> uint16_t wandlerwert = 0; //für das Ergebnis der Wandlung void init(void) { DDRC |= (1 << DDRC |= (1 << DDRC |= (1 << DDRC |= (1 << PORTC PORTC PORTC PORTC |= |= |= |= (1 (1 (1 (1 // Anschlüsse einstellen, Wandler starten << << << << PC0); // 4 Ausgänge für LED PC1); PC2); PC3); PC0); PC1); PC2); PC3); // // // // LED nach VCC, Ausgang auf VCC (1), also LED AUS dito dito dito ADCSRA |= (1 << ADEN); // Wandler ’’einschalten’’ ADMUX = (1 << MUX2); // Kanal gewählt (PC4), siehe Tabelle ADMUX |= (1 << REFS1) | (1 << REFS0); // interne Referenzspannung (2,56V) ADCSRA |= (1 << ADFR); // frei laufende Wandlung ADCSRA |= (1 << ADPS1) | (1 << ADPS0); // Teilungsfaktor 8 ADCSRA |= (1 << ADSC); // Wandlung starten } void main(void) { init(); while(1){ // Endlosschleife wandlerwert = ADCW; // Ergebnis der Wandlung lesen if (wandlerwert > 100) PORTC &= ~(1 << PC0); // LED einschalten if (wandlerwert > 300) PORTC &= ~(1 << PC1); if (wandlerwert > 500) PORTC &= ~(1 << PC2); if (wandlerwert > 700) PORTC &= ~(1 << PC3); _delay_ms(100); PORTC |= (1 << PC0) | (1 << PC1) | (1 << PC2) | (1 << PC3); // alle aus } } Erlärungen: 22) Wandler einschalten nicht vergessen! 23) Kanal wählen, siehe Tabelle 5.1 24) Referenzspannung wählen, siehe Tabelle 5.1 25) free running mode einschalten 26) Teilungsfaktor einstellen, siehe Tabelle 5.1 27) Wandler starten nicht vergessen!! 34) in ADCW findet sich das Ergebnis der Wandlung als 16-Bit-Zahl 35)-38) Abhängig vom Wert der Wandlung werden LEDs eingeschaltet 39),40) 0, 1s warten, alle LEDs ausschalten 7 2 Programm- und Schaltungsbeispiele 2.5 Helligkeit einer LED mit PWM steuern Die LED ist an Pin PB1 angeschlossen, dem 1. Kanal für PWM. An PC5 liegt der Taster. Bei Betätigen des Tasters wird der Wert einer Variablen um 16 erhöht. Da es eine 8-Bit Variable ohne Vorzeichen ist, ist der letzte Wert dieser Variablen 240. Erneute Addition von 16 erzeugt den Wert 0 und es geht von vorn los. Insgesamt gibt es 16 Helligkeitsstufen. Das Programm: 01) 02) 03) 04) 05) 06) 07) 08) 09) 10) 11) 12) 13) 14) 14) 15) 16) 17) 18) 19) 20) 21) 22) 23) 24) 25) #include <avr/io.h> #define F_CPU 1000000UL #include <util/delay.h> void init(){ DDRB |= (1 << PB1); PORTB |= (1 << PB1); DDRC &= ~(1 << PC5); PORTC |= (1 << PC5); // // // // PB1 als Ausgang für LED nach + LED aus PC5 als Eingang Pullup an PC5 einschalten TCCR1A |= (1 << WGM10) | (1 << COM1A1); TCCR1B |= (1 << WGM12) | (1 << CS10); } void main(){ uint8_t pwm = 0; // Wert für PWM init(); while (1){ if (bit_is_clear(PINC, PC5)){ _delay_ms(100); if (bit_is_clear(PINC, PC5)) pwm += 16; } OCR1A = pwm; } } Erlärungen: 11) Setzen der Signalart (COM1A1 - löschen beim Aufwärtszählen) und der Betriebsart (8-Bit PWM) 12) Betriebsart (WGM12, fast PWM) und Vorteiler 1 (CS10). 16) uint8 t: vorzeichenlose 8-Bit-Zahl; Start mit dem Wert Null. 21) Falls der Taster gedrückt wurde, wird pwm um 16 größer. 23) In OCRA1 steht der Wert, bei dem beim Aufwärtszählen das Löschen von PB1 erfolgt. Mehr zu PWM siehe 4. 8 3 Digitale Ein- und Ausgänge 3 Digitale Ein- und Ausgänge Digital bedeutet, es werden nur zwei Zustände unterschieden: 0 und 1 oder LOW und HIGH, das bedeutet streng genommen 0V an Ein- oder Ausgang oder die volle Betriebsspannung. Praktisch wird an den Ausgängen nie die volle Betriebsspannung oder 0V anliegen: Es werden bei 5V Betriebsspannung und Belastung des Ausgangs mit 20mA mindestens 4, 2V für HIGH und höchstens 0, 7V für LOW garantiert. 3.1 Ausgänge bzw. Eingänge definieren Als erstes ist festzulegen, welche der Pins Ein- bzw. Ausgänge sind. Diese Information ist in die Datenrichtungsregister (eine 8 Bit breite Speicherzelle) des Ports zu schreiben. Jeweils 8 Pins werden zu einer Gruppe “Port“ zusammengefasst - aber Achtung: nicht immer hat der Schaltkreis genug “Beine“, um alle hinaus zu führen! Diese Register nennen sich DDRx, x für Port A bis D. Die Anschlußbeine werden für jeden Port durch nummeriert; in der C-Datei “avr/io.h” sind Bezeichner für die Pins (und Register) definiert. Zum Beispiel nennen sich die Pins des Ports B PB0 bis PB7 (Informatiker beginnen immer bei Null zu zählen). Mit diesen Bezeichnern lässt sich am einfachsten programmieren. Dazu muss man eigentlich nur drei Arten der Manipulation von Bits (siehe eigener Abschnitt) beherrschen (d.h. Einsen oder Nullen an die richtige Stelle in einem Register schreiben): Stell dir dazu das Register so vor: Nummer Zustand 7 X 6 X 5 X 4 X 3 X 2 X 1 X 0 X Jeder Nummer ist ein Pin zugeordnet; ist das zum Beispiel das Register DDRC, dann steht 4 für Pin PC4. Zustand X bedeutet, hier ist eine 0 oder eine 1 möglich. 0 an einer Stelle steht für Eingang, 1 steht für Ausgang. Soll der Pin mit der Nummer 4 Ausgang werden, dann ist dort eine Eins zu schreiben: DDRC |= (1 << PC4); schreibt an die richtige Stelle eine 1 (siehe 6.4): Nummer Zustand 7 X 6 X 5 X 4 1 3 X 2 X 1 X 0 X Der Pin 7 soll Eingang sein (um Schalter abzufragen o.ä.), also muss hier eine Null gesetzt werden: DDRC &= ~(1 << PC7); schreibt an die richtige Stelle eine 0 (siehe 6.3): Nummer Zustand 7 0 6 X 5 X 9 4 1 3 X 2 X 1 X 0 0 3 Digitale Ein- und Ausgänge Da sich diese Festlegungen während der Laufzeit des Programms selten ändern, ist es vernünftig, diese Einstellungen in einer Funktion zusammen zu fassen. Diese Funktion muss vor der mainFunktion im Quelltext stehen! Diese Funktion könnte so aussehen: void init(){ DDRC |= DDRC |= DDRC |= DDRC |= DDRC |= DDRC &= DDRC &= DDRC &= } (1 << PC0); (1 << PC1); (1 << PC2); (1 << PC3); (1 << PC4); ~(1 << PC5); ~(1 << PC6); ~(1 << PC7); \\ PC0 bis PC4 als Ausgänge festlegen \\ PC5 bis PC7 als Eingänge festlegen Die init-Funktion sollte auch gleich die Ausgänge auf sinnvolle Werte schalten (im nächsten Abschnitt erklärt) und man kann die pull-up-Widerstände der Eingänge einschalten (erklärt im Abschnitt Eingänge). 3.2 Ausgänge schalten Ausgänge können zwei Zustände annehmen: Ausgeschaltet (0V am Pin) oder eingeschaltet (positive Betriebspannung am Pin). In beiden Zuständen können sie einen Strom von bis zu 40mA je Pin liefern (aber nur insgesamt 300mA zwischen positiver Betriebsspannung und GND). Das reicht, um LED‘s leuchten zu lassen (siehe 8.1). aber kaum um Motoren zu versorgen. Hier müssen Transistoren (siehe 8.2.1) oder Transistorbrücken (siehe 8.2.2) nach geschaltet werden. So wird eingeschaltet (+UB ) am Ausgang (|= OR - siehe 6.4): PORTC |= (1 << PC0); Und so wird ausgeschaltet (etwa 0V ) am Ausgang (&= AN D - siehe 6.3): PORTC &= ~ (1 << PC0); Dabei wird in andere Register (PORTx) geschrieben! Will man den Ausgang nur unabhängig vom gegenwärtigen Zustand umschalten, dann leistet das der Befehl (ˆ= XOR - siehe 6.5): PORTC ^= (1 << PC2); Damit kann man prima eine LED blinken lassen! 10 4 PWM - Pulsweitenmodulation 4 PWM - Pulsweitenmodulation PWM (PulsWeitenModulation) ist ein elegantes Mittel zum Einstellen der Spannung am Ausgang und damit zur Steuerung der Leistung. Im Beispiel (siehe 2.5) wird die Helligkeit einer LED eingestellt, man kann aber auch zum Beispiel die Drehzahl eines Motors regeln. Aber wie soll man die Spannung an einem Ausgang regeln können, wo ein Ausgang doch nur zwischen 0V und der vollen Betriebsspannung umschalten kann? Das geht durch einen Trick: Der Ausgang wird ständig (und schnell) ein- und ausgeschaltet. Dabei kann man das Verhältnis von Ein- und Ausschaltdauer variieren. Die Oszillografenbilder zeigen das: Legt man an einen so getakteten Ausgang einen Kondensator nach GN D, dann stellt sich am Kondensator eine (fast) konstante Spannung ein, deren Betrag sich aus dem Verhältnis von Einund Ausschaltdauer ergibt. Man kann auch die Trägheit ausnutzen: Steuert man mit diesen Impulsen eine LED an, dann nehmen wir die Einzelimpulse nicht wahr, sondern eine hellere oder dunklere LED. Bei Elektromotoren ist es die mechanische Trägheit der drehenden Teile des Motors, die dafür sorgt das er nicht ruckelt, sondern langsamer oder schneller dreht. Wie funktioniert das? Ein Zähler wird mit dem Takt des Controllers angesteuert (ein Vorteiler kann diesen Takt noch herunter teilen). Es gibt 14 verschiedene Betriebsarten (Zähler zählt aufund ab- oder nur aufwärts, der Ausgang wird auf verschiedene Weise umgeschaltet). Der Mega8 hat drei dieser Systeme, so das man die zwei Motoren eines Roboters getrennt steuern kann. Am Beispiel einer Betriebsart soll gezeigt werden, wie man Vorteiler und Zählervoreinstellungen über Register steuert: Betriebsart fast PWM, 8-Bit, Ausgang auf 0 bei Erreichen eines voreingestellten Zählerstandes, Ausgang auf 1 bei Zählerstand 0. TCCR1A - Timer Counter Control Register 1 A Nummer Name 7 COM1A1 6 COM1A0 5 COM1B1 4 COM1B1 3 FOC1A 2 FOC1B 1 WGM11 0 WGM10 Sind WGM13=0 und WGM12=1; dann stellt man mit WGM10 bis WGM13 die Zählweite ein: WGM11 0 0 1 1 WGM10 0 1 0 1 Wirkung PWM aus PWM 8-Bit PWM 9-Bit PWM 10-Bit Der Zähler zählt nun ständig von 0 bis 255 (8-Bit), 511(9-Bit) oder 1023(10-Bit) und zurück. COM1A1 und COM1A0 (COM1B1 und COM1B0 für den zweiten Kanal) bestimmen, wie die Signale ausgeben werden: COM1A1 0 0 1 1 COM1A0 0 1 0 1 Wirkung PWM aus (normaler Ausgang) PWM aus Pin löschen beim Aufwärts- und setzen beim Abwärtszählen Pin setzen beim Aufwärts- und löschen beim Abwärtszählen 11 4 PWM - Pulsweitenmodulation Kanal A arbeitet über Pin PB1, Kanal B ber Pin PB2. TCCR1B - Timer Counter Control Register 1 B Nummer Name 7 - 6 - 5 - 4 WGM13 3 WGM12 2 CS12 1 CS11 0 CS10 CS10 bis CS12 stellen den Vorteiler bzw. die Taktquelle ein: CS12 0 0 0 0 1 1 1 1 CS11 0 0 1 1 0 0 1 1 CS 10 0 1 0 1 0 1 0 1 Wirkung Zähler gestoppt Teilerfaktor 1 Teilerfaktor 8 Teilerfaktor 64 Teilerfaktor 256 Teilerfaktor 1024 externer Takt (Pin T1), abfallende Flanke externer Takt (Pin T1), ansteigende Flanke Das Beispiel 2.5 zeigt eine Anwendung. (für mehr Informationen siehe Datenblatt Atmega8) 12 5 Analoge Eingänge 5 Analoge Eingänge Analoge Eingänge können nicht nur die logischen Pegel 0 und 1 unterscheiden, sondern Spannungen im Bereich von 0V bis zur Höhe der Betriebsspannung lesen. Es gibt zwei Varianten: Ein Komparator vergleicht die anliegende Spannung mit einer Vergleichsspannung (Referenzspannung); der AD-Wandler erzeugt einen dem Betrag der Spannung entsprechenden Zahlenwert. 5.1 AD-Wandler Der Atmega8 (Gehäusevariante mit 28 Pins) verfügt über 6 Eingänge, die mit einem AD-Wandler verbunden werden können. Es sind die Anschlüsse des Port C. Der Spannungswert wird in eine 10-Bit-Zahl gewandelt, was bedeutet die anliegende Spannung kann mit einer Genauigkeit von etwa 0, 1% gemessen werden (10 Bit sind 1024 Abstufungen). ”Gemessen” ist eigentlich nicht richtig, denn die anliegende Spannung wird mit einer Referenzspannung verglichen. Diese Referenzspannung kann auf verschiedene Weise erzeugt werden und unterschiedliche Werte annehmen: - Eine externe Spannung (maximal Vcc ) am Anschluss AREF. - Die Spannung am Anschluss AVCC (min. 2, 7V , max. 5, 5V , aber höchstens um 0, 3V abweichend von Vcc ). - Die interne Referenzspannung (Sollwert 2, 56V ; kann zwischen 2, 3V und 2, 7V liegen). Noch eine Entscheidung muss getroffen werden: Soll der Wandler erst auf Anforderung tätig werden (single conversion mode) oder ständig arbeiten (free run mode). Und es ist noch der zu lesende Anschluss auszuwählen (PC0 bis PC5). Zwie Register steuern das Wandeln, zwei Register nehmen das Ergebnis der Wandlung auf (da mit bis 10 Bit Genauigkeit gewandelt wird, sind zwei 8-Bit-Register nötig).Es sind die Register ADCL und ADCH. Register ADCSRA (ADControll und Status Register): ADEN Enable (1) ADSC Start (1) ADFR Free Run (1) ADIF 1 wenn fertig ADIE - ADPS2 Vorteiler ADPS1 Vorteiler ADPS0 Vorteiler Register ADMUX (ADMUltipleXregister): REFS1 Uref REFS0 Uref ADLAR Ergebnisformat - MUX3 Kanal MUX2 Kanal MUX1 Kanal MUX0 Kanal Soll der ADC mit interner Referenzspannung im free running Modus Spannungen am Pin PC0 lesen, dann müssen wir setzen (auf 1): - ADEN, ADSC, ADFR (enable, start, free running) - ADSP1 und ADSP0; für den Takt, mit dem der AD-Wandler arbeitet werden 50kHz bis 200kHz empfohlen. Dieser Takt ist durch einen Vorteiler aus dem Systemtakt zu gewinnen. Da wir mit 1M Hz Systemtakt arbeiten, bietet sich ein Teilungsfaktor von 8 an. - REFS1 und REFS0 für interne Referenz. Teilerfaktoren: ADSP2 0 0 0 0 1 1 1 1 ADSP1 0 0 1 1 0 0 1 1 13 ADSP0 0 1 0 1 0 1 0 1 Teiler 2 2 4 8 16 32 64 128 5 Analoge Eingänge Referenzspannung: REFS1 0 0 1 1 REFS0 0 1 0 1 Quelle Extern, AREF AVCC ist Referenz reserviert intern, 2, 56V Auswahl des Kanals: MUX2 0 0 0 0 1 1 MUX1 0 0 1 1 0 0 MUX0 0 1 0 1 0 1 Kanal PC0 PC1 PC2 PC3 PC4 PC5 Soll nur mit einem Kanal gemessen werden (und kommt es nicht auf minimalen Energieverbrauch an), dann ist der free-run-mode eine einfache Lösung. Ein Programmbeispiel findet sich hier 2.4. 14 6 Tricks mit Bits - Bitmanipulation 6 Tricks mit Bits - Bitmanipulation C bietet eine ganze Reihe von Operatoren, die Bit für Bit arbeiten und die für uns hier sehr wichtig im Umgang mit Registern sind. Alles was wir hier aufschreiben, kann man auch kürzer notieren, der große Vorteil unserer Schreibweise ist die größere Sicherheit bestimmte Fehler nicht zu machen! Eigentlich machen wir jetzt immer das Gleiche: Der Registerinhalt wird verändert, indem wir ihn mit einer Bitmaske verknüpfen. Wie ist das zu verstehen? Wir wollen immer nur bestimmte Bits setzen (1 oder HIGH) oder löschen (0 oder LOW). Im ersten Schritt bauen wir ein Bitmuster (oder eine M aske), das unsere Wünsche widerspiegelt: Da wo in unserem Muster Einsen stehen, soll sich etwas tun. Am einfachsten geht das mit shif t Verschieben: 6.1 Verschieben: << und >> Wir brauchen eigentlich nur das Links-Schieben: <<. Da wir Einsen an die richtigen Stellen schieben wollen, starten wir mit der 1. Für unsere maske haben wir 8 Bit zur Verfügung, entsprechend der Größe unserer Register; sehen wir uns die 1 genau an, dann sieht sie eigentlich so aus: 00000001 Im ersten Programmbeispiel soll PB6 ein Ausgang sein, dort muss also eine 1 stehen. Schieben wir die 1 an die richtige Stelle: 1 << PB6; Genauer besehen ist das 00000001 << 6; denn PB6 wird ersetzt durch 6 (so steht es in der avr/io.h) und bedeutet: “Schiebe die Eins 6 Stellen nach links“. Unsere Maske sieht dann so aus: 00100000 Sollen mehrere Einsen geschoben werden, weil wir mehr als einen Eingang haben, dann werden die Schiebeanweisungen durch ODER(6.4) miteinander verknüpft: ((1 << PB5) | (1 << PB6) | (1 << PB7)); Wir erhalten als Maske: 11100000 Jetzt könnte man das Bitmuster dem zuständigen Register zuweisen: DDRB = ((1 << PB5) | (1 << PB6) | (1 << PB7)); Dabei kann man aber ungewollt Nullen schreiben (Bits löschen) wo man es eigentlich nicht wollte. Um das zu vermeiden, benutzt man besser die folgenden Operatoren: 6.2 NOT - Negation Manchmal will man nicht das, was man gerade hat, sondern genau das Gegenteil. Der N OT -Operator dreht jedes Bit um: ~((1 << PB5) | (1 << PB6) | (1 << PB7)); liefert jetzt als Maske: 00011111 Man kann den Inhalt eines Registers in das Gegenteil umwandeln: PORTB ~= PORTB; 15 6 Tricks mit Bits - Bitmanipulation 6.3 AND - bitweises UND Die Wirkung dieses Operators lässt sich aus der Wahrheitswertetabelle ablesen: Bit 1 0 0 1 1 Bit 2 0 1 0 1 Bit 1 & Bit 2 0 0 0 1 Wir setzen AN D ein, um Bits zu löschen. Denn egal, was es vorher war (Bit 1), wenn es 0 werden soll (Bit 2), dann wird es das auch (Bit 1 & Bit2). Beispiel: PIN PB6 soll auf LOW gesetzt werden, also muss die Maske so aussehen (zur Erinnerung: Wir wollen PB6 verändern!): 01000000 erzeugt mit: 1 << PB6; AN D setzt eine Null, wenn eines der beiden Bits Null ist, also muss man die Maske “umdrehen”: ~(1 << PB6); und mit dem Inhlt des Registers über AN D verknüpfen: PORTB &= ~(1 << PB6); 6.4 OR - bitweises ODER Für den OR-Operator sieht die Wahrheitswertetabelle so aus: Bit 1 0 0 1 1 Bit 2 0 1 0 1 Bit 1 | Bit 2 0 1 1 1 Diesen Operator setzen wir ein, wenn wir ein Bit auf Eins (HIGH) setzen wollen. Denn alles, was schon 1 war (Bit 1) oder werden soll (Bit 2) wird auch auf 1 gesetzt (Bit 1 | Bit 2). Beispiel: PB6 und PB7 sollen Ausgänge sein: DDRB |= ((1 << PB6) | (1 << PB7)); Also wie gehabt: Maske bauen, mit Register verknüpfen. 6.5 XOR - exclusiv ODER Für den XOR-Operator (“entweder-oder”) ergibt sich diese Wahrheitswertetabelle: Bit 1 0 0 1 1 Bit 2 0 1 0 1 Bit 1ˆBit 2 0 1 1 0 Das ist so zu verstehen: Ist der PIN ausgewählt (Bit 2 auf 1), dann wird sein alter Zustand (Bit 1) umgedreht (Bit 1ˆBit 2). Wir benutzen den XOR-Operator, um Ausgänge umzuschalten: PORTB ^= (1 << PB6); damit wird PIN PB6 umgeschaltet. 16 7 C-Programme - ein paar Grundlagen 7 C-Programme - ein paar Grundlagen Einiges wird schon mit den Beispielen (siehe 2) erklärt, hier ein paar Grundlagen. 7.1 Variable C -Programme sind eine Folge von Anweisungen für den Controller, die er der Reihe nach abarbeitet. Dabei werden die Inhalte von Speicherzellen abgefragt oder verändert. Diese Inhalte stellen Zustände des Controllers dar: Welcher der Pins Eingang oder Ausgang ist, ob ein Ausgang (oder Eingang) auf High oder Low liegt, wie groß der Betrag der an einem Pin anliegenden Spannung ist und vieles mehr. Dazu kommen Zustände, die wir für unser Programm definieren wie: Die Länge einer Pause im Programm, ein Maß für die von einem Roboter zurückgelegte Strecke, der Zustand von Berührungssensoren und anderes mehr. Die Sprache C bietet für das Speichern solcher Zustünde eine Reihe von Datenypen: Wir können (und müssen!) festlegen, ob wir einen Zustand als ganze Zahl, Dezimalbruch, als ein einzelnes Zeichen (alles, was wir auf der Tastatur finden) oder Text (besser: Zeichenkette) speichern wollen. Wir nennen solche Zustandsgrößen Variable. Variable müssen deklariert werden, das bedeutet wir teilen mit, das wir Speicherplatz benötigen, was wir zu speichern gedenken und mit welchem Namen wir diesen Speicherplatz ansprechen wollen. Wird einer Variablen erstmalig ein Wert zugewiesen, nennt man das initialisieren. Danach kann man den Wert der Variablen auslesen oder durch Zuweisen ändern. Beispiele: 01) 02) 03) 04) 05) 06) 07) uint8_t pause; uint8_t pause2 = 255; uint16_t drehzahl = 0; pause = 127; 99 = pause2; uint16_t pause2 = 0; _delay_ms(pause); Erlärungen: 01) Deklaration einer Variablen mit dem Bezeichner (Namen) ”pause”. Solange eine Variable nicht initialisiert wurde kann der Versuch ihren Wert auszulesen zu Ärger führen. Deshalb besser: 02) Deklarieren und Initialisieren in einem Schritt. uint8 t ist eine 8-bittige ganze vorzeichenlose Zahl, das entspricht den im Controller vorhandenen Speicherzellen. Man kann Werte von 0 bis 255 speichern. 03) uint16 t: vorzeichenlose 16-Bit-Zahl; benötigt für Ergebnisse der AD-Wandler (von 0 bis 65535). 04) Der Variablen ”pause” wird der Wert 127 zugewiesen; das Gleichheitszeichen ist ein Zuweisungsoperator: Was rechts steht, wird am links angebenen Ort gespeichert. 05) Sinnlos, siehe 04). 06) Führt zu einem Fehler, das Programm kann nicht übersetzt werden da der Bezeichner ”pause2” schon verwendet wurde. 07) Bei Aufruf der Funktion wird der gerade gültige Wert für ”pause” eingesetzt; hier: 127. Regeln für Bezeichner: • Du darfst Buchstaben (nur englisches Alphabet!), Ziffern und den Unterstrich ( ) benutzen. • Du darfst nie mit einer Ziffer beginnen. • Groß- und Kleinschreibung wird unterschieden. • Erfinde ”sprechende” Bezeichner; drehzahl ist besser als x oder d. 17 7 C-Programme - ein paar Grundlagen 7.2 Funktionen Ein C-Programm besteht aus mindestens einer, der main-Funktion. Die Besonderheit der mainFunktion: Hier beginnt immer das Programm. Ist das ganze Programm sehr einfach und kurz, dann ist die main-Funktion auch die einzige Funktion. Werden Programme länger, dann wird man selbst Funktionen schreiben; dadurch lassen sich Programme gliedern und übersichtlicher gestalten. Funktionen müssen vor ihrem ersten Aufruf definiert werden. Die Definition besteht aus zwei Teilen: Im Funktionskopf wird beschrieben, wie die Funktion aufzurufen ist und im Funktionskörper wird beschrieben, wie sie bei Aufruf arbeitet und was sie als Ergebnis der Arbeit abliefert. Das sieht dann (schematisch) so aus: Datentyp 1) Funktionsbezeichner 2) (Parameterliste)3) { Deklaration lokaler Variablen 4) ... Anweisungen } Erlärungen: 1) Hier wird festgelegt, welchen Typs der Rückgabewert ist (ein Ergebnis der Arbeit der Funktion). Will man nichts zurückgeben lassen, dann schreibt man hier void. 2) Es gelten wieder die Regeln für Bezeichner, siehe 7.1. 3) Man kann der Funktion bei Aufruf Werte übergeben, mit denen sie arbeiten soll. Welchen Typs diese Werte sein werden und in welcher Reihenfolge sie auftreten, wird hier festgelegt. Schau in die Beispiele weiter unten. 4) Man kann innerhalb der Funktion Variable deklarieren. Sie gelten dann aber auch nur innerhalb dieser Funktion! Beispiele: void warte_ein_weilchen( void ) { _delay_ms( 1000 ); return; } Diese Funktion gibt kein Ergebnis zurück (erstes void ) und bekommt bei Aufruf keine Werte mitgeteilt (zweites void ). Bei Aufruf wird einfach nur der im Funktionskörper angegebene Befehl abgearbeitet. Das return kann auch weggelassen werden. void warte_ein_weilchen( uint8_t pausendauer ) { _delay_ms( pausendauer ); return; } In diesem Beispiel wird eine Funktion mit Parameter beschrieben. Sie wird so aufgerufen: ... warte_ein_weilchen( 555 ); ... oder ... pause = 750; warte_ein_weilchen( pause ); ... 18 7 C-Programme - ein paar Grundlagen Offensichtlicher Vorteil: Ich kann die Pausenlänge bei jedem Aufruf der Funktion neu festlegen. Nun eine Funktion, die als Ergebnis ihrer Arbeit etwas zurückgibt: uint8_t sensor_abfrage( void ) { uint8_t wert; ... hole_Wert_irgendwie_vom_ADWandler ... ... return wert; } return wert beschreibt die Rückgabe, die Funktion beendet sich und wirft dabei wert aus - und wert muss aufgefangen werden: ... uint8_t sensormeldung = 0; ... sensormeldung = sensor_abfrage(): ... Die letzte Zeile muss man von rechts nach links lesen: Die Funktion sensor abfrage() wird aufgerufen. Hat sie ihre Arbeit getan, dann steht an der Stelle ihres Aufrufs der von ihr ausgeworfene Rückgabewert (in unserem Beispiel eine 8-Bit-Zahl). Dieser Wert wird jetzt der Variablen sensormeldung zugewiesen (Operator =). Ein weiteres Beispiel (Schalterabfrage über Funktion) siehe 8.3.1 7.3 Steuerung des Programmablaufs Es wird die Regel sein, dass während des Laufs eines Programmes Situationen eintreten, auf die das Programm unterschiedlich reagieren muss. Ein Roboter, der über einen Sensor verfügt um Hindernisse zu bemerken, kann reagieren: Weiter fahren oder ausweichen. Häufig sind Befehlsfolgen zu wiederholen, wie zum Beispiel das Abfragen eines solchen Sensors. 7.3.1 Wahr und Falsch Im Folgenden werden häufig Bedingungen formuliert, dabei soll ein ausdruck immer den Wert true (wahr) oder false (unwahr) annehmen. In C ist alles ungleich 0 wahr. Zum Vergleichen benutzen wir folgende Operatoren: • < und > ; Beispiele: if (x < 12), while (wert1 > wert2) • <= und >= ; Reihenfolge der Zeichen nicht vertauschen! • == für ”ist gleich”; hier lauert eine Falle: Schreibt man versehentlich if (x = 12) statt if (x == 12), dann liefert der erste Ausdruck immer true! • ! = für ”nicht gleich”; Beispiel: if (x! = 12) Manchmal ist nicht nur eine Bedingung zu überprüfen, dann kann man die Bedingungen über logische Operatoren miteinander verknüpfen: • $$ UND ; Beispiel: if ((x > 0)$$(x < 12)); x muss jetzt im Intervall liegen. • || ODER ; Beispiel: if ((y == 5) || (y == 7)); einen der beiden Werte muss y annehmen. • ! NICHT ; Beispiel: if !((y == 5) || (y == 7)); keinen der beiden Werte darf y annehmen. 19 7 C-Programme - ein paar Grundlagen 7.3.2 Programmverzweigungen mit if Sollen nur bei Eintreten eines bestimmten Zustandes (Bedingung) Anweisungen ausgeführt werden (es wird so zu sagen ein Umweg gemacht), dann benutzt man die ”einseitige” if -Anweisung: if (ausdruck) anweisung1 anweisung2 Oder, für zwei alternative Wege, die ”zweiseitige”: if (ausdruck) anweisung1 else anweisung2 Hinweis: anweisung1 und anweisung2 dürfen auch Blöcke von mit { } zusammen gefassten Anweisungen sein. 7.3.3 Programmschleifen Die main-Funktion enthält fast immer eine Endlosschleife: while (1){ ... }. Denn meistens soll unser Programm immerzu laufen. Schleifen mit for Nennt man auch Zählschleifen. Eine Schleifenvariable wird nach jedem Durchlaufen der Schleife mit konstanter Schrittweite verändert. Beispiel: for (uint8_t z=0; z<10; z++){ anweisungen; } anweisung2; anweisung2 wird erreicht, wenn die im Schleifenkopf formulierte Aussage (hier: z < 10) nicht wahr wird. Schleifen mit while Die Schleifenvariable muss vor Eintritt in die Schleife gesetzt und durch eine eigene Anweisung im Schleifenkörper verändert werden. Beispiel: uint8_t z=100; while (z > 10){ anweisungen; z /= 2; } anweisung2; anweisung2 wird wieder erreicht, wenn die im Schleifenkopf formulierte Aussage (hier: z > 10) nicht wahr wird. 20 7 C-Programme - ein paar Grundlagen Schleifen mit do - while Benutzt man die while-Schleife, dann kann es passieren dass der Block mit den zu wiederholenden Anweisungen niemals durchlaufen wird (Ausdruck unwahr). Will man einen mindestens einmaligen Durchlauf garantieren, nutzt man die do - while-Schleife. Beispiel: uint8_t z=0 do { anweisungen; z = z - 10; } while ( z > 0); anweisung2; 21 8 Hardware 8 Hardware Hier sollen ein paar Hinweise zu zusätzlichen Bauelementen und deren Beschaltung gemacht werden. Mehr erfährt man aus Datenblättern und Schaltungsbeschreibungen im Internet. 8.1 Leuchtdioden - LED’s Lassen, wie alle Dioden, den Strom nur in einer Richtung fließen. Sie haben eine sehr steil verlaufende I-U-Kennlinie, was bedeutet, ab einer bestimmten Spannung ruft eine kleine Spannungsänderung eine große Änderung der Stromstärke hervor. Für den praktischen Einsatz heißt das: Betreibe eine LED nie ohne Vorwiderstand! Die Flussspannung der Diode ist abhängig von der Farbe des Lichts und reicht von etwa 1V (Infrarot) bis über 4V (Ultraviolett). Für die meist eingesetzten roten, gelben und grünen LED kann man mit etwa 2V rechnen. 3mm-LED’s Schaltsymbol Die Anode (A -langes Anschlussbein) zeigt zum Pluspol der Spannungsquelle, die Kathode (K) zum Minuspol. Je nach Typ leuchten die Dioden bei 5 bis 10mA schon ausreichend hell, fast alle Typen vertragen auch 20mA. Der Vorwiderstand berechnet sich nach der Gleichung: Rvor = UB −ULED ILED Verwenden wir vier NiMh-Akkus, dann ist die Betriebsspannung UB = 4, 8V , setzen wir die Flussspannung mit ULED = 2V und wählen wir die Stromstärke ILED = 20mA, dann erhalten wir: Rvor = (4,8−2)V 0,020A = 140Ω Der nächste Standardwert ist 150Ω. 8.2 Transistoren Mehr als 40mA soll ein Ausgang des Atmega8 nicht liefern und die Summe der Ströme durch den Controller soll 300mA nicht übersteigen. Will man mehr, dann braucht man geeignete Schalter. Schnell, platzsparend und preiswert sind Transistoren. 22 8 Hardware 8.2.1 Transistoren als einfache Treiber Die ersten drei Typen sind Bipolartransistoren; sie haben gerade diese Typnummern, weil sie die preiswertesten waren. Für unsere Zwecke sind Bipolartransistoren Bauteile, bei denen ein schwacher Steuerstrom in die Basis einen starken Strom durch die Strecke Emitter-Kollektor auslöst. Dabei kann man für die BD-Typen mit einem Faktor von 100 rechnen, das bedeutet: Für einen Strom von 1,5A durch den = 15mA, den bringen die Ausgänge Motor brauchen wir nur einen Steuerstrom von IB = 1500mA 100 de Controllers problemlos auf. Die unterschiedlichen Symbole machen klar, daß die Transistoren unterschiedlich aufgebaut sind: Emitterpfeil nach außen bedeutet NPN-Transistor, Pfeil nach innen PNP-Transistor. PNP und NPN sind die Zonenfolgen aus Halbleitermaterial mit Elektronenüberschuss (N) bzw. Überschuss an positiven Löchern (P). Praktisch bedeutet das: Ein NPN-Transistor ist mit dem Emitter an GND (-Pol) anzuschließen, der Kollektor zeigt nach Plus. Zwischen Kollektor und Plus liegt der Motor. Damit ein Steuerstrom in die Basis fließt, muss an der Basis eine gegenüber dem Emitter positive Spannung (etwa 0, 6V ) anliegen. Für PNP-Transistoren kehren sich die Polaritäten um. Ein Widerstand im Basiskreis begrenzt den Basisstrom (es liegen schließlich viel mehr als 0, 6V an, wenn der Ausgang schaltet!). Einen Nachteil haben die Bipolartransistoren: Über die Strecke Emitter-Kollektor fällt bei Stromfluß eine Spannung von etwa 1V ab. Hat man als Betriebsspannung 4, 8V (vier Akkus) oder 4, 5V (drei Zellen), dann geht für die Motoren dadurch schon eine Menge ”verloren”. Dazu kommt, daß diese Energie in den Tansistoren in Wärme gewandelt wird. Bei IC = 1, 5A und UCE = 1V sind das schon 1, 5W ; so ist man eventuell gezwungen, die Transistoren mit Kühlkörpern zu versehen. Da sind MOSFET-Transistoren die bessere Alternative: Sie haben einen kleinen Widerstand der Strecke Source - Drain und lassen sich (fast) leistungslos über die Spannung am Gate steuern. Der IRF630 ist ein N-Channel-Typ und leitet bei einer Gate-Spannung von 2 bis 4V (positiv gegenüber Source). IRF9530 ist ein P-Channel-Typ, die Polaritäten drehen sich wieder um. Die angegeben Typen haben Widerstände RSD von 0, 2 bis 0, 4Ω. Bei stärkeren Motorströmen sucht man nach MOSFET’s mit kleineren Widerständen (die dann aber ein Cent mehr kosten). 8.2.2 Transistorbrücke steuert Motor an Will man einen Motor nicht nur ein- und ausschalten, dann ist eine Motorbrücke angesagt. Sie erlaubt es, die Drehrichtung des Motors umzukehren und man kann die Motorleistung steuern 23 8 Hardware durch PWM (4). Benötigt werden eigentlich nur die vier Leistungstransistoren; indem man jeden von ihnen mit einem Ausgang des Controllers verbindet würde die Brücke voll steuerbar. Allerdings könnte man dann auch ungewollt die Motorbetriebsspannung über zwei Transistoren kurzschließen, was bei kräftigen Akkus unangenehme Konsequenzen hat. Deshalb wird die Brücke um den Schaltkreis ergänzt. Die Verknüpfung der Gatter sorgt dafür, das Transistoren nur so eingeschaltet werden können, daß immer der Motor im Stromkreis liegt. Der Strom kann also immer nur “diagonal“ fließen. Und nebenbei spart man noch zwei Ausgänge des Controllers ein: Über einen Anschluß wird der Motor ein- und ausgeschaltet, über den zweiten bestimmen wir die Drehrichtung. Der Schaltkreis muss aus der Spannungsquelle des Controllers gespeist werden, für den Motor kann man eine andere Spannungsquelle (höherer Spannung und damit größerer Leistung) nutzen. 8.3 Sensoren 8.3.1 Kontakte - Schalter Mechanische Kontakte in Form von Tastern oder Schaltern sind wohl die einfachsten Sensoren. Man kann sie auf verschiedene Art an die Pins des Controllers anschließen: Variante 1 Bei offenem Schalter ist der Pin über den Widerstand (Größe einige kΩ) mit dem Pluspol der Betriebsspannung verbunden. Da (nahezu) kein Strom fließt, liegt der Eingang auf logisch 1. Schließt man den Schalter, dann fließt ein Strom über die Strecke Widerstand-Schalter, über dem Widerstand fällt die gesamte Spannung ab und der Eingang liegt auf logisch 0. Also: Schalter offen ⇒ Eingang auf 1; Schalter geschlossen ⇒ Eingang auf 0. Variante 2 Das kann man auch einfacher haben, denn die Controller verfügen an den Eingängen über eingebaute Widerstände (Pull-up-Widerstände), die man allerdings einschalten muss. Variante 3 Bei offenem Schalter liegt der Eingang on 0V (GN D); schließt man den Schalter, liegt der Eingang an +5V . Allerdings kann man hier keine eingebauten Pull-up-Widerstände nutzen. Aber (wem es gefällt): Schalter offen ⇒ Eingang auf 0; Schalter geschlossen ⇒ Eingang auf 1. 24 8 Hardware Wie im kurzen Beispiel (siehe 2.2) angesprochen: Mechanische Kontakte ”prellen”; das bedeutet wenn wir einen Schalter schließen müssen wir damit rechnen, dass sich die Kontakte wiederholt schließen und öffnen bis sie endgültig schließen. Das dauert nur Milisekunden und wird von uns kaum bemerkt, aber für den Controller sind ein paar Milllisekunden eine lange Zeit. So kann es vorkommen, dass wir beim Auslesen den Schalter im falschen Moment ”erwischen” und der Controller einen offenen Schalter registriert, obwohl wir drauf drücken. Praktisch löst man das so: Frage den Schalter ab; ist er (womöglich ungewollt) offen, dann warte etwas und frage ihn erneut ab. Die Dauer des Wartens ist eine Ermessensfrage, hier muss man eventuell probieren. Beispiel: Zwei Schalter sollen abgefragt werden. Die Abfrage wird durch eine Funktion realisiert. 01) #include <avr/io.h> 02) #define F_CPU 1000000UL 03) #include <util/delay.h> .. . 10) uint8_t schalter( uint8_t pin_port, uint8_t pin_x ) 11) { 12) if (bit_is_clear(pin_port, pin_x)) return 1; 13) else 14) { 15) _delay_ms(20); 16) if (bit_is_clear(pin_port, pin_x)) return 1; 17) } 18) return 0; 19) } .. . 30) if (schalter( PINB, PB4 )) 31) { 32) tue was; 33) } 34) if (schalter( PINB, PB5 )) 35) { 36) tue was anderes; 37) } .. . Erlärungen: ab 10) Definieren einer Funktion. Als Parameter erwartet sie bei Aufruf das abzufragende Eingangsregister (PINx) und den Anschluss (zB. PB4). 12) Ist der Schalter geschlossen, dann steht im Eingangsregister an der entsprechenden Stelle eine Null (”clear”). Die Funktion wird mit Rückgabewert 1 beendet. 15) Der Schalter war nicht geschlossen; eventuelles Prellen abwarten. 16) Erneutes Abfragen; falls geschlossen, beenden mit Rückgabe der 1. 18) Schalter war nicht geschlossen (trotz Warten) - beenden mit Rückgabe der 0. 30, 34) Irgendwo im Hauptprogramm wird die Funktion aufgerufen mit konkreten Werten für Eingangsregister und Anschluss. 25 8 Hardware 8.3.2 Fototransistor - Lichtsensor Fototransistoren lassen sich für viele Zwecke einsetzen: • zur Linienverfolgung (wie beim ASURO); • zur Messung von Drehzahlen, zum Beispiel, der Räder eines Roboters; • zum Senden und Empfangen von Informationen oder Befehlen; • für Entfernungsmessungen; • für Lichtschranken, etc.. Fotodioden erfüllen den gleichen Zweck, sind bei gleicher Lichtempfangsfläche aber weniger empfindlich. Sie setzt man dann ein, wenn ihre kleineren Schaltzeiten einen Nutzen bringen. Wichtig zu wissen ist, dass Fotohalbleiter für infrarotes Licht wesentlich empfindlicher sind als für sichtbares Licht, zudem sind Infrarotsendedioden auch ”heller”. Das Bild zeigt einige Bauformen: Die 1 ist ein sehr alter Typ (Germanium-Transistor mit Linse); 2, 5, 6 sind Typen mit seitlichem Lichteinfall, bei den übrigen fällt das Licht auf die Oberseite. Dunkel eingefärbte Gehäuse sind für sichtbares Licht undurchlässig, das ist sehr praktisch, denn so stört das Umgebungslicht nicht. 5 stammt aus einer alten Maus (eine mit Kugel, da sind zwei Lichtschranken drin), 7 aus einem alten Diskettenlaufwerk; bei 8 ist das Gehäuse mit einer Vertiefung versehen, um ihn auf Glasfaserleitungen aufstecken zu können. Zwei Schaltungsarten sind möglich: Bei Lichteinfall wird der Fototransistor besser leitend, d.h. am Widerstand fällt dann eine größere Spannung ab. Bei der linken Schaltung wird die Spannung am Eingang bei Lichteinfall also kleiner, bei der rechten Schaltung wächst sie. Zwei Möglichkeiten der Auswertung mit dem Controller bieten sich an (die Komparatoreingänge mal ignorierend): Das Schalten durch einen Wechsel 0→1 bzw. 1→0 an einem Digitaleingang und das Einlesen der Spannung an einem Analogeingang. Der Weg über den AD-Wandler ist programmtechnisch aufwändiger, aber man kann damit flexibler auf wechselnde Bedingungen reagieren; vor Allem, wenn man mit sichtbarem Licht arbeitet. Ein Programmbeispiel findet sich hier 2.3, mehr zum AD-Wandler hier 5.1. 26 8 Hardware Die Digitaleingänge sind konzipiert für das Erkennen der logischen Pegel 0 und 1. Laut Datenblatt wird bis zu einer Eingangsspannung von 1V bis 1, 5V (abhängig von der Betriebsspannung) eine 0 und ab 1, 5V bis 2V die 1 erkannt. Dazwischen liegt ein Bereich von etwa 0, 2V bis 0, 5V (Hysteresis) der eigentlich verboten ist, da er keinem logischen Pegel sicher zugeordnet werden kann. Wenn es nur um eine grobe Unterscheidung auf hell/dunkel geht , dann kann man hier mit weniger Programmieraufwand zum Ziel kommen. Den richtigen Wert für den Reihenwiderstand wird man dann durch Probieren finden. 27 9 Software 9 Software 9.1 Installation Es sind drei Programme bzw. Softwarepakete zu installieren: - Zuerst der Compiler mit den Bibliotheken für die ATMEL-Controller. Von http://sourceforge.net/projects/winavr/files/ die Datei WinAVR-20100110-install.exe (oder neuer) herunter laden und starten. - Danach AVR-Studio herunter laden ( im Moment, Feb. 2010 AVR Studio 4.18 SP1 ) - ”avrstudio download” als Anfrage an eine Suchmaschine sollte zum aktuellen Download führen. Eine Registrierung ist nicht nötig. Das herunter geladene Programm ( AVRStudio4.xxx.exe) starten, es sollte dabei den C-Compiler selbst entdecken und einbinden. - Es folgt das Brennprogramm. Die neueste Version von der Seite des Herstellers laden und installieren ( http://www.lancos.com/ppwin95.html ). 9.2 Das erste Programm Wir starten mit dem Anlegen eines neuen Projektes in AVRStudio: Es erhält einen Namen und am Besten auch ein eigenes Verzeichnis: 28 9 Software Dann muss der richtige Controller ausgewählt werden: Jetzt kann programmiert werden: Rechts oben haben wir das Fenster des Editors, hier schreiben oder laden wir den Quelltext. Achtung: Die Datei mit dem C-Quelltext muss den richtigen Namen haben und am richtigen Ort liegen! Am einfachsten ist es, die mit dem neuen Projekt angelegte zu benutzen. Ist der Quelltext fertig, dann wird mit F7 die Übersetzung gestartet. Im unteren Teil erscheinen Fehlermeldungen, Warnungen und falls alles funktionierte die Mitteilung, wie viel Speicherplatz unser Programm belegt. Man kann auch sehen, dass mehrere Programme nacheinander arbeiten. Gesteuert wird dieser Prozess über ein makefile, das beim Anlegen eines neuen Projekts automatisch erzeugt wird (eben deshalb sollte man ein neues Programm immer als neues Projekt beginnen). Das Endprodukt ist die Datei Projektname.hex. Diese wird jetzt mit Ponyprog in den Controller geladen. 29 9 Software Sollte Ponyprog zum ersten Mal starten, dann müssen wir ein paar Einstellungen vornehmen: 1. Schritt: Die Controllerfamilie auswählen. 2. Schritt: Den richtigen Controller wählen. 3. Schritt: Den Dialog für das Setup aufrufen: Auszuwählen sind serial für die serielle Schnittstelle und die Portnummer. Die wird meist COM1 sein, wenn der Rechner über eine eingebaute Schnittstelle verfügt. Benutzt man einen USB-zu-seriell-Adapter, dann muss man in den Systemeinstellungen nachschauen, welche Portnummer er von Windows erhalten hat. Jetzt kann die Datei mit dem übersetzten Programm geöffnet und in den Speicher des Controllers geladen werden (Schritt 4): Sie befindet sich im Verzeichnis default im Projektverzeichnis. Eventuell muss man bei Dateityp noch .hex auswählen, damit sie sichtbar wird. 30 9 Software Nächster Schritt (5.), Übertragen der Daten: Die Daten werden übertragen Datenübertragung war nicht möglich. Überprüfe die Einstellungen für Ponyprog. Ist das Datenkabel mit Rechner und Controller verbunden? Wird der Controller mit Strom versorgt? Alles in Ordnung. Hat alles funktioniert, dann startet Ponyprog den Controller durch ein Reset (geht auch mit Strg+T ). Löschen kann man das Programm mit Strg+E ). 31