# 10.04.2025 Microcontroller am Beispiel von Morsecode ## 1. Einleitung ### 1.1 Motivation Das Ziel dieses fachlichen Versuchs ist es, die Grundlagen der Mikrocontroller-Programmierung anhand des Arduino-Systems zu erlernen und anzuwenden. Dabei wird ein Arduino genutzt, um Morsecode-Signale zu erzeugen und zu dekodieren. Dies ermöglicht ein tieferes Verständnis für digitale Signalverarbeitung, Timing-Steuerung und serielle Kommunikation. Durch den Versuch sollen folgende Kompetenzen erworben werden: - Grundlegende Programmierung und Steuerung eines Mikrocontrollers (Arduino) - Umsetzung eines Kommunikationssystems mit Morsecode - Anwendung von LED zur Erzeugung von Morsezeichen - Grundlagen der Signalverarbeitung und Decodierung von Zeichen Dieser Versuch verbindet praktische Mikrocontroller-Anwendungen mit einem klassischen Kommunikationsverfahren. ### 1.2 Aufgabenstellung Im Rahmen dieses Projekts sollte ein Arduino-Mikrocontroller so programmiert werden, dass er Morsecode sowohl ausgeben als auch empfangen kann. Dabei lag der Fokus auf zwei Teilaufgaben: 1. Ausgabe: Ein über die serielle Konsole eingegebener Text sollte in Morsecode übersetzt und über verschiedene Ausgabemedien (Konsole, LED, LCD-Display) dargestellt werden. Dazu sollte die Morsecode-Logik mithilfe einer Bibliothek umgesetzt und auf verschiedene Hardware-Komponenten übertragen werden. 2. Eingabe: Über ein Mikrofonmodul sollten Tonimpulse (kurz/lang) erkannt und interpretiert werden. Die Tonfolgen sollten gespeichert und auf der Konsole ausgegeben werden. Ziel war es, zwischen kurzen und langen Tönen zu unterscheiden und diese als Morsezeichen zu erfassen. #### 1.2.1 Aufgabenverteilung Franka Schmid und Max Grauvogl kümmerten sich um die Ausgabe des Morse Codes, erst auf die Console, dann auf ein LCD-Display. Dabei war Franka hauptsächlich für das Programmieren zuständig und Max für die Anbindung der Hardware. Verena Feike und Chieme Hangen waren für die Morse Code Eingabe zuständig. Dabei waren beide bei allen Arbeitschritt dabei. ## 2. Grundlagen ### 2.1 Arduino #### 2.1.1 Was ist Arduino? Arduino ist eine Open-source Plattform für die Entwicklung von Microcontroller Projekten. Hierfür wird ein Arduino Board (Hardware) und die Arduino IDE(Software) benötigt. #### 2.1.2 Aufbau eines Arduino Boards (Hardware) Es gibt unterschiedliche Boards, welche unterschiedliche Größen, Leistungsstufen oder verschiedene Anzahl von In- und Outputpins haben. Hierzu gehören beispielsweise - Arduino Uno - Arduino Mega - Arduino Nano #### 2.1.3 Wichtigste Komponenten: Ein Arduino Board enthält normalerweise folgende Hauptkomponenten: ![alt text](arduino.png) 1. Microcontroller- führt die Programme aus 2. USB-Port- Ermöglicht die Verbindung des Arduino-Boards zum Computer 3. USB to Serial Chip- Übersetzt Daten vom Computer für den Microcontroller; macht die Programmierung vom Computer aus möglich 4. Digital pins (0,1)- Ein- und Ausgang für digitale Signale, z.B. zum An- und Auschalten von LEDs 5. Analog pins- Zum Lesen von analogen Werten (0-1023) 6. 5V /3.3V pins- Für externe Komponenten 7. GND- "Ground", "Negative", "-"; Masseanschluss für den Stromkreis 8. VIN- (Voltage In) für externe Spannungsquellen ### 2.2 Arduino IDE (Software) Die IDE wird zum Schreiben, Kompilieren und Hochladen von Programmen genutzt. Diese kann unter ww.arduino.cc heruntergeladen und dann installiert werden. #### 2.2.1 Aufbau eines Arduino-Programms Jedes Programm hat folgende Funktionen: ``` void setup() { pinMode(13, OUTPUT); // Pin 13 als Ausgang setzen } void loop() { digitalWrite(13, HIGH); // LED an delay(1000); // 1 Sekunde warten digitalWrite(13, LOW); // LED aus delay(1000); } ``` - void setup() wird einmalig zu Beginn des Programms ausgeführt. Setzen von wichtigen Ein- und Ausgängen. - void loop() wird immer wieder in einer Schleife ausgeführt #### 2.2.2 Elektronik & Zubehör ##### LEDs Bei einer LEDs muss ein Vorwiderstand genutzt werden. Dieser verhindert, dass zu viel Strom durch die LED fließt und diese dadurch beschädigt wird. ##### Sensoren /Aktoren Ein Arduino kann mit Sensoren und Aktoren verbunden werden, z.B. Temperatursensoren, Lichtsensoren, Geräuschsensoren. Aktoren wären beispielsweise Servomoteren. ##### Arduino Boards Für unsere Projekte reicht ein Arduino Uno. Dieser ist besonders für Anfänger geeignet. Ein Vergleich der wichtigsten Boards: Board|Arduino Uno | Arduino Mega | Arduino Micro -----|------------|--------------|--------------- PINS, digital | 14 | 54 | 20 PINS, digital mit PWM |6 |15 | 7 PINS, analog | 6 | 16 | 12 Taktrate | 16 MHz | 16MHz | 16MHz Flashspeicher | 32kB | 256 kB | 32 kB SRAM | 2 | 8 | 2,5 Processor | ATmega328P | ATmega 2560 | ATmega32U4 Auf dem Arduino Mega können somit deutlich längere Programme hochgeladen werden. Alle Boards sind gleich schnell, da sie alle die gleiche Taktrate haben. Je größer der SRAM desto mehr Variablen kann der Arduino bereitstellen und verarbeiten. ## 3. Versuchsdurchführung Zwei seperate Versuche wurden durchgeführt: Ein Versuch zur Ausgabe und ein Versuch zur eingabe des Morse Codes. ### 3.1 Ausgabe von Morsecode Um mithilfe eines Arduinos via Morsecode zu kommunizieren, ist eine geeignete Ausgabe des generierten Morsecodes erforderlich. Dies kann beispielsweise über die serielle Konsole der Arduino-IDE, eine LED oder ein externes LCD-Display erfolgen. #### Nutzereingabe über die serielle Konsole Zunächst muss es möglich sein Text in der Konsole der Arduino IDE einzugeben. In der Methode `setup()` fügen wir dafür folgenden Code hinzu: `Serial.begin(9600);` Dies wird benötigt um die serielle Kommunikation zwischen Arduinoboard und dem Computer/ der Arduino IDE zu realisieren. Die `loop()-Funktion` wird kontinuierlich vom Arduino aufgerufen. Hier ergänzen wir den Code zur Verarbeitung der Benutzereingabe: ``` if (Serial.available() > 0) { String input = Serial.readStringUntil('\n'); Serial.print("Du hast eingegeben: "); Serial.println(input); } ``` Nun wird der auf der Konsole eingegebene Text auf der Konsole ausgegeben. #### Übersetzung in Morsecode Zur Übersetzung des eingegebenen Textes in Morsecode verwenden wir die Bibliothek `Morse.h`, welche die eigentliche Konvertierung übernimmt. Innerhalb einer Funktion `receiver()` definieren wir, wie mit dem übersetzten Zeichen umgegangen werden soll – in diesem Fall wird es einfach auf der Konsole ausgegeben: ``` #include "Morse.h" Morse morse; void receiver(char e) { Serial.print(e); } ``` Mit dem Aufruf `morse.println(input);` kann der Input übersetzt und behandelt werden. Diese Zeile wird der `loop()-Methode` hinzugefügt. ``` void loop() { if (Serial.available() > 0) { String input = Serial.readStringUntil('\n'); Serial.print("Du hast eingegeben: "); Serial.println(input); morse.println(input); } } ``` ##### Morsecode-Logik in der transmiter()-Funktion In einer separaten Funktion `transmiter()` wird definiert, welches Symbol wie dargestellt werden soll: ``` void transmiter(uint8_t e) { if (e == MORSE_CHAR) { Serial.print("/"); } else if (e == MORSE_SPACE){ Serial.print(" "); } else if (e == MORSE_EOL){ Serial.println(" EOL "); } else if (e == MORSE_DI || e == MORSE_DIT){ Serial.print("."); } else if (e == MORSE_DAH){ Serial.print("-"); } } ``` Die Bibliothek übersetzt die Buchstaben der Benutzereingabe in Integer-Werte, die mit bestimmten Konstanten (Enums) verknüpft sind: - MORSE_CHAR: Ein Buchstabe ist abgeschlossen - MORSE_SPACE: Ein Leerzeichen - MORSE_EOL: Zeilenende - MORSE_DI bzw. MORSE_DIT: Kurzer Ton (Punkt) - MORSE_DAH: Langer Ton (Strich) Nun wird überprüft, welchem dieser Varianten der Typ entspricht und das ensprechende Zeichen wird auf der Konsole ausgegeben. Diese beiden Methoden müssen jetzt nur noch dem Morse-Modul innerhalb der setup() Methode übergeben werden: `morse.begin(receiver, transmiter);` Der Code soll nun so erweitert werden, dass auch eine LED und ein LCD Display den Morsecode anzeigen. #### Anzeigen mithilfe einer LED Für die LED muss zunächst die richtige LED angesteuert werden. Dafür ergänzen wir die Zeile `pinMode(LED_BUILTIN, OUTPUT);` vor `morse.begin(receiver, transmiter);` in der `setup()-Methode`. Die Transmitter Methode passen wir nun so an, dass im Falle eines MORSE_DI, MORSE_DIT oder MORSE_DAH die Lampe für einen gewissen Zeitraum leuchtet: ``` void transmiter(uint8_t e) { if (e == MORSE_GAP){ delay(DIT_DURATION); } if (e == MORSE_CHAR){ Serial.print("/"); delay(DAH_DURATION); } else if (e == MORSE_SPACE){ Serial.print(" "); delay(7 \* DIT_DURATION); } else if (e == MORSE_EOL) Serial.println(" EOL "); else { digitalWrite(LED_BUILTIN, HIGH); if (e == MORSE_DI || e == MORSE_DIT){ Serial.print("."); delay(DIT_DURATION); } else if (e == MORSE_DAH){ Serial.print("-"); delay(DAH_DURATION); } digitalWrite(LED_BUILTIN, LOW); delay(DIT_DURATION); } } ``` Die Funktion `delay()` wird verwendet um Pausen zwischen den Zeichen einzufügen und um die LED für die gewünschte Zeit leuchten zu lassen. Mit `digitalWrite(LED_BUILTIN, HIGH);` wird die LED angeschalten. Je nach dem ob das Zeichen ein kurzer oder ein langer Laut ist, leuchtet die LED nun für einen gewissen Zeitraum. `digitalWrite(LED_BUILTIN, LOW);` lässt die LED erlöschen. #### Anzeigen mithilfe eines LCD Displays Analog zur Anzeige auf der Konsole soll der Text nun auch auf einem externen Display angezeigt werden. Hierfür müssen in der Setup Methode die Ansteuerungen für die einzelnen Pins definiert werden: ``` #include #include "Morse.h" Morse morse; const int DIT_DURATION = 200; const int DAH_DURATION = 3 * DIT_DURATION; const int rs = A3, en = A5, d4 = A9, d5 = A10, d6 = A11, d7 = A12; LiquidCrystal lcd(rs, en, d4, d5, d6, d7); void setup() { Serial.begin(9600); pinMode(A14,OUTPUT); pinMode(A13,OUTPUT); pinMode(A4,OUTPUT); pinMode(A0,OUTPUT); pinMode(A2,OUTPUT); pinMode(A1,OUTPUT); digitalWrite(A14,LOW); digitalWrite(A13,HIGH); digitalWrite(A4,LOW); digitalWrite(A0,LOW); digitalWrite(A2,LOW); digitalWrite(A1,HIGH); pinMode(LED_BUILTIN, OUTPUT); morse.begin(receiver, transmiter); } ``` In der transmitter() Methode wird abschließend noch unter jedem `Serial.print(c);` Aufruf ein analoger `lcd.print(c);` Aufruf eingefügt. ### 3.2 Eingabe von Morse Codes #### 3.2.1 Ziel Über das Toneingabegerät soll eine Folge aus langen und kurzen Tönen eingegeben werden können. Diese soll anschließend auf der Kommandozeile ausgegeben werden. #### 3.2.2 Versuchsaufbau Als Toneingabegerät wurde das **KY-038 LM393 Sound Detection Module** verwendet. Die digitale Ausgabe dieses Moduls kam dabei zum Einsatz. Zusätzlich wurde eine LED eingebaut, die aufleuchtet, sobald ein Ton erkannt wird. ``` int micPin = 2; int ledPin = 13; int micValue = 0; int ledState = 0; int index = 0; int arrayMaxLength = 10; bool* text = new bool[arrayMaxLength]; // Dynamically allocated array ``` Im ersten Codeabschnitt werden die benötigten Variablen deklariert und initialisiert. Der Mikrofoneingang ist an Pin 2 angeschlossen, die LED an Pin 13. Das Array `text` dient zur Speicherung der erkannten Tonfolge, wobei jeder Eintrag einen kurzen (false) oder langen Ton (true) repräsentiert. Der `index` markiert die stelle des Arrays, die zuletzt beschrieben wurde. ``` void setup() { for (int i = 0; i < arrayMaxLength; i++) { text[i] = false; // Initialize all values to false } // Variables for holding the mic value and led state pinMode(micPin, INPUT); // Configures the sound sensor pin as input pinMode(ledPin, OUTPUT); // Configures the LED pin as output Serial.begin(9600); } ``` In der `setup()` methode werden alle Werte im `text`-array mit `false` initialisiert. Außerdem werde der Pinmode für das Microphon und die LED gesetzt. Zum Schluss wird die serielle Schnittstelle mit einer Baudrate (Bit pro Sekunde) von 9600 zur Ausgabe auf der Konsole gestartet. ``` void loop() { // Read the sound sensor value micValue = digitalRead(micPin); // Check if the sound sensor has detected noise if (micValue == LOW) { digitalWrite(ledPin, LOW); } else { digitalWrite(ledPin, HIGH); // Toggles the LED state digitalWrite(micPin, 0); delay(300); // Pauses for 300ms seconds micValue = digitalRead(micPin); if (micValue == HIGH) { text[index] = true; Serial.println("lang"); } else { text[index] = false; Serial.println("kurz"); } index++; ``` Zu Beginn der `loop()`-Methode wird zunächst der aktuelle Wert des Mikrofonsensors ausgelesen. Wird ein Ton erkannt, leuchtet die LED auf, und das Programm pausiert für 300 Millisekunden. Anschließend wird der Sensorwert erneut ausgelesen, um zu entscheiden, ob es sich um einen langen oder kurzen Ton handelt. Ein langer Ton wird durch ein erneut erkanntes Signal (HIGH) nach der Pause bestimmt und im Array als `true` gespeichert. Ein kurzer Ton hingegen wird als `false` abgespeichert. Zusätzlich wird der erkannte Ton ("lang" oder "kurz") über die serielle Schnittstelle ausgegeben. ``` // wait till sound is gone to start next input do { digitalWrite(micPin, 0); delay(100); micValue = digitalRead(micPin); } while (micValue == HIGH); delay(300); Serial.print("next input:"); digitalWrite(ledPin, LOW); } ``` Danach wird gewartet bis der Ton vorbei ist, damit nicht ein sehr langer Ton fälschlicherweise als mehrere kurze Töne erkannt wird. Dazu wird im Abstand von 100 Millisekunden überprüft, ob weiterhin ein Signal anliegt. Sobald kein vorhanden ist, wird ein letzes Mal 300ms gewartet. Danach wird die LED ausgeschaltet und über die serielle Schnittstelle die Meldung "next input:" ausgegeben – dies signalisiert, dass nun der nächste Ton eingegeben werden kann. ``` // extend input array, if neccessary if (index == arrayMaxLength) { // Create a new larger array bool* temp = new bool[arrayMaxLength * 2]; // Copy old data to new array for (int i = 0; i < arrayMaxLength; i++) { if (text[i]) { Serial.print("-"); } else { Serial.print("."); } temp[i] = text[i]; } Serial.println("end line"); // Free the old array and point text to the new one delete[] text; text = temp; // Update arrayMaxLength arrayMaxLength *= 2; } } ``` Wenn die maximale Länge des Arrays erreicht ist, wird das Array dynamisch vergrößert. Dazu wird ein neues Array mit der doppelten Größe erstellt, und alle bisherigen Einträge werden in das neue Array kopiert. Währenddessen wird zur Kontrolle die bisher gespeicherte Tonfolge in Form von Punkten (für kurze Töne) und Strichen (für lange Töne) auf der Konsole ausgegeben. Anschließend wird das alte Array freigegeben und der Zeiger text auf das neue, größere Array gesetzt. Die Variable arrayMaxLength wird entsprechend angepasst. Der ganze Code nochmal zusammengesetzt: ``` int micPin = 2; int ledPin = 13; int micValue = 0; int ledState = 0; int index = 0; int arrayMaxLength = 10; bool* text = new bool[arrayMaxLength]; // Dynamically allocated array void setup() { for (int i = 0; i < arrayMaxLength; i++) { text[i] = false; // Initialize all values to false } // Variables for holding the mic value and led state pinMode(micPin, INPUT); // Configures the sound sensor pin as input pinMode(ledPin, OUTPUT); // Configures the LED pin as output Serial.begin(9600); } void loop() { // Read the sound sensor value micValue = digitalRead(micPin); // Check if the sound sensor has detected noise if (micValue == LOW) { digitalWrite(ledPin, LOW); } else { digitalWrite(ledPin, HIGH); // Toggles the LED state digitalWrite(micPin, 0); delay(300); // Pauses for 300ms seconds micValue = digitalRead(micPin); if (micValue == HIGH) { text[index] = true; Serial.println("lang"); } else { text[index] = false; Serial.println("kurz"); } index++; // wait till sound is gone to start next input do { digitalWrite(micPin, 0); delay(100); micValue = digitalRead(micPin); } while (micValue == HIGH); delay(300); Serial.print("next input:"); digitalWrite(ledPin, LOW); } // extend input array, if neccessary if (index == arrayMaxLength) { // Create a new larger array bool* temp = new bool[arrayMaxLength * 2]; // Copy old data to new array for (int i = 0; i < arrayMaxLength; i++) { if (text[i]) { Serial.print("-"); } else { Serial.print("."); } temp[i] = text[i]; } Serial.println("end line"); // Free the old array and point text to the new one delete[] text; text = temp; // Update arrayMaxLength arrayMaxLength *= 2; } } ``` #### 3.3.3 Ergebnisse Kurze Töne werden zuverlässig erkannt. Leider hat das Programm Schwierigkeiten, lange Töne korrekt zu erfassen – diese werden manchmal fälschlicherweise als mehrere kurze Töne abgespeichert. Die Ursache dafür könnte entweder in einer ungenauen Implementierung oder in den Einschränkungen des verwendeten Moduls liegen. ## 4. Zusammenfassung In diesem Versuch wurde die Funktionsweise eines Mikrocontrollers am Beispiel der Morsecode-Kommunikation praktisch erprobt. Dabei wurde ein Arduino verwendet, um Texteingaben in Morsecode zu übersetzen und über Konsole, LED und LCD-Display auszugeben. Die Eingabe von Morsecode erfolgte über ein Mikrofonmodul, wobei kurze und lange Töne unterschieden und in einem Array gespeichert wurden. ### Probleme Die Ausgabe funktionierte zuverlässig über alle Medien hinweg. Die Eingabe hingegen zeigte Schwächen bei der Erkennung langer Töne, was auf die begrenzte Genauigkeit des Sensors oder Optimierungsbedarf im Code zurückzuführen sein könnte. Insgesamt konnte durch den Versuch ein tieferes Verständnis für Mikrocontroller-Programmierung, digitale Signalverarbeitung und einfache Kommunikationsprotokolle gewonnen werden. ## 5. Literaturverzeichnis Söderby, Karl (2024): Getting Started with Arduino, [online] https://docs.arduino.cc/learn/starting-guide/getting-started-arduino/?_gl=1*cseioa*_up*MQ..*_ga*OTE5MTU0NDEwLjE3NDQwMjY4NjA.*_ga_NEXN8H46L5*MTc0NDAyNjg1OS4xLjAuMTc0NDAyNjg1OS4wLjAuNzI2OTA2MTYz [abgerufen am 07.04.2025] Gudino, Miguel (2017): Arduino Uno, Mega und Micro im Vergleich, [online] https://www.arrow.de/research-and-events/articles/arduino-uno-vs-mega-vs-micro erstellt am 22.12.2017 [abgerufen am 07.04.2025] arunav1 (2022): Easiest way to connect LCD screen to Arduino mega!, [online] https://projecthub.arduino.cc/arunav1/easiest-way-to-connect-lcd-screen-to-arduino-mega-0fc95b, erstellt am May 11, 2022 [abgerufen am 07.04.2025] Weis, Thomas (2018): Arduino Sound Sensor Modul – Tutorial, [online] https://www.makerblog.at/2018/11/arduino-sound-sensor-modul-tutorial/ [abgerufen am 09.04.2025]