Last Updated: 12/10/2021

Project 5 - DCC Accessory Decoder, sounds and lights Arduino UNO

Projects >> Project 5 - DCC Accessory Decoder, sounds and lights Arduino UNO

DCC Accessory Decoder, sounds and lights Arduino UNO

usually when buying an DCC stationary Accessory decoder for a model railway it tends to just be able to switch something on or off, be it a sound, a light, a point/turnout or any other component we could switch.

On my layout, The Isle of Mudd (IOM), my Accessory decoders tend to control sequences of items so for my lift bridge the decoder first sets the signals to red, closes the gates, then raises the bridge. I could do this using a number of decoder addresses in the conventional way but I want to press a single button and the Arduino then takes control of the sequence.

In this project I wanted a decoder to control my Engine shed module. This consists of various lights and effects as well as a series of background sounds, some linked to the lighting effects.

DO NOT CONNECT YOUR ARDUINO DIRECTLY TO THE TRACK

All my decoders are built using the circuit at http://www.scale-n.nl/ScaleN_Naslag_Arduino_DCCMonitor.aspx

The components are very cheap and can be built up in minutes on a bread board. For my finished projects I tend to solder the components onto a shield however in this tutorial you will see that I have built them onto a seperate piece of veroboard that is part of my DCC test equipment.

Although the page above uses the mynabay DCC_Decoder library I will be using the NmraDCC library that is available through the Arduino IDE library.

I have found this library to work very well both with my NCE Power Cab and all version of DCC++ and DCC++ EX

You will see in the tutorial that I am using DCC++ EX to test the decoder but you could use a commercial system instead.

DCC Accessory Decoder, sounds and lights Arduino UNO

Below is the test set up on my workbench

Speaker connected to the Serial UART MP3 player, A string of 16 neopixels, bright white LED on the small bread board and top right above the UNO is my DCC board built from the information at http://http://www.scale-n.nl/ScaleN_Naslag_Arduino_DCCMonitor.aspx

Once everything is working correctly on the breadboard the components will then be soldered noto either an Arduino prototype shield or a home made version using some veroboard.

The white paper under the set up is there to protect everything from short circuits in case I have any bits of wire or solder on the work bench.

Breadboard build

Behind the UNO is my DCC++ EX test bed connected to a home made power supply.. This simple set up allows me to easily test the projects I'm building without having to take them out to the layout. You will notice that I have a DCC loco decoder on the breadboard, I use this connected to a small motor to simulate a loco when testing DCC controllers..

DCC++ EX test bed

 

Example 1: BlankDecoderNMRAv1.ino


Click to Download code:BlankDecoderNMRAv1.ino

This sketch is a simplified bersion of the NmraDccAccessory_Decoder_1 example in the NMRA library.

I have stripped out everything except the basic apart from the function void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower )

This function deals with calls to an Accessory decoder.

Inside the functino I have placed a simple switch() statement that switches the Accessory Address and inside that switch statement is an if() statement that deals with the direction selected.

Note: When an Accessory address is called you will get multiple calls, this is normal and part of the NMRA standards for DCC.


 
/* 11/10/2021
 *  BlankDecoderNMRAv1
 *  
 *  Based on the NmraDccAccessory_Decoder_1 example in the NMRA library examples.
 * 
 * Blank decoder that has switch statemnt set up for Accessory
 * Notice when the decoder is triggered mutliple calls are made to the decoder
 * This is not a fault, All codes are sent mutiple times very quickly
 * as if it was a loco it could be on a dirty bit of track


 Commands to send to DCC++ EX base station for testing
 <1>  = power on
 < a 25 0 > = accessory number 25 direction 0
 < a 25 1 > = accessory number 25 direction 1


 Pins used

 2 = interrupt pin for decoder messages/circuit
 
 */


#include "NmraDcc.h"


NmraDcc  Dcc ;
DCC_MSG  Packet ;


// This function is called whenever a normal DCC Turnout Packet is received and we're in Output Addressing Mode
void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower )
{
  Serial.print("notifyDccAccTurnoutOutput: ") ;
  Serial.print(Addr,DEC) ;
  Serial.print(',');
  Serial.println(Direction,DEC) ;
 // Lines below of no interest to me so commented out but left in to show original position
 // Serial.print(',');
 // Serial.println(OutputPower, HEX) ;

 //Add thr accessory decoders you want to use into the sketch
  switch(Addr){
    case 25:
      if(Direction < 1){
       Serial.println(0); 
      }else{
        Serial.println(1);  
      }
      break;
      //Any address not listed above...do nothing
    default:
      break;
  }
  
}

