The following article was made by the guest author Bastian Brumbi made available:
After it in first part In this part, further modules are added to the basic functions. In addition to the components from the first part, the following products are required:
Hardware
Next we take a look at the hardware. Now put the new components on the Breadboard from the previous part and cable them as follows:
Microcontroller |
module |
D 4 |
Led ring - in |
A 4 |
OLED - SDA |
A 5 |
OLED - SCL |
software
Additional libraries must be installed for the LED ring and the OLED display. As usual, these can be installed via the integrated library manager or as a .zip file in the Arduino IDE:
Adafruit SSD1306
Adafruit neopixel
The goal is to display the date number on the display and the programming of the cards. The LED ring is intended to represent a progress bar and the status by changing the color. Since we cannot read this data through a command on the DFPlayer, we have to store it in the program. For this purpose, two arrays are initialized, in the first the duration of each individual file is given in seconds, in the second the name, which is to be displayed on the display. It should be noted that the positions are moved in the array, but must match the file name (001.mp3 = [0]). For reasons of the low storage space, the shortest possible names should be chosen without umlauts.
The associated libraries are still integrated for the display and the LED ring. For the display, the Wire Library, which is required for communication, and the Gfx Library integrated for the graphic calculations.
Copy or load the source code to the Arduino ID (Download):
#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> #define Rst_pin 9 #define Ss_pin 10 #define Up 7 #define OK 6 #define Dwn 5
For the IIC address of the display, we define an additional symbolic constant here.
#define Screen_address 0x3c
Then the global variables and objects are created with the corresponding constructor. We take over some of the following methods from the first part.
Adafruit_neopixel ring(12, LED, Neo_grb + Neo_khz800); Adafruit_ssd1306 display(128, 32, &Wire, -1); Software softserial(2, 3); 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 !!!
In addition, the arrays are initialized for the title and the duration and the variables for determining the time played.
String title[Filecount] = {"TXT1", "Txt2", "Txt3", "Txt4"}; // title to be displayed intimately duration[Filecount] = {234, 231, 234, 123}; // duration in seconds intimately stop = 0; intimately start time = 0; intimately correction = 0; intimately Currfile = 0; byte Volume = 20; 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(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(); }
This function is later called for displaying text on the display:
void display text(String text){ display.Clear display(); display.SettextSize(2); display.SettextColor(SSD1306_White); display.setcursor(5,10); display.print(text); display.display(); }
The StartCard () function largely corresponds to the old one, only that during the programming process of the file index and with a stored card, the stored card is displayed on the display. In addition, the ring shines blue during the description process of the card and after successful describing it is switched off again.
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; } }
The following method represents the progress bar on the LED ring. At the beginning, the traffic jams of the DFPlayer are queried. If nothing is played, the method is ended and if not paused, the LEDs are switched off. After that, the time played is calculated and the number of LEDs that are supposed to light map () Function determined. In the end, the ring is dimmed or, if not yet the end of the file, the previously calculated number of LEDs is displayed.
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(); } }
The next function is the replacement for the previous button queries in Loop (). If no button is pressed, the end follows by a earlyreturn. While a button is pressed, the volume is increased or reduced in 2 steps. An orange circular sector is displayed according to the newly set volume.
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); } }
In the set up() the starting functions are executed, the player initialized, the RFID sensor (exactly we im first part), the LED ring and the display.
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"); }
In the Loop () Become the methods progress() and Volume () repeated permanently. When the middle button is pressed, the current time is saved in a variable, withdrawn from time when continuing and saved as a correction factor.
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(); } }
Conclusion
In this part we added the following functions to the Tonuino set:
- Display of progress
- Relief in operation in programming the cards
- Improvement of the volume setting
- Representation of the volume on the LED ring
- Display of the title on the display
In the next part, the project will be built into a 3D printed housing and a Rotary Encoder is added as an alternative control of the volume.
Have fun recovery :)
2 comments
Bastian Brumbi
Vielen Dank für das ausführliches Feedback und die wertvollen Hinweise!
Ich habe mir die angesprochenen Punkte noch einmal genau angeschaut und die erwähnten Fehler behoben.
Dieses Projekt soll das bisherige Tonuino Projekt erweitern, sodass auch Einsteiger das Programm nachvollziehen können. Falls sie die Wiedergabefunktionen des Offiziellen Tonuino Projekts (von Thorsten Voss) benötigen, können sie einen Blick auf dieses Werfen, welches jedoch deutlich komplexer ist.
Nochmals danke für das Feedback; Ich hoffe, dass die Verbesserungen helfen werden!
Grüße,
Bastian Brumbi
Bernd-Steffen
Hallo zusammen, die vorgestellten Erweiterungen zum TonUINO von Thorsten Voss sind ganz nett für die Erleichterung der Bedienung (Nr. der Wiedergabedatei oder Lautstärke-Niveau anzeigen) allerdings vermisse ich die vielfältigen Funktionen (z.B. freie Wahl eines Ordners, Wiedergabemodus wie Hörbuch-, Hörspiel- oder Party-Modus, Admin-Funktionen) des o.a. Projektes! Der Autor hätte auch in der Beschreibung darauf hinweisen können, dass die Anzahl der wiederzugebeneden Dateien fix im Sketch einzugeben sind, ebenso die zugehörigen Texte und Dauer der Wiedergabe. Das empfinde ich als schwerwiegende Einschränkung gegenüber der Voss-Version! Außerdem sind einige Fehler im Sketch (auch in der Download-Datei aus der Cloud!) enthalten: die Konstanten LED (s. Zeile 22) und SW (s. Zeile 256) sind nicht definiert und führen zu Fehlern beim Compilieren. Im Sketch des 3. Teils sind die Definitionen dann vorhanden, (#define LED 4 und #define SW 8)
Ansonsten ein schöne Erweiterung – super wäre, wenn die genannten Funktionen des Voss-Projektes mit den hier vorgestellten Erweiterungen kombiniert würden. Auch sollte dieses Projekt als Quelle erwähnt werden!
Viele Grüße von Bernd-Steffen