Saturday, July 13, 2019

Bluetooth phone app loco control Part 4 - Adding sound


Contents

 

Preamble

In the previous stages of my investigations into controlling a loco using a Bluetooth phone app you will see that -
  • .....  I started off by using a freebie app and its associated code provided by Steve Massiker at arduinorailwaycontrol.com (see Part 1)
  • .... I then tinkered with the default speed settings to try and improve the way the loco responded at slow speeds (see Part 2)
  • ...... I moved on to explore the potential of a different phone app (RoboRemoFree) as this enabled me to have 255 speed steps rather than the nine speed steps offered by Steve's set-up (see Part 3)
In this part of my investigations, I will describe how I added a set of digitised sound files to be (sort of) synchronised with the speed of the loco.

Introduction

Arduino is an Open Source development, which means the resources are made freely available for anyone to explore and further enhance. As a consequence, over the years, numerous add-ons have been developed which enable the basic micro-controller boards to be extended. In the first parts of my explorations, a motor driver and a Bluetooth transceiver were added. In this section, I have added another module - a small SD card player which can be controlled by the Arduino board.
The DFPlayer Mini not only interfaces with the Arduino, it also includes an audio amplifier enabling it to be connected directly to a speaker to provide up to 2 watts of output. Its small size (20 x 20 x 10mm) makes it ideal for squeezing into the insides of a battery powere loco. I bought mine from a UK supplier for £3.50, but they can be bought directly from China or Hong Kong for less the £1.00.

To control the DFPlayer, I downloaded a library of instructions from the GitHub website and installed them in the Arduino IDE program.

The procedure for installing the library I followed was:
 
  1. Visit the GitHub website and click the Download button
  2. Remember where I saved the downloaded ZIP file
  3. Open the Arduino IDE program
  4. Click the 'Sketch' menu item and select Include Library and then Add ZIP. library
  5. Navigate to where I saved the ZIP file
  6. Open it
  7. The library is now installed. When selecting the Include Library option once more, the new library should appear the the bottom of the list of installed libraries.

Before editing the code, I needed to wire up the Player, motor driver and Bluetooth transceiver to the Nano, so I would know to which pins they were connected.

The wiring

I needed to change the existing  wiring (see Part 3) to add the Player to the Nano. The Player and the Bluetooth transceiver are both serial communication devices and only one serial device can normally be added to the Nano. However, it is possible to add another serial device by using the port used by the Nano to communicate with the computer. In addition, the library which I had just installed required the Player to be connected to pins D8 and D9 and so, the motor driver needed to be connected to another pin which could provide PWM output.

You will notice when comparing this wiring diagram with that used previously, that I made a series of other changes as well.

  • I removed the voltage regulator. The L298N motor driver can supply 5 volts (provided its input does not exceed 12 volts) and so this was tapped to power the Player, the Nano and the HC-06 transceiver.
  • I kept the control pins for the motor driver the same (D5 and D6) but moved the PWM input to pin D3.
  • The serial connections for the DFPlayer were attached to pins D8 and D9.
  • The TX and RX connections for the HC-06 were attached to Pins D1 and D0 via a plug and socket so the transceiver could be disconnected when the Nano was connected to the computer.
  • A 3mm white LED was connected to Pin D12 via a 220ohm resistor for a front light.

The Sound files

I prepared twelve sound files:
  • 001 - Silence (1 min)
  • 002 - Silence plus horn sound (2.5 seconds)
  • 003 - engine start-up to idle (7 seconds)
  • 004 - idle (15 secs)
  • 005 - idle plus horn (2.5 seconds)
  • 006 - slow run (15 secs)
  • 007 - slow run plus horn (2.5 secs)
  • 008 - mid run (15 secs)
  • 009 - mid run plus horn (4 secs)
  • 010 - fast run (15 secs)
  • 011 - fast run plus horn (2.5 secs)
  • 012 - shut down (7 secs)
It's not essential to have the numbers at the start of each file name, but it is essential that the files are saved on the SD card in the correct order. When the Arduino executes the command to play, for example, file 3, it will play the third file stored on the card. Putting the numbers in front of the file names helps ensure they are in the correct order before they are copied and pasted from the computer to the card.

I downloaded several sound clips from the internet until eventually homing in on some clips of an old tractor on the SoundSnap website. Although it cost me $15 to buy five sound clips (only three of which I used), I had been unable to track down anything which gave me the range of sounds I wanted. I figured that there was not a lot of difference between the sounds made by a tractor and a narrow gauge diesel locomotive - though no doubt some purists will disagree!