void setup()
{
  Serial.begin(115200);
  
  

  Serial.println("BlankDecoderNMRAv1...");
  
  // Setup which External Interrupt, the Pin it's associated with that we're using and enable the Pull-Up 
  Dcc.pin(0, 2, 1);//Pin 2 is the interrupt pin on an UNO
  
  // Call the main DCC Init function to enable the DCC Receiver
  Dcc.init( MAN_ID_DIY, 10, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0 );

  Serial.println("Init Done");
}

void loop()
{
  // You MUST call the NmraDcc.process() method frequently from the Arduino loop() function for correct library operation
  Dcc.process();
  
}

Example 2: IOM_UNO_ShedV1_1Page.ino


Click to Download code:IOM_UNO_ShedV1_1Page.ino

The Decode ciruit uses Pin2 (interrupt pin) on the Arduino UNO so when building your accessory that you want to control you cannot use this pin. This is why I list all the pins I have used in the header so as not to have conflicts.

This script is split into three files but is put here as a single file foe ease of download..

IMPORTANT: When building animations to be controlled by DCC you CANNOT use delay. Delay will block any incoming commands to the decoder.

You will need to use millis() for timers, if you do not know how to do this see the Lesson 7 delay() and millis()

In the example below I am using a Serial UART MP3 player. I have done a tutorial on the Serial UART MP3 player explaining alll the codes used in this sketch.

As the UNO only has one serial port I have used the SoftwareSerial library.

I used are a single birght white LED for the arc welding effect.

I also have a string of Neopixels that are being controlled by theAdafruit_NeoPixel library downloadable from the Arduino IDE library manager.

The sketch works through a number of sounds on the SD card at random and when it selects arcweld() the function synchronises the white led with the Arc welder sound that plays a number of times.

Sound 17 is the Ash Pit, this will be a seperate triggered sound for when a loco is over the ash pit and coal loading area. A sound of coal being shovelled plays and a set of neopixels that will be set in the ask pit glow and then fade over time as the ass cools.

I find it much easier to build the souds and light control as a serperate script with everything in functions that will eventually be called by the DCC script.

 
/*
   IOM_UNO_ShedV1

  Effects required
  All effects must use millis() because decoder/button instructions can be received

  variable glow for ash pit
  Coal fill shovel sound

  Workshop effects

   Pit lights...neopixels
   Shed lights...neopixels

  Arc welders plus sync flashing...LEDS

  Random workshop sounds



  Sound on Card

  01 grinder 37 seconds
  02 big arc welder 7 seconds
  03 small arc weld 8 seconds
  04 drill/machine 11 secnods
  05 file 5 seconds
  06 grinder 4 seconds
  07 light hammer 4 seconds
  08 hacksaw 4 secs
  09 big machine 28 secs...could repeat
  10 another big machine 11 secs to end
  11 tool box 3 secs
  12 seagulls boats 8m 48 secs
  13 waterfall 3m 19 could repeat
  14 single whistle
  15 short whistle.horn
  16 guard whistle
  17 birds/country side 1m 50
  18 short whistle
  19 double short whistle
  20 hydraulics 2m 33
  21 ship/boat big horn
  22 coaling 21 sec repeatable

  pins

  2 required for DCC decoder script
  3 software serial...MP3 player
  4 software serial...MP3 player
  5 Neopixels
  6 arc welder
*/
//Serial UART WAV/MP3 player
#include "SoftwareSerial.h"
#define ARDUINO_RX 4//should connect to TX of the Serial MP3 Player module
#define ARDUINO_TX 3//connect to RX of the module
SoftwareSerial myMP3(ARDUINO_RX, ARDUINO_TX);
byte sendBuffer[6]; //buffer that will be used to store commands before sending

//Neopixels
#include "Adafruit_NeoPixel.h"
// Which pin on the Arduino is connected to the NeoPixels?
#define neoPin        5
// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 16 // 16 on my test strip
Adafruit_NeoPixel pixels(NUMPIXELS, neoPin, NEO_GRB + NEO_KHZ800);



//Arc Welder variables
const int arcPin = 6;//arc welder Digital Pin
int arcWeldState;
unsigned long arcWeldSoundTimer;
unsigned long arkEndFlashTimer;
unsigned long arkFlashTimer;
int arcSoundCounter;

