Last Updated: 03/04/2024
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 https://www.digitaltown.co.uk/79DCCDecoderCircuit.php
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.
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..
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
https://www.digitaltown.co.uk/79DCCDecoderCircuit.php
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.