The clips were edited using Audacity - wonderful free program which enabled me to select and save sections from the downloaded sound files and also superimpose the sound of the horn over the sound of the engine noise, and adjust the volume of the two so one wasn't drowned out by the other.

The edited files were exported as 16-bit WAVs, which means they play immediately they are called up by the Arduino code. On other players with MP3 files, I have found there can be a silent gap of up to a second between tracks or when one track loops around.

For more information on using Audacity to edit sound effect files see - How I edited sound files for my railbus

Unfortunately, as the files I used were edited from paid-for originals, I cannot provide them here so I am afraid you will have to do your own tracking-down of files (or also pay for the ones I used)

The RoboRemo App

As I wanted more than five controls on the screen of the app, I had to abandon the free version of RoboRemo and invest in the paid-for Bluetooth version - at a cost of £2.89.

I set up five buttons and one slider:

The actions for the buttons were (without the quote marks):
  • Reverse - 'r'
  • Forward - 'f'
  • STOP - 'x'
  • Horn - 'h'
  • Engine start/stop - 'e'
The slider was given an id of 's' and the scale (min, max) set from 30 to 255.

The code

As previously, the entire code is shown here first followed by a break-down of each section explaining its function. This should enable you to copy and paste the code into Arduino IDE if you want to follow in my footsteps

 //Train control using RoboRemo  
 //Slider on app has id of 's' and range of 40 to 255  
 //Forward button on app has action 'f'  
 //Reverse button on app has action 'b'  
 //STOP button on app has action 'x'  
 #include <softwareserial .h="">  
 #include <DFPlayerMini_Fast.h>  
 // SOFTWARE SERIAL  
 SoftwareSerial mySerial(10, 11); // RX, TX for SD player  
 DFPlayerMini_Fast myMP3; // Initialising SD Player  
 //Pin connections  
 int motorSpeedPin = 3;  
 #define IN1 6 //motor Fwd   
 #define IN2 5 //motor Reverse  
 #define fwdLED 12 //Front facing LED pin  
 #define revLED 13 //Rear facing LED pin  
 // VARIABLES //  
 int val = 0; //Speed  
 int oldval = 0; //Previous speed  
 int Track = 1; // which track is playing  
 char cmd[100]; //Command from app  
 int cmdIndex;  //Used to index character in the command  
 int loSound = 80; // Speed setting between idle and slow run sounds  
 int midSound = 120; // Speed setting for slow to mid run sounds  
 int hiSound = 170; // Speed setting for mid to high speed run sounds  
 void setup() {  
 // Initializing Serial (Hardware and Software Serial)  
  Serial.begin(9600);  
  mySerial.begin(9600);  
  myMP3.begin(mySerial);  
  myMP3.volume(30); //Volume setting  
  myMP3.loop(1); //Loops first (silent) sound track  
 // Initialising Motor-Driver  
  pinMode(motorSpeedPin, OUTPUT);   
  pinMode(IN1, OUTPUT);   
  pinMode(IN2, OUTPUT);  
  analogWrite(motorSpeedPin, 0);  
 // Initialising LED pins  
  pinMode(fwdLED, OUTPUT);  
  pinMode(revLED, OUTPUT);  
  digitalWrite(fwdLED, HIGH); //Turns on front LED as indicator that loco is switched on  
 }  
 void loop() {  
 // ---- Intrepreting app commands  
  if(Serial.available()) {  
   char c = (char)Serial.read();  
   if(c=='\n') {  
    cmd[cmdIndex] = 0;  
    exeCmd(); // execute the command  
    cmdIndex = 0; // reset the cmdIndex  
   } else {     
    cmd[cmdIndex] = c;  
    if(cmdIndex<99) cmdIndex++;  
   }  
  }   
 }  
 //// FUNCTIONS ////  
 void exeCmd() {  
  if(cmd[0]=='s') {  
   oldval = val;  
   val = 0;  
   for(int i=2; cmd[i]!=0; i++) { // number begins at 2  
    val = val*10 + (cmd[i]-'0'); // if cmd is "speed 100", val will be 100  
    }  
   }  
    if(val<loSound-10) val=0; //Sets speed to zero to stop motor buzz on default PWM setting  
    // Direction and Stop  
    if (cmd[0] =='f') { // (f) Forward  
    digitalWrite(IN1, HIGH);  
    digitalWrite(IN2, LOW);  
    digitalWrite(fwdLED, HIGH);  
    digitalWrite(revLED, LOW);  
    }  
    if (cmd[0] =='b') { // (b) Backward  
     digitalWrite(IN1, LOW);  
     digitalWrite(IN2, HIGH);  
     digitalWrite(fwdLED, LOW);  
     digitalWrite(revLED, HIGH);  
     }  
     if (cmd[0] =='x') { // (x) Stop button  
      digitalWrite(IN1, LOW);  
      digitalWrite(IN2, LOW);  
      digitalWrite(fwdLED, LOW);  
      digitalWrite(revLED, LOW);  
      myMP3.loop(1);  
      Track = 1;  
      val = 0;  
      oldval = 0;  
     }  
     analogWrite(motorSpeedPin, val); // Throttle  
 //Playing the SD card tracks  
 //There are twelve tracks on the SD card  
 //001 - Silence (1 min)  
 //002 - Silence plus horn sound (2 seconds)  
 //003 - engine start-up to idle (15 seconds)  
 //004 - idle (15 secs)  
 //005 - idle plus horn (2.5 seconds)  
 //006 - slow run (15 secs)  
 //007 - slow run plus horn (2.5 secs)  
 //008 - mid run (15 secs)  
 //009 - mid run plus horn (4 secs)  
 //010 - fast run (15 secs)  
 //011 - fast run plus horn (2.5 secs)  
 //010 - shut down (3 secs)  
     if(cmd[0]=='e' && Track==1){  
     myMP3.play(3);  
     delay(10000);   
     myMP3.loop(4);//Play 'engine start-up' and then on to 'idle' (Track 4)  
     Track = 4;  
     cmd[0]='n';  
     }  
     if(cmd[0]=='e' && Track==4){  
     myMP3.play(12);  
     delay(7000);  
     myMP3.loop(1);//Play 'engine shut down' then on to silence (Track 1)  
     Track = 1;  
     cmd[0]='n';  
     }  
     if(cmd[0]=='h' && (Track==1 || Track==4 || Track==6 || Track==8 || Track==10)) {  
      myMP3.play(Track + 1); //Play relevant horn track  
      delay(3000);  
      myMP3.loop(Track);      
     }  
     if(Track==4 && val>loSound){  
     myMP3.loop(6);  
     Track = 6;  
     }  
     if(Track==6 && val<loSound){  
     myMP3.loop(4);  
     Track = 4;  
     }  
     if(Track==6 && val>midSound){  
     myMP3.loop(8);  
     Track=8;  
     }  
     if(Track==8 && val<midSound){  
     myMP3.loop(6);  
     Track=6;  
     }  
     if(Track==8 && val>hiSound){  
     myMP3.loop(10);  
     Track=10;  
     }  
     if(Track==10 && val<hiSound){  
     myMP3.loop(8);  
     Track=8;  
     }  
 }  