//Ash pit variables
unsigned long ashSoundTimer;
int ashSoundCounter;
//4 neopixels for ash effects...each to operate independently
unsigned long ashNeoPixelTimer[4];
int ashNeoPixelCounter[4] = {251, 251, 251, 251}; //values required for them to start

//Other sound variables
unsigned long soundChange;
unsigned long soundEnd;
int soundVolume;
int soundStart;

int playThisSound;



void setup() {
  Serial.begin(9600);
  Serial.println("IOM_UNO_ShedV1...");
  //serial mp3 player
  myMP3.begin(9600);
  delay(500);//allow everything to settle down
  //first we need to select the TF Card
  selectTFCard();
  delay(100);
  playSound(19);//double whistle test on start up
  delay(3000);
  //neopixels
  pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
  pixels.clear(); // Set all pixel colors to 'off'
  pixels.show();
  for (int q = 0; q < 16; q++) { //pixel test
    pixels.setPixelColor(q, pixels.Color(0, 0 , 150)); //green
    pixels.show();
    delay(100);
  }
  pixels.clear(); // Set all pixel colors to 'off'
  pixels.show();
  //arc welder
  pinMode(arcPin, OUTPUT);
  digitalWrite(arcPin, HIGH);
  delay(500);
  digitalWrite(arcPin, LOW);
}

void loop() {
  //ashpit();//triggered sound

  backgroundSound();
  
}


////////////////////////SerialWav.ino//////////////////////////////////////

//Serial UART commands

//Manual says the code for this is 7E 03 35 01 EF so load into buffer array
void selectTFCard(){
  sendBuffer[0] = 0x7E;
  sendBuffer[1] = 0x03;
  sendBuffer[2] = 0x35;
  sendBuffer[3] = 0x01;
  sendBuffer[4] = 0xEF;
  sendUARTCommand();
}

void playSound(byte songNumber){
  sendBuffer[0] = 0x7E;
  sendBuffer[1] = 0x04;
  sendBuffer[2] = 0x41;
  sendBuffer[3] = 0x00;
  sendBuffer[4] = songNumber;
  sendBuffer[5] = 0xEF;
  sendUARTCommand();
}
//play a sound at a set volume, only seems to apply to root directory files
void playSoundAtVolume(byte volume,byte songNumber){
  sendBuffer[0] = 0x7E;
  sendBuffer[1] = 0x04;
  sendBuffer[2] = 0x31;
  sendBuffer[3] = volume;
  sendBuffer[4] = songNumber;
  sendBuffer[5] = 0xEF;
  sendUARTCommand();
}
//loop sound 7E 04 33 00 01 EF
void repeatSound(byte songNumber){
  sendBuffer[0] = 0x7E;
  sendBuffer[1] = 0x04;
  sendBuffer[2] = 0x33;
  sendBuffer[3] = 0x00;
  sendBuffer[4] = songNumber;
  sendBuffer[5] = 0xEF;
  sendUARTCommand();
}


//set the playback volume 7E 03 31 0F EF = set volume to 0x0F = 15
void setplayVolume(byte volume){
  sendBuffer[0] = 0x7E;
  sendBuffer[1] = 0x03;
  sendBuffer[2] = 0x31;
  sendBuffer[3] = volume;
  sendBuffer[4] = 0xEF;
  sendUARTCommand();
}

//stop the current track playing
void stopSound(){
  sendBuffer[0] = 0x7E;
  sendBuffer[1] = 0x02;
  sendBuffer[2] = 0x0E;
  sendBuffer[3] = 0xEF;
  sendUARTCommand();
}

void sendUARTCommand(){
  int q;
  for(q=0;q < sendBuffer[1] + 2;q++){
    myMP3.write(sendBuffer[q]);  
  }
  Serial.println("Commands Sent");
  for(q=0;q < sendBuffer[1] + 2;q++){
    Serial.println(sendBuffer[q],HEX);  
  }
  delay(25);//stops odd commands being missed
}

//////////////////////////////sounds.ino//////////////////////////////////

