Für diesen Beitrag werden wir ein Projekt bauen, das Münzen erkennt, die wir in eine Spardose werfen. Ein LCD-Bildschirm informiert uns über den Wert der erkannten Münze. Außerdem speichern wir den Geldbetrag zusammen mit dem Datum auf einer MicroSD-Karte.
Der Weg der Münze führt in den Kasten über eine Rampe. Eine Idee könnte sein, die Vorderseite transparent zu lassen, um den Weg der Münzen und den Schaltkreis in der Spardose zu sehen. Außerdem könnte man eine Schublade zum Sammeln der Münzen einbauen.
Benötigte Materialien
1 |
|
1 |
|
6 |
|
1 |
|
1 |
|
1 |
|
|
|
|
|
|
Benötigte Software
- Arduino IDE
- cash_box Sketch
- DS3231 library (Github, developed by Jarzebski)
- Wire library (integriert)
- LiquidCrystal_I2C library (über Bibliotheksverwalter)
- SD library (integriert)
- SPI library (integriert)
Die Grundidee der Spardose
Die Idee für dieses Projekt ist es, eine Rampe zu bauen, auf der eine Münze hinunterrollt. Sie wird oben und unten von einem Seil gestützt, so dass sie nur auf zwei kleinen Punkten aufliegt. Dadurch ist die Reibung minimal und die Gefahr ist geringer, dass sie stehen bleibt. Wenn die Münze ihren Messpunkt erreicht, wird sie durch eine Öffnung fallen, die die gleiche Größe hat. Der jeweilige Näherungssensor wird die Münze erkennen und wir erhalten dadurch ihren Wert. Auf dem LCD-Bildschirm wird dieser angezeigt, und wir speichern ihn auf der MicroSD-Karte.
Sie fragen sich vielleicht, warum wir für den Zusammenbau Holz und keinen 3D-Drucker verwendet haben. Die Antwort ist, dass Balsaholz sehr leicht und handlich ist. Man kann während der Entwicklung des Projekts mögliche Änderungen vornehmen. Das spart Zeit und Geld. Wenn das Projekt fertig ist, kann man das Gehäuse mit einem schönen Design natürlich auch für einen 3D-Drucker entwerfen.
Die wichtigsten Teile der Struktur sind die Rampe und die Öffnungen für die Münzen. Daraus ergeben sich die Dimensionen der gesamten Box. Wie Sie sehen können, sind die Münzöffnungen nicht in der Reihenfolge des niedrigsten bis höchsten Wertes angeordnet, sondern nach der Größe vom kleinsten zum größten: 10 cts (19 mm), 5 cts (21 mm), 20 cts (22 mm), 1 Euro (23 mm), 50 cts (24 mm) und 2 Euro (25 mm). Der Grund liegt auf der Hand: Würden wir die Öffnungen nach ihrem Wert anordnen, wäre die erste die 5-Cent-Öffnung, was zur Folge hätte, dass die 5-Cent-Münze und die 10-Cent-Münze durch diese erste Öffnung fallen würden, da letztere kleiner ist. Genau wie die 50-Cent-Münze und die 1-Euro-Münze.
Damit jeder Sensor seine Münze richtig erkennt, müssen wir sie einstellen. In meinem Fall musste ich zunächst einige Änderungen am Entwurf vornehmen, da es unmöglich war, die Sensoren zu justieren. Die Sendediode war nicht abgedeckt wodurch das Signal in alle Richtungen abgestrahlt wurde und einige Sensoren das von anderen Sensoren abgestrahlte Signal erfassten. Die erste Lösung, an die ich dachte und die ich umsetzte (wie man auf den Bildern sehen kann) bestand darin, einige Wände zwischen die einzelnen Sensoren zu setzen. Das verbesserte das Verhalten, aber es war immer noch nicht möglich, alle gut einzustellen. Die Signale in der Box prallten in alle Richtungen ab. Also setzte ich um, was in der Anleitung des Moduls steht. Ich nutzte Zylinder von etwa 8 mm (die richtige Stärke der BIC-Stifte) aus weißem Karton.
Ich habe es in schwarz versucht, aber es muss daran liegen, dass diese Farbe Licht absorbiert. Dadurch ist das Licht, das aus der Röhre kommt, sehr schwach und so empfängt der Empfänger kein brauchbares Signal. Ich habe es dann mit der weißen Farbe umgesetzt. Das von jedem Sensor ausgesendete Signal, dass die Röhre verlässt, prallt von der Wand abprallt ab und kehrt nur zum Empfänger seines Moduls zurück. Das ist das Schöne daran, solche Projekte umzusetzen und nach Lösungen für die Probleme zu suchen. Zuerst ist man ein bisschen verzweifelt, aber wenn man die Lösung findet, ist es sehr beruhigend. Jetzt können alle Sensoren individuell richtig eingestellt werden. Es ist wichtig, die Handbücher der verschiedenen Module zu lesen.
Schaltung und Beschreibung der Funktionsweise
Wie im Plan zu sehen ist, besteht die Schaltung aus 6 Näherungssensoren zur Münzerkennung, einem MicroSD-Kartenlesemodul, einem Real Time Clock (RTC)-Modul, einem Nano V3 CH340 Mikrocontroller und einem 16x2 LCDisplay mit I2C-Kommunikationsmodul.
Die gesamte Schaltung wird mit 5V versorgt. Das erste, was wir über die Näherungsdetektoren wissen sollten ist, dass der Ausgang dieses Moduls einen HIGH-Pegel hat, wenn er kein Objekt erkennt. Entgegengesetzt dazu hat er einen LOW-Pegel, wenn er ein Objekt erkennt. Um sicherzustellen, dass das Modul ein Objekt erkannt hat und es kein Fehler war, werden wir im Sketch eine doppelte Prüfung programmieren. Erklärungen dazu gibt es direkt im Sketch. Die Ausgänge der Sensoren sind mit digitalen Pins des Mikrocontrollers verbunden, die als Eingänge konfiguriert werden müssen. Wenn ein Signal am Mikrocontroller ankommt, führen wir die doppelte Prüfung durch, und senden den Wert der Münze für die Ausgabe an den LCD-Bildschirm. Außerdem wird der Wert an das SD-Modul gesendet, um ihn zu speichern.
Es folgt nun die Beschreibung des Sketches. Zu Beginn müssen als erstes alle Bibliotheken geladen werden, die wir für den korrekten Betrieb der Komponenten benötigen.
/* Necessary libraries */ #include <Wire.h> // Library for I2C Bus #include <LiquidCrystal_I2C.h> // Library for I2C LCD display #include <DS3231.h> // Library for Real Time Clock module #include <SD.h> // Library for SD module #include <SPI.h> // Library for SPI Communications
Nachdem wir die Bibliotheken geladen haben, müssen wir das Display konfigurieren. Dabei geben wir die I2C-Adresse, sowie die Anzahl der Spalten und Zeilen an.
/* LCD display implement with 20 column and 2 lines */ LiquidCrystal_I2C lcd = LiquidCrystal_I2C (0x27, 20, 2);
Der nächste Schritt besteht darin, ein clock-Objekt für das RTC-Modul zu instanziieren, um die benötigten Bibliotheksmethoden anwenden zu können, sowie eine Variable zum Speichern des Datums jeder Währungsablesung.
/* Instance for the clock module object DS3231 */ DS3231 clock; // Implementation of the "clock" object. RTCDateTime dt; // Definition of the variable to store the date
Wie im Code zu sehen ist, müssen wir nun die Nummern der digitalen Pins des Mikrocontrollers definieren, an die wir die Näherungssensoren anschließen. Außerdem legen wir den Anfangszustand der Variablen für die Münzerkennung fest. Der sollte zu Beginn natürlich false sein, da noch keine Münze erkannt wurde.
/* Define microcontroller pins to coin sensors */ #define sensor_10_cent 2 #define sensor_5_cent 3 #define sensor_20_cent 4 #define sensor_1_euro 5 #define sensor_50_cent 6 #define sensor_2_euro 7 /* Define the initial state value of the sensors variable (no coin detected) */ bool val_sensor_10_cent = false; bool val_sensor_5_cent = false; bool val_sensor_20_cent = false; bool val_sensor_1_euro = false; bool val_sensor_50_cent = false; bool val_sensor_2_euro = false;
Bevor wir zur setup()-Methode übergehen, müssen wir ein Objekt für unser MicroSD-Kartenlesemodul implementieren und eine Variable initialisieren, in der der Gesamtwert der Münzen gespeichert wird:
/* SD card module object instance */ File object_card_file; // Implementation of the "object_card_file" object. /* Variable to store the value of saved money */ float money_saved = 0;
Nachdem wir die erforderlichen Variablen, Objekte und Parameter definiert haben, müssen wir die Initialisierung der Schaltungskomponenten in der Methode setup() konfigurieren. Als erstes initialisieren wir die serielle Konsole, damit wir die vom Mikrocontroller gesendeten Nachrichten sehen können.
Serial.begin(9600); // Initializing the Serial Console
Nun müssen wir die Pins der Sensoren als Eingänge initialisieren:
/* Define the microcontroller pins of the sensors as input */ pinMode(sensor_10_cent, INPUT); pinMode(sensor_5_cent, INPUT); pinMode(sensor_20_cent, INPUT); pinMode(sensor_1_euro, INPUT); pinMode(sensor_50_cent, INPUT); pinMode(sensor_2_euro, INPUT);
Als Nächstes initialisieren wir das LCDisplay. Wir aktivieren die Display-Beleuchtung und zeigen eine Meldung an, während alle anderen Module der Schaltung initialisiert werden:
/* LCD display */ lcd.init(); // Initializing LCD display lcd.backlight(); // LCD display blacklight on lcd.print("Initializing ... "); // Message on the LCD display while the project is initializing
Jetzt müssen wir das RTC-Modul initialisieren, das tun wir mit der folgenden Zeile:
/* Initializing DS3231 Module */ clock.begin();
Das letzte zu initialisierende Modul ist der MicroSD-Kartenleser. Zuerst lassen wir uns auf der seriellen Konsole die Meldung anzeigen, dass die Initialisierung des Moduls beginnt. Die Bedingung prüft, ob der CS-Pin (Chip Select) des Moduls mit dem Pin 10 des Mikrocontrollers verbunden ist. Gibt die Funktion false zurück, wird auf dem seriellen Monitor ein Fehler angezeigt und mit while(1) eine Endlosschleife ausgeführt. Andernfalls wird eine erfolgreiche Initialisierung angezeigt und das Programm fortgeführt:
/* Initialize card module */ Serial.print ("Initializing microSD module ..."); // Initialization message microSD module in Serial Monitor if (!SD.begin(10)) { // If there isn't microSD module Serial.println("Failed to initialize."); // Show "Initialization failure" message while(1); } Serial.println("Successful initialization"); // Successful initialization
Anschließend führen wir die Methode read_data() aus, um den Inhalt der Datei cardfile.txt in das Objekt object_card_file zu laden. Mit der Methode SD.open öffnen wir die Datei cardfile.txt und zeigen den Inhalt der Datei in der seriellen Konsole an. Dann schließen wir den Inhalt des Objekts object_card_file. Wenn das Objekt nicht verfügbar ist, wird die Meldung angezeigt, dass keine Informationen vorhanden sind.
read_data(); // Go to read_data method
.
.
.
void read_data() { // read_data method object_card_file = SD.open("cardfile.txt"); // Open the file "cardfile.txt" if (object_card_file) { // If the object file exists while (object_card_file.available()) { // Loop to display the file data on the serial console. Serial.write(object_card_file.read()); } object_card_file.close(); // Close the file. } else { Serial.println("No file to display"); // Display serial console message of file failure. } }
Wenn die read_data()-Methode beendet ist, kehrt das Programm zum Ausgangspunkt zurück und positioniert den Cursor im Display in Spalte 0 und Zeile 0, d.h. in der ersten Stelle des LCD-Bildschirms mit der Anweisung lcd.setCursor(0, 0). Danach werden die aktuellen Werte ausgegeben:
void lcd_message() { // lcd_message method. lcd.clear(); // Clean LCD display. lcd.setCursor(0, 0); // Position the cursor in real column 1 and real row 1. lcd.print("Total money saved: "); // Display the message on screen lcd.setCursor(1, 1), // Position the cursor in real column 2 and real row 2. lcd.print(money_saved); // Display the message on screen. lcd.setCursor(10, 1); // Position the cursor in real column 10 and real row 2. lcd.print("Euros"); // Display the message on screen. save_data(); // Go to save_data method. }
Die Kommentare der vorangegangenen Methode erläutern hinreichend die Aktionen der einzelnen Zeilen. Am Ende dieser Methode erfolgt ein Aufruf der Methode save_data(), auch dort sind die Befehle ausführlich kommentiert:
void save_data() { // save_data method. object_card_file = SD.open("cardfile.txt", FILE_WRITE); // If there is a card, open the file "cardfile.txt" or create it and open it if it does not exist. if(object_card_file) { // If the file exists, the following is written in a single line in the file. object_card_file.print("Total money saved: "); // Write the text between the quotation marks. object_card_file.print(money_saved); // Write the value of money_saved variable. object_card_file.print(" Euros ---> Date: "); // Write the text between the quotation marks. object_card_file.print(dt.year); // Write de year. object_card_file.print("-"); // Write the text between the quotation marks. object_card_file.print(dt.month); // Write de month. object_card_file.print("-"); // Write the text between the quotation marks. object_card_file.print(dt.day); // Write the day. object_card_file.println(" "); // Type a blank space and line break. object_card_file.close(); // Close the file. } else { Serial.println("Fail open file"); // Display Serial Console message of file open failure. } }
Mit der Ausführung der vorherigen Methode endet der setup()-Block unseres Codes und es wird die loop()-Methode ausgeführt. Diese Methode prüft, ob wirklich eine Münze erkannt wurde, speichert den Wert in der Variablen money_saved, zeigt ihn auf dem LCD-Bildschirm an und speichert Datum sowie Uhrzeit in der dazugehörigen Variablen. Sehen wir uns an, jeder der 6 Sensoren seine Münze erkennt. Der unten stehende Code gilt für jeden Sensor. Ändern Sie einfach die Sensorvariable und denken Sie daran, dass der Sensor ein HIGH-Pegel-Signal an den Mikrocontroller sendet, wenn er keine Münze erkennt und ein LOW-Pegel-Signal, wenn er eine Münze erkennt.
Für die Erkennung verwenden wir zwei doppelte Bedingungen, wobei die erste if-Anweisung prüft, ob am entsprechenden Pin weiterhin HIGH-Pegel anliegt. Wenn dies der Fall ist, wird der Wert der Variablen val_sensor_10_cent nicht geändert und die Bedingung verlassen. Wenn aber der Pegel auf LOW wechselt, wird der else-Teil ausgeführt. Darin wird geprüft, ob es sich um eine falsche Ablesung handelt. Dann wird 50 Mikrosekunden gewartet und die Ablesung erneut geprüft, diesmal mit der zweiten if-Anweisung. Wenn der Pin weiterhin einen HIGH-Pegel hat, bedeutet dies, dass die vorherige Zustandsänderung eine fehlerhafte Ablesung war. Dann fährt das Programm fort, ohne den Wert der Variablen val_sensor_10_cent zu ändern und verlässt die Bedingung. Wenn dagegen ein LOW-Pegel anliegt, bedeutet dies, dass er weiterhin ein Objekt (in dem Fall die Münze) erkennt und den Wert der Variablen val_sensor_10_cent auf true ändert.
/**** 10 cent coin detection process ****/ // When the sensor detects an object, it sends a LOW level to the Arduino. if(digitalRead(sensor_10_cent)) { // Check sensor: if sensor output is HIGH, val_sensor_10_cent = false; // no coin is detected and jumps to the next coin check. } else { // Then when currency is detected, the sensor reading is rechecked. delayMicroseconds(50); // Pause for 50 milliseconds to check again. if(digitalRead(sensor_10_cent)) { // if now the sensor output is HIGH, val_sensor_10_cent = false; // then the first reading was erroneous and } // it is out of the double conditional. else { // But if the reading is low again, val_sensor_10_cent = true; // then if currency was detected and the variable changes to TRUE. } }
Wenn sich der Wert der Variablen val_sensor_10_cent auf true ändert, wird die folgende einfache Bedingung ausgeführt und zeigt auf dem seriellen Monitor die Münzerkennungsmeldung von 10 Cent an. Dann wird der Wert der Variablen money_saved geladen, zu dem die 10 Cent addiert werden. Der neue Wert wird dann in der Variablen gespeichert. Schließlich wird die oben beschriebene Methode lcd_message ausgeführt.
if(val_sensor_10_cent == true) { // If the variable has the value TRUE, Serial.println("10 cent coin detected"); // message on the serial console of the detected coin value. money_saved = (money_saved + 0.10); // Add the value of the detected currency to the variable and save it again. lcd_message(); // Go to lcd_message method. }
Diese beiden Bedingungen sind für jeden der sechs Sensoren implementiert und die Erklärung ist für alle die gleiche. Am Ende der loop()-Methode wird der Wert des aktuellen Tages, Monats, Jahres und der Uhrzeit in der Variablen dt gespeichert, die wir im RTC-Modul deklarieren, die Zeile ist:
dt = clock.getDateTime(); // Save the data provided by the Real Time Clock module of the time and date in the variable dt.
Ich hoffe, dass Sie dieses Projekt interessant fanden und wünsche Ihnen viel Spaß damit.
2 Reacties
Andreas Wolter
@Günther Hinneburg: das sieht nicht nach Fehlermeldungen aus. Könnten Sie die genaue Meldung posten?
Haben Sie in den Voreinstellungen für “Ausführliche Ausgabe während” einen Haken gesetzt?
Da es scheinbar funktioniert, gehe ich davon aus, dass es sich nicht um schwerwiegende Probleme handelt.
Meistens sind es fehlerhafte Typkonvertierungen oder etwas ähnliches.
Grüße,
Andreas Wolter
AZ-Delivery Blog
Günther Hinneburg
Sehr geehrte Damen und Herren, ich habe den oben genannten Sketch kopiert. Funktionierte auch alles super. Jedoch habe ich ein Problem beim kompilieren erhalte ich zwei Meldungen:
18 | DS3231 clock; // Implementation of the “clock” object.
19 | RTCDateTime dt; // Definition of the variable to store the date
Was mache ich falsch?
Mit freundlichen Grüßen
Günther Hinneburg