NOTE: 

Check that the symbols are showing up correctly in the code - some browsers change them.

For example the last part of the code should look like this

How it works


The first section sets up the serial connection and opens the library for the DFPlayer

 #include <SoftwareSerial.h>  
 #include <DFPlayerMini_Fast.h>  
 // SOFTWARE SERIAL  
 SoftwareSerial mySerial(10, 11); // RX, TX for SD player  
 DFPlayerMini_Fast myMP3; // Initialising SD Player  

The next section sets up the pins for the various devices. Note: Because the Bluetooth transceiver is using the Hardware Serial port, it does not need to be initialised.

 //Pin connections  
 int motorSpeedPin = 3;  
 #define IN1 6 //motor Fwd   
 #define IN2 5 //motor Reverse  
 #define fwdLED 12 //Front facing LED pin  
 #define revLED 13 //Rear facing LED pin  

The next chunk of code sets-up the variables used in the program:

 // VARIABLES //  
 int val = 0; //Speed  
 int oldval = 0; //Previous speed  
 int Track = 1; // which track is playing  
 char cmd[100]; //Command from app  
 int cmdIndex;  //Used to index character in the command  
 int loSound = 80; // Speed setting between idle and slow run sounds  
 int midSound = 120; // Speed setting for slow to mid run sounds  
 int hiSound = 170; // Speed setting for mid to high speed run sounds  