//sound functions
void backgroundSound(){
  if(soundStart < 1  || millis() < soundEnd){
    switch(playThisSound){
      case 1 ... 2:
        arcWeld();
        break;
      case 3:
        grinderSound();
        break;
      case 4 ... 5:
        drillSound();
        break;
      case 6 ... 9:
        fileSound();
        break;
      case 10 ... 12:
        hammerSound();
        break;
      case 13:
        bigMachineSound();
        break;
      case 14 ... 16:
        toolboxSound();
        break;
      case 17://triggered sound
        ashpit();
        break;
      default:
        break;
    }  
  }
  
  if(millis() > soundEnd){
    playThisSound = random(30);//increase this value to get more time without sounds
     //playThisSound = random(14,18);//increase this value to get more time without sounds
    Serial.print("playThisSound: ");
    Serial.println(playThisSound);
    soundStart = 0;
    digitalWrite(arcPin, LOW);
    
    if(playThisSound > 17){//don't want it to be able to opick track 17
        soundEnd = millis() + random(2000,20000);//take a 10 second break from playing sounds  
    }
    
    
  }
}

void toolboxSound(){
  if(soundStart < 1){
    soundStart = 1;
    soundVolume = 15;
    playSoundAtVolume(soundVolume, 11); //drill 4 seconds
    soundChange = millis() + 4000;
    soundEnd = millis() + 4000;
  } 
}

void bigMachineSound(){
  if(soundStart < 1){
    soundStart = 1;
    soundVolume = 15;
    playSoundAtVolume(soundVolume, 9); // 28 seconds
    soundChange = millis() + 24000;
    soundEnd = millis() + 28000;
  }
  if(millis() > soundChange && millis() < soundEnd){
    soundChange = millis() + 500;
    soundVolume -=1;
    if(soundVolume < 0){
      soundVolume = 0;  
    }
    setplayVolume(soundVolume);   
  } 
}

void hammerSound(){
  if(soundStart < 1){
    soundStart = 1;
    soundVolume = 15;
    playSoundAtVolume(soundVolume, 7); //drill 4 seconds
    soundChange = millis() + 5000;
    soundEnd = millis() + 5000;
  } 
}

void fileSound(){
  if(soundStart < 1){
    soundStart = 1;
    soundVolume = 15;
    playSoundAtVolume(soundVolume, 5); //drill 5 seconds
    soundChange = millis() + 5000;
    soundEnd = millis() + 5000;
  } 
}

void drillSound(){
  if(soundStart < 1){
    soundStart = 1;
    soundVolume = 15;
    playSoundAtVolume(soundVolume, 4); //drill 11 seconds
    soundChange = millis() + 11000;
    soundEnd = millis() + 11000;
  } 
}

void grinderSound(){
  if(soundStart < 1){
    soundStart = 1;
    soundVolume = 15;
    playSoundAtVolume(soundVolume, 1); //grinder 37 seconds
    soundChange = millis() + 32000;
    soundEnd = millis() + 37000;
  } 
  if(millis() > soundChange && millis() < soundEnd){
    soundChange = millis() + 500;
    soundVolume -=1;
    if(soundVolume < 0){
      soundVolume = 0;  
    }
    setplayVolume(soundVolume);   
  }
}


void arcWeld() {
  if(soundStart < 1){
    soundStart = 1;
    soundEnd = millis() + 40000;
    arcSoundCounter = 0;
  } 
  if (millis() > arcWeldSoundTimer && arcSoundCounter < 5) {
    playSoundAtVolume(15, 2);
    arkEndFlashTimer = millis() + 7000;//resets the flash timer
    arcSoundCounter++;
    arcWeldSoundTimer = millis() + random(7000, 10000);
  }
  if (millis() < arkEndFlashTimer) {
    if (millis() > arkFlashTimer) {
      arkFlashTimer = millis() + random(10, 40); //next change state
      if (arcWeldState > 0) {
        arcWeldState = 0;
        digitalWrite(arcPin, LOW);
      } else {
        arcWeldState = 1;
        digitalWrite(arcPin, HIGH);
      }
      
    }
  } else {
    digitalWrite(arcPin, LOW);
  }
}

