The following article was made by the guest author Bastian Brumbi made available:
After we im second part We will now add the operation from a visual perspective, we will now add a Rotary Encoder for simpler operation to set the volume.
Hardware
The following products are required from the previous parts:
In addition, are also required:
- Print button
- Rotary
- Punching board
- Dupont cable
- Linen
- Screw (M2 m3),
- Thread melting inserts (M3)
- PCB spacer
You need one on tools soldering iron With accessories and one 3D printer.
In order for the rotary code to work reliably, the CLK and you PIN must be connected to an interrupt pin of the microcontroller. Our Nano Microcontroller has two interrupt-capable pins, D2 and D3.
The first thing we can do is soldered up to the PCB, followed by pen strips for the external components.
The yellow lines are soldered under the PCB and the oranges on the top.
Before you put the circuit into operation, check that no short circuits were created by soldering on the hole grid.
Next we connect the other components with dupont cables. In order for the cabling to become more decent, it is recommended to crime the cables in the correct length. If you do not have the appropriate tools, you can also Pre -crimped F2F cable use.
For the clarity, all voltage and mass compounds at the top are not shown directly, these must be connected to the three pen strips on the top right at the top right. The RFID module requires a operating voltage of 3.3V, for this there is a pen strip at the bottom left.
For the housing, the two files (Housing 1, Housing 2) can be printed out with a 3D printer. The part with the large oval hole must be printed in white or other light -translucent color, since the LED ring is attached behind it.
When the two parts have been printed, the thread inserts are pressed into the larger 6 holes with a hot soldering iron. In the end, all components are attached with appropriate (M2 or M3) screws.
software
The following libraries must be installed:
Mfrc522
Dfrobot dfplayermini
Adafruit SSD1306
Adafruit neopixel
In addition to the Libraries from the previous parts, we now need the encoder library.
As usual, the libraries can be installed via the integrated library manager or as a .zip file in the Arduino IDE.
Copy or load the following code to Arduino IDE, select the right board and port and load the program by pressing the upload button onto the board (Download).
First, the libraries used are integrated.
#include <Arduino.h> #include <Spi.h> #include <Mfrc522.h> #include "DfrobotdfPlayermini.h" #include <Software.h> #include <Adafruit_neopixel.h> #include <Wire.h> #include <Adafruit_gfx.h> #include <Adafruit_ssd1306.h> #include <Encoder.h>
Next, symbolic constants are created that create objects of the libraries and created global variables. Some of the following methods are taken from the previous blog posts.
#define RX 15 #define Tx 14 #define LED 4 #define Dwn 5 #define OK 6 #define Up 7 #define Rst_pin 9 #define Ss_pin 10 #define CLK 2 #define Dt 3 #define SW 8 #define Screen_address 0x3c Encoder encoder(Dt,CLK); Adafruit_neopixel ring(12, LED, Neo_grb + Neo_khz800); Adafruit_ssd1306 display(128, 32, &Wire, -1); Software softserial(RX, Tx); Dfrobotdfplayermini mydfplayer; Mfrc522 mfrc522(Ss_pin, Rst_pin); Mfrc522::Mibare_key key; Mfrc522::Status code status; byte sector = 1; // position in the memory byte blockadr = 4; byte data block[] = { // buffer to describe 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; byte buffer[18]; // buffer for reading byte trailer block = 7; Bool State = true; const intimately Filecount = 4; // !!! Number of files, please adjust !!!
The arrays title and duration must be filled with the current data as in the previous part.
String title[Filecount] = {"TXT1", "Txt2", "Txt3", "Txt4"}; // title to be displayed intimately duration[Filecount] = {293, 191, 354, 123}; // duration in seconds intimately stop = 0; intimately start time = 0; intimately correction = 0; intimately Currfile = 0; byte Volume = 20; long position; void readcard() { // Read the RFID Stack -> Buffer IF ( ! mfrc522.Picc_readcarderial()) return; // card cannot be read -> demolition byte size = Sizeof(buffer); status = (Mfrc522::Status code) mfrc522.PCD_authenticate(Mfrc522::Picc_cmd_mf_auth_key_a, trailer block, &key, &(mfrc522.uid)); IF (status != Mfrc522::Status_ok) { Serial.print(F("PCD_authenticate () Failed:")); Serial.print(mfrc522.Get status code name(status)); return; } status = (Mfrc522::Status code) mfrc522.MIFARE_READ(blockadr, buffer, &size); IF (status != Mfrc522::Status_ok) { Serial.print(F("MIFARE_READ () Failed:")); Serial.print(mfrc522.Get status code name(status)); } mfrc522.Picc_halta(); mfrc522.PCD_StopCrypto1(); } void writecard() { // Writing the DataBlock -> RFID Stack IF ( ! mfrc522.Picc_readcarderial()) return; // card cannot be read -> demolition status = (Mfrc522::Status code) mfrc522.PCD_authenticate(Mfrc522::Picc_cmd_mf_auth_key_a, trailer block, &key, &(mfrc522.uid)); IF (status != Mfrc522::Status_ok) { Serial.print(F("PCD_authenticate () Failed:")); Serial.print(mfrc522.Get status code name(status)); return; } status = (Mfrc522::Status code) mfrc522.MIFARE_WRITE(blockadr, data block, 16); IF (status != Mfrc522::Status_ok) { Serial.print(F("MIFARE_WRITE () Failed:")); Serial.print(mfrc522.Get status code name(status)); } mfrc522.Picc_halta(); mfrc522.PCD_StopCrypto1(); }
The following method was expanded to scroll the text to the right:
void display text(String text){ display.Clear display(); display.SettextSize(2); display.SettextColor(SSD1306_White); display.setcursor(5,10); display.print(text); display.display(); display.Start scrollright(0x00,0x0f); } void startcard() { readcard(); // new card IF(buffer[0] != 1) { // marker not available mydfplayer.play(1); intimately I = 1; display text((String)I); while(true) { IF(!digital read(Up)) { while(!digital read(Up)) delay(20); IF(I < Filecount) { I+=1; mydfplayer.next(); } Else { I = 1; mydfplayer.play(1); } display text((String)I); } IF(!digital read(Dwn)) { while(!digital read(Dwn)) delay(20); IF(I > 1) { I-=1; mydfplayer.preview(); } Else { I = Filecount; mydfplayer.play(Filecount); } display text((String)I); } IF(!digital read(OK)) { // confirm and describe display text("Write"); while(!digital read(OK)) delay(20); data block[0] = 1; data block[1] = I; ring.fill(ring.Color(0, 0, 255), 0, 12); ring.show(); while(!mfrc522.Picc_isnewCardpresent()) delay(20); writecard(); display text((title[I - 1])); ring.fill(ring.Color(0, 0, 0), 0, 12); ring.show(); return; } } } // registered card Else { Currfile = buffer[1]; display text((title[currfile - 1])); mydfplayer.play(Currfile); start time = Millis()/1000; correction = 0; } } void progress() { IF(mydfplayer.readstate() != 1) { IF(State == 1) { ring.fill(ring.Color(0, 0, 0), 0, 12); ring.show(); } return; // does not play -> demolition } for(intimately I=0; I<12; I++) { ring.setpixelcolor(I, ring.Color(0, 0, 0)); } intimately played = (Millis()/1000) - start time - correction; byte LED = map(played, 0, duration[Currfile - 1], 0, 13); IF(LED > 12) { display text("End"); display.stop(); for(intimately I = 255; I>=0; I--) { for(intimately n=0; n<12; n++) { ring.setpixelcolor(n, ring.Color(0, I, 0)); } ring.show(); delay(10); } LED = 0; } Else { for(intimately I=0; I<LED; I++) { ring.setpixelcolor(I, ring.Color(0, 255, 0)); } ring.show(); } } void volume() { intimately LED; IF(digital read(Up) && digital read(Dwn)) return; for(intimately I=0; I<12; I++) { ring.setpixelcolor(I, ring.Color(0, 0, 0)); } while(!digital read(Up)) { IF(Volume<30) Volume += 2; mydfplayer.volume(Volume); LED = map(Volume, 1, 30, 0, 12); for(intimately I=0; I<LED; I++) { ring.setpixelcolor(I, ring.Color(255, 255, 0)); } ring.show(); delay(700); } while(!digital read(Dwn)) { IF(Volume>0) Volume -= 2; mydfplayer.volume(Volume); LED = map(Volume, 1, 30, 0, 12); for(intimately I=0; I<12; I++) { ring.setpixelcolor(I, ring.Color(0, 0, 0)); } for(intimately I=0; I<LED; I++) { ring.setpixelcolor(I, ring.Color(255, 255, 0)); } ring.show(); delay(700); } for(intimately I = 255; I>=0; I--) { for(intimately n=0; n<LED; n++) { ring.setpixelcolor(n, ring.Color(I, I, 0)); } ring.show(); delay(2); } }
The function Volumerot () Reads the position of the Rotary Encoder and changes the volume according to the direction of rotation. The volume is shown on the LED ring as usual.
void volumerot() { IF (encoder.readand reset() == 0 || digital read(SW)) return; intimately LED = 0; while(true) { long position = encoder.readand reset(); IF(position > 0 && Volume <= 28) Volume += 1; Else IF(position < 0 && Volume >= 2) Volume -= 1; mydfplayer.volume(Volume); delay(250); Serial.print(Volume); LED = map(Volume, 1, 30, 0, 12); ring.fill(ring.Color(0,0,0),0,12); for(intimately I=0; I<LED; I++) { ring.setpixelcolor(I, ring.Color(255, 255, 0)); } ring.show(); IF(digital read(SW)) break; } for(intimately I = 255; I>=0; I--) { for(intimately n=0; n<LED; n++) { ring.setpixelcolor(n, ring.Color(I, I, 0)); } ring.show(); delay(2); } } void set up() { softserial.Begin(9600); Serial.Begin(115200); while(!mydfplayer.Begin(softserial, false, false)) { Serial.print(F("DfPlayer Unluke to Begin")); delay(1000); } Spi.Begin(); mfrc522.PCD_init(); for (byte I = 0; I < 6; I++) { key.keybye[I] = 0xff; } pin mode(Up, Input_pullup); pin mode(OK, Input_pullup); pin mode(Dwn, Input_pullup); pin mode(SW, Input_pullup); mydfplayer.volume(Volume); //0-30 ring.Begin(); ring.show(); ring.setbrightness(75); ring.fill(ring.Color(0, 0, 0), 0, 12); ring.show(); IF(!display.Begin(Ssd1306_switchcapvcc, Screen_address)) { Serial.print(F("SSD1306 Allocation Failed")); } display.Clear display(); display.display(); display text("Start"); display.stop(); Serial.print("Start"); }
The call for the Rotary Encoder is in the Loop () In the end, complemented and therefore called cyclically.
void loop() { IF (mfrc522.Picc_isnewCardpresent()) { // Card recognized Serial.print("Card recognized"); startcard(); } volume(); progress(); IF(!digital read(OK)) { while(!digital read(OK)) delay(20); // Waiting until button was let go IF(State) { mydfplayer.pause(); State = 0; stop = Millis()/1000; } Else { mydfplayer.start(); State = 1; correction += ABS(stop - (Millis()/1000)); } } IF(!State) { ring.fill(ring.Color(120, 0, 0), 0, 12); ring.show(); } volumerot(); }
The turning code fulfills the same function as the outer buttons. If you don't want to use the encoder, you can Loop () delete.
In the end, the two 3D printed parts are screwed. Our project is complete.
Service
Place an NFC card on the top of the box, if the card is already described, the stored file is played directly. If a formatted card is launched, you can select the file number via the outer buttons. If you have found the right file, you can write the number on the card by pressing the middle button. Remove the card and put it back on. If the ring no longer shines blue, the card is described and can be used now. To set the volume, you can either press the outer buttons or hold the Rotary Encoder down and turn it. When pressing the middle button, the file is paused.
Have fun recovery :)
All the necessary files again at a glance:
Sketch: Tonuino
4 comments
Bastian Brumbj
Hallo Konrad,
leider kann ich ihnen keine bearbeitbare Datei zur Verfügung stellen, weil ich diese Dateien in einem anderen Programm erstellt habe. Gerne können sie aber ihre eigene Version anhand den Bemaßung in gegeben Dateien erstellen.
Grüße,
Bastian
Konrad
Hi beisammen,
bin dabei gerade Alles in Ruhe mal zusammen zu tragen. Dieses Projekt interessiert mich sehr. Danke schon mal jetzt für die spannende Aufgabe.
Frage: Ist es möglich die stl Dateien in einem “bearbeitbaren” Format (z.B. für FreeCAD) hochladbar zu machen? Ich würde gerne das ein oder andere anpassen. Danke
Konrad
Bastian Brumbi
Hallo Bernd-Steffen,
es freut mich zu hören, dass ihnen mein Blogbeitrag gefallen hat. Den Link für die Encoder Library habe ich inzwischen eingefügt. Die Pindefinition im softwareserial Konstruktor sollte stimmen (Arduino und DFPlayer müssen wie im Schaltplan verbunden werden: RX-TX TX-RX). Das das Display ab einer Anzahl von 20 Dateien keinen Text ausgibt, kann am begrenzten Speicher des Arduinos liegen. Hier könnte eine externe Speicherkarte, von welcher die Informationen abgerufen werden, Abhilfe schaffen.
Grüße,
Bastian
Bernd-Steffen
Hallo Bastian, noch eine schöne Erweiterung der kleinen TonUINO-Variante. Die rollende Anzeige gefällt mir gut, die Lautstärke-Regelung mit dem Encoder-Steller ist m.E. entbehrlich, da das mit den Tasten gut funktioniert, aber das ist ja Geschmacksache. Sehr gut gefallen hat mir das Gehäuse aus dem 3D-Drucker. Da habe ich gleich meinen „angeworfen“ und die zwei Teile in weiß gedruckt. Mit den schwarzen Schrauben sieht das Ganze sehr schick aus!
Nun zu zwei Kritikpunkten:
1. die encoder-library zu finden war eine Herausforderung. In der Bibliotheksverwaltung der Arduino IDE war dazu etliches zu finden, aber nicht die richtige. Erst die Suche im Netz ließ mich fündig werden. Da wäre es hilfreich für potenzielle Nachbauer, die Quelle als Link im Beitrag anzugeben, so wie das bei den libraries in Teil1 und 2 der Fall war.
2. die Parameter Rx und Tx (als SSt des DF-Players zum Nano A0 und A1) sind im Sketch vertauscht:
SoftwareSerial softSerial(RX, TX);
Wie man im Teil1 und 2 sieht, kommt in der Definition erst Tx und dann Rx. Der Befehl müsste also richtig heißen:
SoftwareSerial softSerial(TX, RX);
… sonst kommt kein Ton aus dem Lautsprecher. ;0)
Was mich noch irritiert hat, ist der Umstand, dass das Display dunkel bleibt, wenn man mehr als 20 Dateien mit den entsprechenden Texten und Zeiten im Sketch angibt! Das war bei Teil 2 nicht der Fall. Warum das so ist, hat sich mir auch nach der Analyse des Sketches noch nicht erschlossen.
Genug der Kritik, denn alles in allem eine schöne Erweiterung des Projektes, das beim Nachbau viel Spaß gemacht hat. Schade, dass man hier keine Bilder posten kann, sonst würde ich meinen weißen Musik-Würfel hier mal zeigen. Viele Grüße von Bernd-Steffen