Hopefully the comments (signified with a prefix of //) make them self explanatory.

The setup section of the program is very similar to that used in Part 3, with a couple of slight amendments:

 void setup() {  
 // Initializing Serial (Hardware and Software Serial)  
  Serial.begin(9600);  
  mySerial.begin(9600);  
  myMP3.begin(mySerial);  
  myMP3.volume(30); //Volume setting  
  myMP3.loop(1); //Loops first (silent) sound track  
 // Initialising Motor-Driver  
  pinMode(motorSpeedPin, OUTPUT);   
  pinMode(IN1, OUTPUT);   
  pinMode(IN2, OUTPUT);  
  analogWrite(motorSpeedPin, 0);  
 // Initialising LED pins  
  pinMode(fwdLED, OUTPUT);  
  pinMode(revLED, OUTPUT);  
  digitalWrite(fwdLED, HIGH); //Turns on front LED as indicator that loco is switched on  
 }  

The initialisation of the serial connections is slightly different as the Bluetooth module is connected to the Hardware Serial port and so is initialised with Serial.begin(9600) which sets up the baud rate for the communication. Similarly, the baud rate for the player is also set at 9600.

The final command turns on the front LED. I found this useful when testing the loco, to check that the Arduino board was switched on and functioning as expected.

The main loop is exactly the same as in Part 3. This parses the information sent by the app which arrives as a series of individual characters, and then when the terminal character for each string is receiver ('/n'), puts the characters together as a string.

 void loop() {  
 // ---- Intrepreting app commands  
  if(Serial.available()) {  
   char c = (char)Serial.read();  
   if(c=='\n') {  
    cmd[cmdIndex] = 0;  
    exeCmd(); // execute the command  
    cmdIndex = 0; // reset the cmdIndex  
   } else {     
    cmd[cmdIndex] = c;  
    if(cmdIndex<99) cmdIndex++;  
   }  
  }   
 }  

There is only one function (exeCmd), the first part of which was explained in Part 3.

 //// FUNCTIONS ////  
 void exeCmd() {  
  if(cmd[0]=='s') {  
   oldval = val;  
   val = 0;  
   for(int i=2; cmd[i]!=0; i++) { // number begins at 2  
    val = val*10 + (cmd[i]-'0'); // if cmd is "speed 100", val will be 100  
    }  
   }  
The next statement is new:

    if(val<loSound-10) val=0; //Sets speed to zero to stop motor buzz on default PWM setting  

I find that the motor buzzes because the default PWM setting (490Hz) is quite low. So when stationary, I turn off the motor to stop it from buzzing. I am in the process of playing around with the PWM settings and intend to increase the setting to help prevent excess noise from the motor.

The next section is almost as it was in Part 3.

    // Direction and Stop  
    if (cmd[0] =='f') { // (f) Forward  
    digitalWrite(IN1, HIGH);  
    digitalWrite(IN2, LOW);  
    digitalWrite(fwdLED, HIGH);  
    digitalWrite(revLED, LOW);  
    }  
    if (cmd[0] =='b') { // (b) Backward  
     digitalWrite(IN1, LOW);  
     digitalWrite(IN2, HIGH);  
     digitalWrite(fwdLED, LOW);  
     digitalWrite(revLED, HIGH);  
     }  
     if (cmd[0] =='x') { // (x) Stop button  
      digitalWrite(IN1, LOW);  
      digitalWrite(IN2, LOW);  
      digitalWrite(fwdLED, LOW);  
      digitalWrite(revLED, LOW);  
      myMP3.loop(1);  
      Track = 1;  
      val = 0;  
      oldval = 0;  
     }  
     analogWrite(motorSpeedPin, val); // Throttle  

The main differences are that in each sub section, there are commands to control the forward and reverse LED lights - turning them on (by setting the output to HIGH) or turning them off (by setting the output to LOW).

In addition, at the end of the STOP sub-section, in addition to turning off both LEDs and setting the speed (val) to zero, the player is instructed to play the first track (silence).

The final section of the Function, controls the SD player.The first part of this section controls what happens if the 'e' instruction is received from the Bluetooth app (ie when the Engine Start/Stop button is pressed).

      if(cmd[0]=='e' && Track==1){  
     myMP3.play(3);  
     delay(10000);   
     myMP3.loop(4);//Play 'engine start-up' and then on to 'idle' (Track 4)  
     Track = 4;  
     cmd[0]='n';  
     }  
     if(cmd[0]=='e' && Track==4){  
     myMP3.play(12);  
     delay(7000);  
     myMP3.loop(1);//Play 'engine shut down' then on to silence (Track 1)  
     Track = 1;  
     cmd[0]='n';  
     }  

The first 'if' statement checks whether the player is playing Track 1 (ie silence). If so it will play Track 3 (engine start-up) for seven seconds and then move on play the sound of the engine idling (ie track 4).

The second 'if' statement, checks whether Track 4 is playing (ie the idling sound). If so, it will play the engine shot-down track (Track 12) for seven seconds and then play silence (Track 1).

The next 'if' statement, detects whether the horn button has been pressed on the app. If so, it then checks which track is playing and plays the track following it - for example, if Track 6 (slow run) is playing, it will play track 7 for 3 seconds before returning to play Track 6.


     if(cmd[0]=='h' && (Track==1 || Track==4 || Track==6 || Track==8 || Track==10)) {  
      myMP3.play(Track + 1); //Play relevant horn track  
      delay(3000);  
      myMP3.loop(Track);      
     }  

Finally, the last set of  'if' statements change the track which is playing, dependent on the speed of the motor.


     if(Track==4 && val>loSound){  
     myMP3.loop(6);  
     Track = 6;  
     }  
     if(Track==6 && val<loSound){  
     myMP3.loop(4);  
     Track = 4;  
     }  
     if(Track==6 && val>midSound){  
     myMP3.loop(8);  
     Track=8;  
     }  
     if(Track==8 && val<midSound){  
     myMP3.loop(6);  
     Track=6;  
     }  
     if(Track==8 && val>hiSound){  
     myMP3.loop(10);  
     Track=10;  
     }  
     if(Track==10 && val<hiSound){  
     myMP3.loop(8);  
     Track=8;  
     }  
 }  

As you can see, if one of the trigger values for speed which were set at the beginning of the program is reached, it checks which track is playing and either moves up or down to the next relevant track.


Conclusion

Of source, it is not perfect. In an ideal world, the pitch and speed at which the sound of the engine is played would vary in proportion to the speed of the locomotive. This is not achievable when the sounds are tracks on an SD player. I did try playing a track which accelerated or decelerated the sound as the tracks changed, but this proved impossible to work reliably. Like the horn or the engine start/stop tracks, the Arduino was unable to do something else and, as these tracks were triggered by a speed change, it was unable to detect the new speed until the track finished playing - which either led to some very jerky running or completely confused the system as to which track ought to be playing. If someone with more programming knowledge than me is able to sort out this conundrum I will be very grateful.

In the meantime, I am quite pleased with the outcome. 


It took me several tries until I got the relative speeds of the sounds matched to the speed of the loco without making the jump from one track to the next too noticeable. If I had more time and more patience, I could add several more speed triggers and more tracks to make the increases and decreases in engine sound less noticeable - but I'll leave that to someone else.

4 comments:

RussellCofIdaho said...

Fine engineering and programming, thank you! I have quite a bit of Arduino bits gathering dust and you have given me ideas to get them working!

Charles said...

Interesting investigation, I'm just about to start going battery+DT myself inspired by your blog.

If you are investigating microcontrollers you might want to look at the ESP8266/Wemos D1 Mini type. They are very useful as very compact WiFi enabled well priced controllers and work with the same IDE you're using.

They would probably work better as nodes controlled by a central point, maybe a Raspberry Pi. That may move more towards software than you'd prefer but I did find this interesting project doing just this with the Rocrail project: https://www.esp8266.com/viewtopic.php?f=6&t=17207 worth downloading the PDF

Thanks for the continued inspiration for my own railway :)

Cheers
Charles

GE Rik said...

Interesting, Charles. Thanks for the link. Probably more complex than I'm aiming for, but gives an idea of what's possible.

Rik

Jason W. Chenard said...

I had been thinking about using more or less this group of components for this purpose for a while. In particular, I had thought about using several custom-made sound files in this sound board as you have done here. The overall amount of work required seemed a bit daunting given my limited free time. I might now give it a go since you've done most of the work!

Just discovered your blog, and I will be sifting through the back issues as time allows; Thanks for sharing.

Also, good for you for avoiding the temptation to give out the copyrighted sound files. Collectively, we've become too blasé about this sort of thing. Either someone's giving something away by choice, or he's selling it, in which case taking it without paying is stealing. We should all be doing a better job of being respectful in this area.

Thanks again for your contributions.