//handles 4 neopixels for falling ash plus coal filling sound
void ashpit() {
  int q;
  if (millis() > ashSoundTimer && ashSoundCounter < 20) {
    soundStart = 1;
    playSoundAtVolume(15, 22);

    ashSoundCounter++;
    ashSoundTimer = millis() + random(21000, 30000);
    soundEnd = ashSoundTimer + 5000;
  }
  if(ashSoundCounter == 20 && soundStart > 0){
    soundStart = 0;
    soundEnd = millis() + 5000; 
    pixels.clear();
    pixels.show(); 
  }
  
  //cycle through the neopixels

  if (ashNeoPixelCounter[0] == 0  && ashSoundCounter < 10) {
    for (q = 0; q < 4; q++) {
      ashNeoPixelCounter[q] = 251;
      ashNeoPixelTimer[q] = millis() + random(1000, 1200);
    }
  }
  if (ashSoundCounter  > 15) { //All ash should be cold by now
    for (q = 0; q < 4; q++) {
      pixels.setPixelColor(q, pixels.Color(0, 0, 0));
      pixels.show();
    }
  }
  for (q = 0; q < 4; q++) {
    if (millis() > ashNeoPixelTimer[q] && ashNeoPixelCounter[q] > -1) {
      switch (ashNeoPixelCounter[q]) {
        case 0 ... 24:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 0, 0)); //red
          break;
        case 25 ... 49:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 1, 0)); //red
          break;
        case 50 ... 74:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 2, 0)); //red
          break;
        case 75 ... 99:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 3, 0)); //red
          break;
        case 100 ... 124:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 4, 0)); //red
          break;
        case 125 ... 149:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 5, 0)); //red
          break;
        case 150 ... 174:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 6, 0)); //red
          break;
        case 175 ... 199:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 7, 0)); //red
          break;
        case 200 ... 224:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 8, 0)); //red
          break;
        case 225 ... 250:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 9, 0)); //red
          break;
        default:
          pixels.setPixelColor(q, pixels.Color(0, 0, 0)); //red
          break;
      }
      if (random(0, 10) > 7) {
        pixels.setPixelColor(q, pixels.Color(0, 0, 0)); //red
      }
      pixels.show();
      ashNeoPixelCounter[q]--;
      ashNeoPixelTimer[q] = millis() + random(5, 50);
    }
  }
}

Example 3: IOM_UNO_Shedv2_singlePage.ino


Click to Download code:IOM_UNO_Shedv2_singlePage.ino

This script combines the two scrits above, once again it has been joined as a single file for ease of download.

As you can see the main changes are made in the Switch() statement that now has code for decoder 25 and 26.

25 turns the background sound and animations on or off.
26 control the sound of the firebox being cleaned and coal loaded.

Because of the way it has been coded calling the Ashpit() function will stop the background sounds and they will automatically resume once the ashpit() function has completed.

 
/*  11/10/2021
    IOM_UNO_Shedv2
    incorporates the effects from IOM_UNO_Shedv1

Sound on Card

  01 grinder 37 seconds
  02 big arc welder 7 seconds
  03 small arc weld 8 seconds
  04 drill/machine 11 secnods
  05 file 5 seconds
  06 grinder 4 seconds
  07 light hammer 4 seconds
  08 hacksaw 4 secs
  09 big machine 28 secs...could repeat
  10 another big machine 11 secs to end
  11 tool box 3 secs
  12 seagulls boats 8m 48 secs
  13 waterfall 3m 19 could repeat
  14 single whistle
  15 short whistle.horn
  16 guard whistle
  17 birds/country side 1m 50
  18 short whistle
  19 double short whistle
  20 hydraulics 2m 33
  21 ship/boat big horn
  22 coaling 21 sec repeatable

  Commands to send to DCC++ EX base station for testing
  <1>  = power on
  < a 25 0 > = accessory number 25 direction 0
  < a 25 1 > = accessory number 25 direction 1


  Pins used

  2 = interrupt pin for decoder messages/circuit
  3 software serial...MP3 player
  4 software serial...MP3 player
  5 Neopixels
  6 arc welder
*/

//Set up NMRA DCC library
#include "NmraDcc.h"
NmraDcc  Dcc ;
DCC_MSG  Packet ;

//Serial UART WAV/MP3 player
#include "SoftwareSerial.h"
#define ARDUINO_RX 4//should connect to TX of the Serial MP3 Player module
#define ARDUINO_TX 3//connect to RX of the module
SoftwareSerial myMP3(ARDUINO_RX, ARDUINO_TX);
byte sendBuffer[6]; //buffer that will be used to store commands before sending

//Neopixels
#include "Adafruit_NeoPixel.h"
// Which pin on the Arduino is connected to the NeoPixels?
#define neoPin        5
// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 16 // 16 on my test strip
Adafruit_NeoPixel pixels(NUMPIXELS, neoPin, NEO_GRB + NEO_KHZ800);

