Experimentieren mit dem Atmega8-Board - Lise

Werbung
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
Herunterladen