Einstieg Mikrocontroller mit dem Arduino
4 Programmschleifen
Elektronik-Labor Projekte AVR
1.4.22: Einstieg Mikrocontroller mit Arduino
29.4.22: Einstieg Arduino 1, Portausgaben
6.5.22: Einstieg Arduino 2, Digitale Eingänge
13.5.22: Einstieg Arduino 3, Analoge Messungen
20.5.22: Einstieg Arduino 4, Programmschleifen
28.5.22: Einstieg Arduino 5, Übungen
10.6.22: Einstieg Arduino 6, Sesoren
21.6.22: Einstieg Arduino 7, Ampelsteuerung
24.6.22: Einstieg Arduino 8, Zufall und Spiele
Der Reaktionstester
Baue dir einen Reaktionstester mit sechs LEDs. Sie leuchten
nacheinander von links nach rechts im Abstand von 0,1 s auf. Sobald die
erste LED leuchtet, musst du auf den Knopf drücken. Wenn du länger
brauchst, leuchten mehr LEDs und zeigen deine Reaktionszeit. 0,2 s sind
ein guter Wert. Wer nicht gut geschlafen hat, braucht länger.
Das Programm verwendet einige Dinge, die man immer wieder gebrauchen
kann. Dazu gehört die Zählschleife. Man verwendet dazu eine Variable,
die vielleicht schon vorhanden ist. Sie könnte i heißen. Die Schleife
sieht dann so aus:
for (i=0; i<10; i++){
…
}
In diesem Fall wird i bis 9 hochgezählt. Weil die Zählung mit 0
beginnt, wird alles zwischen den geschweiften Klammern genau zehnmal
durchgeführt. Im Reaktionstest wird die Variable n verwendet. Weil sie
hier zum ersten Mal verwendet wird, muss der Typ (int) mit angegeben
werden. Die Schleife wird sechsmal durchlaufen. Aber sie kann mit break
(Unterbrechung) vorzeitig beendet werden, wenn du auf den Knopf drückst.
Das Programm verwendet eine Binärzahl int d=0b1111111100000000 mit
insgesamt 16 Bit. Damit sollen die LEDs am PORTD geschaltet werden.
Weil der Port D aber nur 8 Bit hat, gelten die rechten acht Nullen,
sodass alle LEDs aus wären. Aber in der Schleife gibt es einen
Schiebebefehl d>>=1 (Andere Schreibweise d=d>>1), der alle
Bits um eine Stelle nach rechts verschiebt (shift right) und damit die
LEDs einschaltet. Dieselbe Wirkung hätte übrigens eine Division durch
2. Probiere es aus: d=d/2 oder d/=2.
//Reaktionstest 6 LEDs
void setup(){
DDRD=252;
pinMode(10, INPUT_PULLUP);
}
void loop() {
int d=0b1111111100000000;
for (int n=0; n<6; n++){
d>>=1;
PORTD=d;
delay(100);
if (digitalRead(10)==0) break;
}
delay (1000);
PORTD=0;
delay (1500);
}
Aufgabe: Ändere das Programm so, dass insgesamt acht LEDs verwendet werden, die doppelt so schnell durchlaufen.
Automatisches Morseprogramm
Das Morseprogramm verwendet ebenfalls das Verschieben von Bits und eine
Zählschleife. Neu ist die Tonerzeugung mit analogWrite (9,100). Der
Zweck von analogWrite ist eigentlich ein anderer, nämlich die Steuerung
der Helligkeit einer LED. Aber dabei wird der Port 1000 Mal pro Sekunde
ein- und ausgeschaltet. Beim Anschluss eines Lautsprechers entsteht
deshalb ein Ton von 1000 Hz. Die übergebene Zahl 100 bedeutet, dass der
Port im 100/255 der Gesamtzeit eingeschaltet ist, aber das ist für den
Ton nicht entscheidend.
Das Programm besitzt eine lange Liste, die den Aufbau der Morsezeichen
enthält. Dazu gibt es ein Datenfeld (ein Array) mit bis zu 256 Daten
von d[0] bis d[255]. An der Stelle 15 wird das O definiert. Der
Computer verwendet für ein großes O die Zahl (den ASCII Code) 79. Aber
die Zahl 15 selbst beschreibt das Morsezeichen für O: ---, also drei
Striche. Die Dezimalzahl 15 lautet als Binärzahl 00001111. Die drei
rechten Einsen stehen für die drei Striche. Die erste 1 von links
markiert nur den Anfang des Morsezeichens, weil es ja kurze und lange
Zeichen gibt. Wenn der Aufbau klar ist, kannst du selbst Morsezeichen
zusammenbauen. Punkte werden mit Nullen kodiert. Ein S besteht aus drei
Punkten. Der Code lautet also 00001000.
Um die Zeichen hörbar zu machen, muss das Programm genau abzählen, wo
ein Zeichen beginnt, und wann alle acht Bits gelesen sind. Dazu werden
auch while-Schleifen (while = solange, während) eingesetzt. Solange
dies und jenes gilt, tue das und das. Und der Code m wird immer
wieder um eine Stelle nach links geschoben, diesmal mit einer
Multiplikation mit 2. Zuerst wird mit while(m<128) beobachtet, wann
die Start-Eins erscheint. Wenn sie nämlich links angekommen ist, ist
der Start des eigentlichen Zeichens erkannt. Ab dann wird mit if
(m&128) geschaut, ob ein Strich oder ein Punkt erzeugt werden soll.
Alles zu kompliziert und zu viel auf einmal? Das ist normal. Ich habe
einen halben Tag gebraucht, bis es richtig funktioniert hat. Es reicht,
wenn man am Anfang nur etwas versteht. Wahrscheinlich hast du erkannt,
dass char text[] = "HALLO NANO " festlegt, welcher
Text gemorst werden soll. Also setzte mal deinen Namen oder einen
anderen eigenen Text ein. Und char dot = 100 bestimmt die
Geschwindigkeit. Damit kannst du die Funkamateure im Kurs auf die Probe
stellen, welche Geschwindigkeit sie noch fehlerfrei hören können.
//Morse-Ausgabe
void setup() {
Serial.begin(9600);
}
void loop() {
char text[] = "HALLO NANO ";
//char text[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ";
char dot = 100; //Punktlänge 100 ms für 60 Buchstaben pro Minute
pinMode(0, OUTPUT);
int d[255];
d[1] = 32;
d[2] = 69; //"E", . , 00000010, ASCII 69
d[3] = 84; //"T", - , 00000011, ASCII 84
d[4] = 73; //"I", .. , 00000100, ASCII 73
d[5] = 65; //"A", .- , 00000101, ASCII 65
d[6] = 78; //"N", -. , 00000110, ASCII 78
d[7] = 77; //"M", -- , 00000111, ASCII 77
d[8] = 83; //"S", ... , 00001000, ASCII 83
d[9] = 85; //"U", ..- , 00001001, ASCII 85
d[10] = 82; //"R", .-. , 00001010, ASCII 82
d[11] = 87; //"W", .-- , 00001011, ASCII 87
d[12] = 68; //"d", -.. , 00001100, ASCII 68
d[13] = 75; //"K", -.- , 00001101, ASCII 75
d[14] = 71; //"G", --. , 00001110, ASCII 71
d[15] = 79; //"O", --- , 00001111, ASCII 79
d[16] = 72; //"H", .... , 00010000, ASCII 72
d[17] = 86; //"V", ...- , 00010001, ASCII 86
d[18] = 70; //"F", ..-. , 00010010, ASCII 70
d[19] = 32;
d[20] = 76; //"L", .-.. , 00010100, ASCII 76
d[21] = 32;
d[22] = 80; //"P", .--. , 00010110, ASCII 80
d[23] = 74; //"J", .--- , 00010111, ASCII 74
d[24] = 66; //"B", -... , 00011000, ASCII 66
d[25] = 88; //"X", -..- , 00011001, ASCII 88
d[26] = 67; //"C", -.-. , 00011010, ASCII 67
d[27] = 89; //"Y", -.-- , 00011011, ASCII 89
d[28] = 90; //"Z", --.. , 00011100, ASCII 90
d[29] = 81; //"Q", --.- , 00011101, ASCII 81
d[30] = 32;
d[31] = 32;
d[32] = 53; //"5", ..... , 00100000, ASCII 53
d[33] = 52; //"4", ....- , 00100001, ASCII 52
d[34] = 32;
d[35] = 51; //"3", ...-- , 00100011, ASCII 51
d[36] = 32;
d[37] = 32;
d[38] = 32;
d[39] = 50; //"2", ..--- , 00100111, ASCII 50
d[40] = 32;
d[41] = 32;
d[42] = 32;
d[43] = 32;
d[44] = 32;
d[45] = 32;
d[46] = 32;
d[47] = 49; //"1", .---- , 00101111, ASCII 49
d[48] = 54; //"6", -.... , 00110000, ASCII 54
d[49] = 32;
d[50] = 32;
d[51] = 32;
d[52] = 32;
d[53] = 32;
d[54] = 32;
d[55] = 32;
d[56] = 55; //"7", --... , 00111000, ASCII 55
d[57] = 32;
d[58] = 32;
d[59] = 32;
d[60] = 56; //"8", ---.. , 00111100, ASCII 56
d[61] = 32;
d[62] = 57; //"9", ----. , 00111110, ASCII 57
d[63] = 48; //"0", ----- , 00111111, ASCII 48
for (int n=0; n< strlen(text); n++){
char c= text[n]; //Einzelne Zeichen abtrennen
Serial.print(c);
unsigned int m=0;
for (int j=1; j<91; j++){
if (d[j]==(unsigned int)c){
m=j; //Morsecode finden
j=91;
}
}
//Serial.println(m);
int start=0;
int i=1;
if(m==1) delay (3*dot); //Leerzeichen-Pause
while(m<128){ //Startbit finden
i++;
m=m*2;
}
while(i<8){
m=m*2;
i++;
if (m&128){ //Strich
analogWrite (9,100);
delay (3*dot);
analogWrite (9,0);
delay(dot);
}
else{ //Punkt
analogWrite (9,100);
delay (dot);
analogWrite (9,0);
delay(dot);
}
}
delay (3*dot); //Pause zwischen Zeichen
}
}
Morsetaste und Dekoder
Diesmal sollst du selbst morsen. Und das Programm soll dir dabei
zuhören und die Morsezeichen verstehen. Am Bildschirm erscheint dann,
was du gemorst hast. Der Tastschalter wird als Morsetaste an D2 und GND
angeschlossen. In Reihe zum Lautsprecher ist ein Widerstand eingebaut.
Deshalb kannst du alle Arten von Lautsprechern und Kopfhörern
verwenden.
Dieses Programm verwendet dieselbe Tabelle wie das vorige
Morseprogramm. Aber die Dekodierung der Morsezeichen funktioniert
anders herum. Diesmal muss das Programm genau überwachen, wie lange
jemand auf die Taste drückt und wie lange die Pausen dazwischen sind.
Ist das ein Punkt oder ein Strich? Gehört dies noch zum selben Zeichen,
oder beginnt hier ein neues? Oder ist die Pause noch länger, sodass ein
Leerzeichen den Abstand zu einem neuen Wort markieren soll?
Strichlängen und Pausen werden hier in Einheiten von 50 ms gemessen.
Das bedeutet, dass ein Ton 50, 100, 150 oder z.B. 300 ms lang sein
kann. Für einen Telegrafie-Meister sind die 50 ms viel zu lang, aber
für den ersten Einstieg gerade richtig. Bei 40 BpM (Buchstaben pro
Minute) dauert ein Punkt 150 ms und ein Strich 450 ms. Die Zeit t liegt
mit 5 Einheiten mal 50 ms = 250 ms gerade dazwischen. Kürzer als 250 ms
wird also als Punkt gewertet und länger als 250 ms als Strich. Man muss
deshalb nicht genau mit 40 BpM morsen, sonders es darf auch etwas
schneller oder etwas langsamer sein.
Wer noch nie etwas über Morsezeichen erfahren hat, kann es hiermit
lernen. Probiere es einfach aus und lerne ein paar Zeichen. Es ist
wichtig, sich einfach den Klang und den Rhythmus zu merken, aber keine
Worte. Wenn man ein Zeichen sprechen will, kann man sagen: da da dit dit
dit (für --… = „7“)Auf Kurzwelle hört man diese beiden Abkürzungen
besonders häufig, und deshalb lerne sie am besten zuerst:
CQ (Allgemeiner Anruf, kommt wohl von „I seek you“, ich suche dich)
73 (Viele Grüße, vielleicht weil es so schön klingt?)
//Morse-Eingabe
void setup() {
Serial.begin(9600);
pinMode(2, INPUT_PULLUP);
}
void loop() {
int dot=150; //Punktlänge 150 ms für 40 Buchstaben pro Minute
int t=dot/30;
pinMode(0, OUTPUT);
int d[255];
d[1] = 32;
d[2] = 69; //"E", . , 00000010, ASCII 69
d[3] = 84; //"T", - , 00000011, ASCII 84
d[4] = 73; //"I", .. , 00000100, ASCII 73
d[5] = 65; //"A", .- , 00000101, ASCII 65
d[6] = 78; //"N", -. , 00000110, ASCII 78
d[7] = 77; //"M", -- , 00000111, ASCII 77
d[8] = 83; //"S", ... , 00001000, ASCII 83
d[9] = 85; //"U", ..- , 00001001, ASCII 85
d[10] = 82; //"R", .-. , 00001010, ASCII 82
d[11] = 87; //"W", .-- , 00001011, ASCII 87
d[12] = 68; //"d", -.. , 00001100, ASCII 68
d[13] = 75; //"K", -.- , 00001101, ASCII 75
d[14] = 71; //"G", --. , 00001110, ASCII 71
d[15] = 79; //"O", --- , 00001111, ASCII 79
d[16] = 72; //"H", .... , 00010000, ASCII 72
d[17] = 86; //"V", ...- , 00010001, ASCII 86
d[18] = 70; //"F", ..-. , 00010010, ASCII 70
d[19] = 32;
d[20] = 76; //"L", .-.. , 00010100, ASCII 76
d[21] = 32;
d[22] = 80; //"P", .--. , 00010110, ASCII 80
d[23] = 74; //"J", .--- , 00010111, ASCII 74
d[24] = 66; //"B", -... , 00011000, ASCII 66
d[25] = 88; //"X", -..- , 00011001, ASCII 88
d[26] = 67; //"C", -.-. , 00011010, ASCII 67
d[27] = 89; //"Y", -.-- , 00011011, ASCII 89
d[28] = 90; //"Z", --.. , 00011100, ASCII 90
d[29] = 81; //"Q", --.- , 00011101, ASCII 81
d[30] = 32;
d[31] = 32;
d[32] = 53; //"5", ..... , 00100000, ASCII 53
d[33] = 52; //"4", ....- , 00100001, ASCII 52
d[34] = 32;
d[35] = 51; //"3", ...-- , 00100011, ASCII 51
d[36] = 32;
d[37] = 32;
d[38] = 32;
d[39] = 50; //"2", ..--- , 00100111, ASCII 50
d[40] = 32;
d[41] = 32;
d[42] = 32;
d[43] = 32;
d[44] = 32;
d[45] = 32;
d[46] = 32;
d[47] = 49; //"1", .---- , 00101111, ASCII 49
d[48] = 54; //"6", -.... , 00110000, ASCII 54
d[49] = 32;
d[50] = 32;
d[51] = 32;
d[52] = 32;
d[53] = 32;
d[54] = 32;
d[55] = 32;
d[56] = 55; //"7", --... , 00111000, ASCII 55
d[57] = 32;
d[58] = 32;
d[59] = 32;
d[60] = 56; //"8", ---.. , 00111100, ASCII 56
d[61] = 32;
d[62] = 57; //"9", ----. , 00111110, ASCII 57
d[63] = 48; //"0", ----- , 00111111, ASCII 48
while(1){
int m=1;
while (digitalRead(2)==1); //Start abwarten
int ende =0;
while (ende ==0){ //bis zur Zeichenpause
m<<=1;
int t1=0;
while (digitalRead(2)==0){
analogWrite (9,100);
delay (50);
analogWrite (9,0);
t1++;
//Tonlänge messen
}
if (t1>t)
m+=1;
//lang genug? Strich!
int t2=0;
while (digitalRead(2)==1){
delay (50);
t2++;
//Pause messen
if (t2>t) {
ende=1;
//lang genug? Zeichen-Ende!
break;
}
}
}
Serial.print((char) d[m]); //Zeichen drucken
int t3=0;
while (digitalRead(2)==1){
delay (20);
t3++;
//Pause messen
if
(t3>(3*t)){
//lang genug? Wort-Ende
Serial.print (" ");
break;
}
}
}
}
Download: Arduino4.zip
Zurück: ArduinoKurs3.htm
Erweiterungen
Der Reaktionstester wurde mit vereinten Kräften an zwei Stellen
erweitert. Zum einen wurde eine Ausgabe eingebaut, damit man die die
gemessenen Reaktionszeiten im seriellen Monitor sehen kann. Und zum
anderen wurde eine Zufallszeit zwischen den einzelnen Spielen
eingebaut, damit man sich nicht an den gleichmäßigen Takt gewöhnen kann.
//Reaktionstest 6 LEDs
void setup(){
Serial.begin(9600);
DDRD=252;
pinMode(10, INPUT_PULLUP);
}
void loop() {
int n;
int d=0b1111111100000000;
for (n=0; n<6; n++){
d>>=1;
//d=d>>1;
//d=d/2;
//d/=2;
PORTD=d;
delay(100);
if (digitalRead(10)==0) break;
}
Serial.print((n+1)*100);
Serial.println(" ms");
delay (1000);
PORTD=0;
delay (random(500, 1500));
}
Bei den Morseprogrammen kam der Gedanke auf, dass ein Arduino
morsen sollte und ein zweiter die Zeichen empfangen sollte. Deshalb
wurde hier in das Ausgabeprogramm eine Erweiterung eingebaut, mit der
der Port D12 als Sendeleitung dient. Sie muss an D2 des Empfängers
angeschlossen werden. Ein Test hat gezeigt, dass es funktioniert. Zwar
sendet der Sender mit 60 BpM und dem Empfänger ist auf 40 BpM
eingestellt, aber der Empfänger ist tolerant genug gegen Abweichungen
der Geschwindigkeit, sodass die Zeichen fehlerfrei gelesen werden.
void loop() {
char text[] = "HALLO NANO ";
//char text[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ";
char dot = 100; //Punktlänge 100 ms für 60 Buchstaben pro Minute
pinMode(12, OUTPUT);
digitalWrite (12,1);
...
...
if (m&128){ //Strich
analogWrite (9,100);
digitalWrite (12,0);
delay (3*dot);
analogWrite (9,0);
digitalWrite (12,1);
delay(dot);
}
else{ //Punkt
analogWrite (9,100);
digitalWrite (12,0);
delay (dot);
analogWrite (9,0);
digitalWrite (12,1);
delay(dot);
}
Anwendungen und Erweiterungen hierzu im Kurs 5.
Elektronik-Labor Projekte AVR