//Arc Welder variables
const int arcPin = 6;//arc welder Digital Pin
int arcWeldState;
unsigned long arcWeldSoundTimer;
unsigned long arkEndFlashTimer;
unsigned long arkFlashTimer;
int arcSoundCounter;

//Ash pit variables
unsigned long ashSoundTimer;
int ashSoundCounter;
//4 neopixels for ash effects...each to operate independently
unsigned long ashNeoPixelTimer[4];
int ashNeoPixelCounter[4] = {251, 251, 251, 251}; //values required for them to start

//Other sound variables
unsigned long soundChange;
unsigned long soundEnd;
int soundVolume;
int soundStart;
int playThisSound;
int SoundOn = 1;//defaults to on


// This function is called whenever a normal DCC Turnout Packet is received and we're in Output Addressing Mode
void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower )
{
  Serial.print("notifyDccAccTurnoutOutput: ") ;
  Serial.print(Addr, DEC) ;
  Serial.print(',');
  Serial.println(Direction, DEC) ;
  // Lines below of no interest to me so commented out but left in to show original position
  // Serial.print(',');
  // Serial.println(OutputPower, HEX) ;

  //Add thr accessory decoders you want to use into the sketch
  switch (Addr) {
    //background workshop sounds
    case 25:
      if (Direction < 1) {//< a 25 0 > background sounds off
        SoundOn = 0;
        soundEnd = millis();
      } else {            //< a 25 1 > background sounds on
        SoundOn = 1;
      }
      break;
    //coal pit sounds
    case 26:
      if (Direction < 1) {//< a 26 0 > coal off
        ashSoundCounter = 20;
        soundEnd = millis(); 
      } else {            //< a 26 1 > coal on
        playThisSound = 17;
        soundStart = 0;
        soundEnd = 0;
        
      }
      break;
    //Any address not listed above...do nothing
    default:
      break;
  }

}

void setup()
{
  Serial.begin(115200);
  Serial.println("IOM_UNO_Shedv2...");

  // Setup which External Interrupt, the Pin it's associated with that we're using and enable the Pull-Up
  Dcc.pin(0, 2, 1);//Pin 2 is the interrupt pin on an UNO
  // Call the main DCC Init function to enable the DCC Receiver
  Dcc.init( MAN_ID_DIY, 10, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0 );
  Serial.println("DCC Init Done");

  //serial mp3 player
  myMP3.begin(9600);
  delay(500);//allow everything to settle down
  //first we need to select the TF Card
  selectTFCard();
  delay(100);
  //Play a test sound number 19 on card
  playSound(19);//double whistle test on start up
  delay(3000);

  //neopixels
  pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
  pixels.clear(); // Set all pixel colors to 'off'
  pixels.show();
  //Test the neopixels
  for (int q = 0; q < 16; q++) { //pixel test
    pixels.setPixelColor(q, pixels.Color(0, 0 , 150)); //green
    pixels.show();
    delay(100);
  }
  pixels.clear(); // Set all pixel colors to 'off'
  pixels.show();
  
  //arc welder
  pinMode(arcPin, OUTPUT);
  //quick test of arc welder led
  digitalWrite(arcPin, HIGH);
  delay(500);
  digitalWrite(arcPin, LOW);
}

void loop()
{
  // You MUST call the NmraDcc.process() method frequently from the Arduino loop() function for correct library operation
  Dcc.process();
  backgroundSound();
}


/////////////////////////////////////SerialWav commands//////////////////////////////////////////

//Serial UART commands

//Manual says the code for this is 7E 03 35 01 EF so load into buffer array
void selectTFCard(){
  sendBuffer[0] = 0x7E;
  sendBuffer[1] = 0x03;
  sendBuffer[2] = 0x35;
  sendBuffer[3] = 0x01;
  sendBuffer[4] = 0xEF;
  sendUARTCommand();
}

void playSound(byte songNumber){
  sendBuffer[0] = 0x7E;
  sendBuffer[1] = 0x04;
  sendBuffer[2] = 0x41;
  sendBuffer[3] = 0x00;
  sendBuffer[4] = songNumber;
  sendBuffer[5] = 0xEF;
  sendUARTCommand();
}
//play a sound at a set volume, only seems to apply to root directory files
void playSoundAtVolume(byte volume,byte songNumber){
  sendBuffer[0] = 0x7E;
  sendBuffer[1] = 0x04;
  sendBuffer[2] = 0x31;
  sendBuffer[3] = volume;
  sendBuffer[4] = songNumber;
  sendBuffer[5] = 0xEF;
  sendUARTCommand();
}
//loop sound 7E 04 33 00 01 EF
void repeatSound(byte songNumber){
  sendBuffer[0] = 0x7E;
  sendBuffer[1] = 0x04;
  sendBuffer[2] = 0x33;
  sendBuffer[3] = 0x00;
  sendBuffer[4] = songNumber;
  sendBuffer[5] = 0xEF;
  sendUARTCommand();
}


//set the playback volume 7E 03 31 0F EF = set volume to 0x0F = 15
void setplayVolume(byte volume){
  sendBuffer[0] = 0x7E;
  sendBuffer[1] = 0x03;
  sendBuffer[2] = 0x31;
  sendBuffer[3] = volume;
  sendBuffer[4] = 0xEF;
  sendUARTCommand();
}

//stop the current track playing
void stopSound(){
  sendBuffer[0] = 0x7E;
  sendBuffer[1] = 0x02;
  sendBuffer[2] = 0x0E;
  sendBuffer[3] = 0xEF;
  sendUARTCommand();
}

void sendUARTCommand(){
  int q;
  for(q=0;q < sendBuffer[1] + 2;q++){
    myMP3.write(sendBuffer[q]);  
  }
  Serial.println("Commands Sent");
  for(q=0;q < sendBuffer[1] + 2;q++){
    Serial.println(sendBuffer[q],HEX);  
  }
  delay(25);//stops odd commands being missed
}

///////////////////////////////sound and light functions/////////////////

//sound functions
void backgroundSound(){
  if(soundStart < 1  || millis() < soundEnd){
    switch(playThisSound){
      case 1 ... 2:
        arcWeld();
        break;
      case 3:
        grinderSound();
        break;
      case 4 ... 5:
        drillSound();
        break;
      case 6 ... 9:
        fileSound();
        break;
      case 10 ... 12:
        hammerSound();
        break;
      case 13:
        bigMachineSound();
        break;
      case 14 ... 16:
        toolboxSound();
        break;
      case 17://triggered sound
        ashpit();
        break;
      default:
        break;
    }  
  }
  
  if(millis() > soundEnd){
    playThisSound = random(30);//increase this value to get more time without sounds
     //playThisSound = random(14,18);//increase this value to get more time without sounds
    Serial.print("playThisSound: ");
    Serial.println(playThisSound);
    soundStart = 0;
    digitalWrite(arcPin, LOW);
    
    if(playThisSound > 16){
        soundEnd = millis() + random(2000,20000);//take a 10 second break from playing sounds  
    }
    //if sound is turned off just keep looping nothing
    if(SoundOn < 1){
      playThisSound = 100;//
      soundEnd = millis() + random(2000,20000);//take a 10 second break from playing sounds  
    }
    
  }
}

void toolboxSound(){
  if(soundStart < 1){
    soundStart = 1;
    soundVolume = 15;
    playSoundAtVolume(soundVolume, 11); //drill 4 seconds
    soundChange = millis() + 4000;
    soundEnd = millis() + 4000;
  } 
}

void bigMachineSound(){
  if(soundStart < 1){
    soundStart = 1;
    soundVolume = 15;
    playSoundAtVolume(soundVolume, 9); // 28 seconds
    soundChange = millis() + 24000;
    soundEnd = millis() + 28000;
  }
  if(millis() > soundChange && millis() < soundEnd){
    soundChange = millis() + 500;
    soundVolume -=1;
    if(soundVolume < 0){
      soundVolume = 0;  
    }
    setplayVolume(soundVolume);   
  } 
}

void hammerSound(){
  if(soundStart < 1){
    soundStart = 1;
    soundVolume = 15;
    playSoundAtVolume(soundVolume, 7); //drill 4 seconds
    soundChange = millis() + 5000;
    soundEnd = millis() + 5000;
  } 
}

void fileSound(){
  if(soundStart < 1){
    soundStart = 1;
    soundVolume = 15;
    playSoundAtVolume(soundVolume, 5); //drill 5 seconds
    soundChange = millis() + 5000;
    soundEnd = millis() + 5000;
  } 
}

void drillSound(){
  if(soundStart < 1){
    soundStart = 1;
    soundVolume = 15;
    playSoundAtVolume(soundVolume, 4); //drill 11 seconds
    soundChange = millis() + 11000;
    soundEnd = millis() + 11000;
  } 
}

void grinderSound(){
  if(soundStart < 1){
    soundStart = 1;
    soundVolume = 15;
    playSoundAtVolume(soundVolume, 1); //grinder 37 seconds
    soundChange = millis() + 32000;
    soundEnd = millis() + 37000;
  } 
  if(millis() > soundChange && millis() < soundEnd){
    soundChange = millis() + 500;
    soundVolume -=1;
    if(soundVolume < 0){
      soundVolume = 0;  
    }
    setplayVolume(soundVolume);   
  }
}


void arcWeld() {
  if(soundStart < 1){
    soundStart = 1;
    soundEnd = millis() + 40000;
    arcSoundCounter = 0;
  } 
  if (millis() > arcWeldSoundTimer && arcSoundCounter < 5) {
    playSoundAtVolume(15, 2);
    arkEndFlashTimer = millis() + 7000;//resets the flash timer
    arcSoundCounter++;
    arcWeldSoundTimer = millis() + random(7000, 10000);
  }
  if (millis() < arkEndFlashTimer) {
    if (millis() > arkFlashTimer) {
      arkFlashTimer = millis() + random(10, 40); //next change state
      if (arcWeldState > 0) {
        arcWeldState = 0;
        digitalWrite(arcPin, LOW);
      } else {
        arcWeldState = 1;
        digitalWrite(arcPin, HIGH);
      }
      
    }
  } else {
    digitalWrite(arcPin, LOW);
  }
}

//handles 4 neopixels for falling ash plus coal filling sound
void ashpit() {
  int q;
  if (millis() > ashSoundTimer && ashSoundCounter < 20) {
    soundStart = 1;
    playSoundAtVolume(15, 22);

    ashSoundCounter++;
    ashSoundTimer = millis() + random(21000, 30000);
    soundEnd = ashSoundTimer + 5000;
  }
  if(ashSoundCounter == 20 && soundStart > 0){
    soundStart = 0;
    soundEnd = millis() + 5000; 
    pixels.clear();
    pixels.show(); 
  }
  
  //cycle through the neopixels

  if (ashNeoPixelCounter[0] == 0  && ashSoundCounter < 10) {
    for (q = 0; q < 4; q++) {
      ashNeoPixelCounter[q] = 251;
      ashNeoPixelTimer[q] = millis() + random(1000, 1200);
    }
  }
  if (ashSoundCounter  > 15) { //All ash should be cold by now
    for (q = 0; q < 4; q++) {
      pixels.setPixelColor(q, pixels.Color(0, 0, 0));
      pixels.show();
    }
  }
  for (q = 0; q < 4; q++) {
    if (millis() > ashNeoPixelTimer[q] && ashNeoPixelCounter[q] > -1) {
      switch (ashNeoPixelCounter[q]) {
        case 0 ... 24:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 0, 0)); //red
          break;
        case 25 ... 49:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 1, 0)); //red
          break;
        case 50 ... 74:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 2, 0)); //red
          break;
        case 75 ... 99:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 3, 0)); //red
          break;
        case 100 ... 124:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 4, 0)); //red
          break;
        case 125 ... 149:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 5, 0)); //red
          break;
        case 150 ... 174:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 6, 0)); //red
          break;
        case 175 ... 199:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 7, 0)); //red
          break;
        case 200 ... 224:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 8, 0)); //red
          break;
        case 225 ... 250:
          pixels.setPixelColor(q, pixels.Color(ashNeoPixelCounter[q], 9, 0)); //red
          break;
        default:
          pixels.setPixelColor(q, pixels.Color(0, 0, 0)); //red
          break;
      }
      if (random(0, 10) > 7) {
        pixels.setPixelColor(q, pixels.Color(0, 0, 0)); //red
      }
      pixels.show();
      ashNeoPixelCounter[q]--;
      ashNeoPixelTimer[q] = millis() + random(5, 50);
    }
  }
}

Additional Resource Links

http://www.scale-n.nl/ScaleN_Naslag_Arduino_DCCMonitor.aspx

DCC++ and DCC++ EX

Arduino C++ tutorials

Lesson 7: delay() v's millis(), controlling timing of programs

Arduino C++ Serial UART MP3 Tutorial

NeoPixels18/10/2021

Comments


This site has been designed to be child friendly, this means that comments cannot be added to videos or directly to the site.
To add a comment or ask a question please email the address in this image: and use Project 5 - DCC Accessory Decoder, sounds and lights Arduino UNO as a reference.