DCC++ Talking IR Throttle
d. bodnar 3-19-2016  &  11-03-2017

 

   

 

Introduction

Recently I have been experimenting with an innovative DCC project called DCC++.  It is composed of open source hardware and software that allows you to build and operate a DCC controller for well under $20.00.  For more information on DCC++ visit TrainBoard.com at this address:  http://www.trainboard.com/highball/index.php?forums/dcc.177/

In addition there are four excellent videos on YouTube.  This link is to the first video   https://www.youtube.com/watch?v=-nsVdpMhiTU - the other videos should be to the right of the first video.

In the photo at the top of this page the basic DCC++ hardware is composed of the two boards to the far left.   The bottom board is an Arduino Uno and the shield atop the Uno is an Arduino motor shield.  The Uno can be purchased from eBay for a bit less than $5.00 and the motor shield for a bit over $5.00.

Under most circumstances the DCC++ hardware is connected to a computer (Mac, Linux or Windows) via a USB cable.  Computer software, including JMRI, can be used to activate and control the DCC++ controller.

While I have used JMRI successfully with DCC++ I felt that there was a need for a simple add-on board that would serve as a throttle for DCC++. 

Much of the hardware and software used in this project are adapted from another DCC controller that I built a few years ago.  There is additional information on YouTube and my web page

In addition to operating one or two trains the throttle can also be used to control a point to point line for a trolley or small engine.  I used reed switches at each end of the line but other sensors could be used.

Hardware

As mentioned above the throttle described here interfaces with the DCC++ controller which must be set up and programmed first. 

The DCC++ controller is given commands via a standard serial connection.  These commands are in plain text and can easily be sent from either a PC or another Arduino.  An overview of these commands is here: https://github.com/DccPlusPlus/BaseStation/wiki/Commands-for-DCCpp-BaseStation

The throttle that I am using is made up of an additional Arduino, this time a smaller Pro Mini, a 2 line LCD display and an infrared transmitter / receiver unit. 

The infrared transmitter is a basic TV remote control that generates standard Sony TV codes.  The infrared receiver is designed to gather these codes and send them to the Arduino as a series of pulses that the Arduino can decode and react to.

The last component in the throttle is an MP3 sound unit that allows the throttle to speak each command that it receives.  While this may seem like an unneeded complication I think you will see that it comes in very handy when operating trains from the throttle as you don't need to look at the LCD screen to know that a command has been successfully received and acted upon.  It also allows those of us with less than ideal vision to use the throttle without using the screen at all.

This schematic shows the components that make up the throttle and how they are connected.  For the point to point two reed switches (one at each end of the track) are wired in parallel and connected to Trigger1 (SW1) - the other trigger can be used for station stops which have not been implemented in the software.

The MP3 player is circled in yellow.

The only connections between the throttle and the DCC++ controller are ground and serial transmit on the throttle to serial receive on the DCC++ controller.

Here the Arduino Pro Mini can be seen.  Note that the circuit board was designed for another project and modified for use with the DCC++ controller.

Some changes were made on the bottom of the board.

This TV remote sends Sony codes.  Just about any universal TV remote can be set to Sony format and used with this throttle.

The UP / DOWN arrows adjust the speed and the LEFT / RIGHT arrows change the direction of travel.  On this remote Power turns off the power to the H-Bridge and Exit sets the speed to zero.  The Mute key allows you to select the DCC address of a loco.

Because this remote has fewer keys some do double duty.  For example, the "*" key does the STOP function if it is pressed and released once.  If it is pressed and released a 2nd time within 1 second it shuts off the power to the DCC++ controller.

 

 

Build Notes - using board dated 1-5-15

This photo shows most of the parts used in the throttle.  The remote control in the upper right is from eBay and comes with the IR receiver module at top center.  I used a different IR receiver module, a GP1UX511QS from Mouser.   They work equally well.  The Arduino and DFPlayer are both socketed in sections cut from a 40 pin female header.  The capacitor values are not critical.  I used a 330 uf, 25 volt electrolytic and two 1 uf, 25 volt tantalums.  The DFPlayer that supplies the spoken feedback is discussed in great detail here:  http://www.trainelectronics.com/Arduino/DefectDetector/  Information is also given on how to add MP3 files to the microSD card.

The 2 line LCD and its I2C controller are in the upper left.  These are discussed here: http://www.trainelectronics.com/Arduino/  You can also purchase the LCD with the controller already soldered to it:  http://www.ebay.com/itm/Serial-IIC-I2C-TWI-1602-16X2-Character-LCD-Display-Module-FOR-Arduino-UNO-R3-YEL-/111451535550?pt=LH_DefaultDomain_0&hash=item19f30778be

Traces need to be cut in two places on the back of the board.

Here they have been cut - I used a Dremel but a hobby knife works, too.

First build up the power supply and install the headers as shown here.  Make sure you add a 2 pin female header for the Arduino's pins A4 and A5.  These two pins are not part of the two main rows of pins.  They are either on the side of the Arduino just inside of pins A2 and A3 or at one end.  There are holes in the circuit board to accommodate either placement.  You just need to add a 2 pin make header to the Arduino and a 2 pin female header to the board.

Use a volt meter to confirm that 5 volts is being sent to the VCC / ground pins.

The built in serial port is needed to talk to the DCC++ controller so the DFPlayer needs to connect its RX pin to pin 2 on the Arduino.  There is a 1K resistor between these two pins.  The red, black wires near the bottom of the board supply 5 volts and ground to the IR receiver.  The white wire goes from the output of the IR receiver to pin 11 on the Arduino.

 

 

 

 

Sound Files

The sound files are all saved as MP3's on a microSD card that goes into the DFPlayer.  They were generated using Windows built in Text to Speech application.  You could also use your voice by recording from a microphone.  I captured the sound with Audacity, an open-source audio editor.  Each file was saved with a name ranging from 0001,mp3 to 0023.mp3.  One method of creating these files is discussed towards the bottom of this page (search for the section titled Audio Files)  http://www.trainelectronics.com/Arduino/DefectDetector/

A zip file containing my sound files can be downloaded here:  DCC_Arduino/DCC++/IRThrottle/images/DCC++Sounds.zip

Libraries:

#include <IRremote.h>
#include<EEPROM.h>
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.h>

Operation
Note - first button is larger remote and second is the smaller Keyes remote ( Large / Keyes)
  • Set Loco Address - press Menu / OK - select 2-Loco ID - enter 4 digit address (1234 or 0003 or 0202)
  • Speed Up - Slow Down - press Up Arrow or Down Arrow
  • Stop - press Exit / *
  • Power Off - press Power / # or press Exit twice within 1 second (press / release / press / release within 1 second)
  • Change Direction - Right Arrow - go forward --  Left Arrow - go backward
  • Change between Loco 1 and Loco 2 - Menu / OK - select #1 to swap (must set 2nd ID after selecting it)
  • Functions - press 1-->9 for functions F1-->F9 - press 0 (zero) to clear all
  • Point to Point
    • Set speed before selecting Menu
    • press Menu / OK  - select #3 -
    • Enter 3 digit time to pause at each end (ie enter 12 seconds as 012)
    • Point to Point run begins at set speed -
       
Software - Version: DCC__Throttle-P2P-Talking-MP3-Array-v3-3P  
   The software is still under development and is sure to contain numerous bugs - it is not elegantly written and surely can be optimized!   
    You can download this file here
        DCC_Arduino/DCC++/IRThrottle/images/DCC__Throttle-P2P-Talking-MP3-Array-v3-3P.ino
    or just copy / paste the code into a blank Arduino sketch
/*
  UP = faster - 13 - hold to repeat
  DN = slower - 17 -hold to repeat
   = STOP - 11
  # = Disable / enable DCC (pin 8) -12
  < = - 14
  > = -16
  OK = Menu choices -15
  Power = power off to DCC++ (<0>)
*/
// the following defines the codes for Keyes brand and Sony IR remote controls
#define KeyesUp 0xFF629D
#define SonyUp 0x90
#define KeyesLeft 0xFF22DD
#define SonyLeft 0xC90
#define KeyesOK 0xFF02FD
#define SonyMenu 0x70
#define KeyesRight 0xFFC23D
#define SonyRight 0x490
#define KeyesDown 0xFFA857
#define SonyDown 0x890
#define Keyes1 0xFF6897
#define Sony1 0x10
#define Keyes2 0xFF9867
#define Sony2 0x810
#define Keyes3 0xFFB04F
#define Sony3 0x410
#define Keyes4 0xFF30CF
#define Sony4 0xC10
#define Keyes5 0xFF18E7
#define Sony5 0x210
#define Keyes6 0xFF7A85
#define Sony6 0xA10
#define Keyes7 0xFF10EF
#define Sony7 0x610
#define Keyes8 0xFF38C7
#define Sony8 0xE10
#define Keyes9 0xFF5AA5
#define Sony9 0x110
#define Keyes0 0xFF4AB5
#define Sony0 0x910
#define KeyesStar 0xFF42BD
#define SonyExit 0xC70
#define KeyesPound 0xFF52AD
#define SonyPower 0xA90
#define SonyMute 0x290
#define SonyPIP 0xDB0
#include<EEPROM.h>
int irButton;
int IRdelay = 240; // determines speed of repeating button pushes when button held
int LED = 13; // LED to blink when DCC packets are sent in loop
int ReedEnd = 3;  // Reed Switch at each end
int ReedSS = 4;  // Reed Switch for Station Stop
#include <IRremote.h>
int RECV_PIN = 11;
IRrecv irrecv(RECV_PIN);
decode_results results;
byte Fx = 0;
int LocoAddress[2] = {1830, 3};
int LocoDirection[2] = {1, 1};
int LocoSpeed[2] = {0, 0};
byte LocoFN0to4[2];
byte LocoFN5to8[2];
byte Locofn9to12[2];// 9-12 not yet implemented

int old_speed = 0;

int PTPSpeed = 50; // speed for point to point
int PTPTime = 3; // time after reed switch hit
int PTPFlag = 0; // set to 1 to activate Point to Point
int PTPRandom = 0;  // =0 if normal time, =1 if random (20% + rnd (80%))
int PTPTimeSave = 0;
int ReedCheck = 0;
int ZeroSpeedFlag = 0;
int ActiveAddress = 0; // make address1 active
int irCode = 0;
int inMenu = 0;  // keeps track of being in the menu area 0=out, 1=in
int inMenu2 = 0;  // keeps track of being in the menu area 0=out, 1=in

int digits = 0;
int upFlag = 0;  // trying to get keys to repeat!
int dnFlag = 0;
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#define I2C_ADDR    0x27 // <<----- Add your address here.  Find it from I2C Scanner
#define BACKLIGHT_PIN     3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7

LiquidCrystal_I2C	lcd(I2C_ADDR, En_pin, Rw_pin, Rs_pin, D4_pin, D5_pin, D6_pin, D7_pin);
#include <SoftwareSerial.h>
#include <DFPlayer_Mini_Mp3.h>
SoftwareSerial mySerial(5, 2); // RX, TX - note, RX not used
int MP3file = 0;
int buusyPin = 10;  // sound player busy
int bsy = 0;
int z = 0;

int i = 0;
int irTemp = 0;
int StopButtonPressedOnceFlag = 0; // used to detect 2nd button push within a fraction of a second
unsigned long time;
char VersionNum[] = "3.3P"; ///////////////////////////////////////////////////////////////////////////VERSION HERE///////

void setup() {
  randomSeed(analogRead(0));
  pinMode(LED, OUTPUT);
  pinMode(ReedEnd,  INPUT_PULLUP);
  pinMode(ReedSS,  INPUT_PULLUP);
  LocoAddress[0] = EEPROM.read(0) * 256;
  LocoAddress[0] = LocoAddress[0] + EEPROM.read(1);
  if (LocoAddress[0] >= 10000) { // set defalut as 3 if not in proper range (0-9999)
    LocoAddress[0] = 3;
  }
  LocoAddress[1] = EEPROM.read(2) * 256;
  LocoAddress[1] = LocoAddress[1] + EEPROM.read(3);
  if (LocoAddress[1] >= 10000) { // set defalut as 3 if not in proper range (0-9999)
    LocoAddress[1] = 3;
  }
  lcd.begin (16, 2); //  LCD is 16 characters x 2 lines
  lcd.setBacklightPin(BACKLIGHT_PIN, POSITIVE);
  lcd.setBacklight(HIGH);  // Switch on the backlight
  lcd.home (); // go home
  Serial.begin (115200);
  lcd.setCursor(0, 0);
  lcd.print("DCC++ Throttle");
  lcd.setCursor(0, 1);
  lcd.print("2-13-16 - v");//3.3p");
  for (int i = 0; i < 4; i++) {
    lcd.print(VersionNum[i]);
    //delay(500);
  }
  irrecv.enableIRIn(); // Start the receiver
  mySerial.begin(9600);  // for MP3 player
  mp3_set_serial (mySerial);  //set Serial for DFPlayer-mini mp3 module
  mp3_set_volume (30);          // 15 is low for unpowered speaker - 30 good for unpowered speaker - requires power off to reset volume
  Serial.print("2-13-2016  version ");//3.3p");
  for (int i = 0; i < 4; i++) {
    Serial.print(VersionNum[i]);
    //delay(500);
  }
  Serial.println("");
  Serial.print("<0>");// power off to DCC++ unit
  delay(1500);
  lcd.clear();

  //  PTPFlag = 1;  // set to 1 for testing
  //  PTPSpeed = 50; // speed for point to point test
  //  PTPTime = 3 ; // pause time for test

  //  LocoAddress[ActiveAddress] = DCCAddress1; // start out with main DCC address active - secondary is DCCAddress2
}

void loop() {
  doMainLCD();
  if (PTPFlag == 1) { // if set do PTP by going to PTP speed & looking for ReedCheck to change from 1 to 0
    ReedCheck = digitalRead(ReedEnd);
    if (ReedCheck == 0) { // reverse if in test area at ends
      PTPSpeed = LocoSpeed[ActiveAddress];
      LocoSpeed[ActiveAddress] = 0;// PTPSpeed;  STOP
      doDCC();
      if (LocoDirection[ActiveAddress] == 0) {
        LocoDirection[ActiveAddress] = 1;
      } else LocoDirection[ActiveAddress] = 0;
      doMainLCD();
      if (PTPRandom == 1) {
        PTPTimeSave = PTPTime;// save time for random computation
        Serial.println(PTPTime);
        PTPTime = PTPTime * .8;
        Serial.println(PTPTime);
        PTPTime =   random (0, PTPTime);
        Serial.println(PTPTime);
        PTPTime = PTPTime + PTPTimeSave * .2;
        Serial.println(PTPTime);
      }
      Serial.print(" Delay ");
      for (int xxyyzz = 0; xxyyzz <= PTPTime; xxyyzz++) {
        delay( 1000);
        lcd.setCursor(12, 1);
        lcd.print("   ");
        lcd.setCursor(12, 1);
        lcd.print(PTPTime - xxyyzz);

        Serial.print(PTPTime - xxyyzz);
        Serial.print(",");
      }
      Serial.println(" "); // move to new line
      if (PTPRandom == 1) PTPTime = PTPTimeSave;
      LocoSpeed[ActiveAddress] = PTPSpeed; // back to speed in other direction
      doDCC();
      doMainLCD();
      do {
        delay(2000);
        ReedCheck = digitalRead(ReedEnd);
      } while (ReedCheck == 0);
    }
  }
  if (irrecv.decode(&results))
  {
    translateIR();
    irrecv.resume(); // Receive the next value
  }
  if ((irButton > 0 && irButton < 13) | (irButton == 14 | irButton == 16)) {
    upFlag = 0;
    dnFlag = 0;
  }
  if (irButton != 13 && irButton != 17 && irButton != 0) { // set delay to slower speed for non repeating keys
    IRdelay = 240;
  }
  if (irButton >= 1 && irButton <= 10) { //Funtions done with numbers 1-9 - clear all with 10.......................
    mp3_play(irButton);
    doFunction();
    irButton = 0;
    irTemp = 0;
  }
  if (irButton == 15) {  // was 19 (PIP on Sony) changed to 15 (Menu-sony or OK-Keyes.......................MENU
    PTPSpeed = LocoSpeed[ActiveAddress];  // save current speed for use in PTP
    ZeroSpeedFlag = 1;
    all2ZeroSpeeed(); // stop all when entering menu
    doOKMENU();
    ZeroSpeedFlag = 0;
    all2ZeroSpeeed(); // restore all when exiting menu
    //    all2SavedSpeed(); // restore speed when done with menu
  }
  if (irButton == 14 ) { //Right volume button - reverse if going forward....................... RIGHT ARROW
    LocoDirection[ActiveAddress] = 0;//!LocoDirection[ActiveAddress] ;
    mp3_play(12);
    dlayPrint();
    lcd.setCursor(6, 0);
    lcd.print("<");
    upFlag = 0;
    dnFlag = 0;
    LocoSpeed[ActiveAddress] = constrain(LocoSpeed[ActiveAddress], 0, 126);
    doDCC();
    old_speed = LocoSpeed[ActiveAddress] ;
    irButton = 0;
    saySpeed();
  }
  if (irButton == 16 ) { //Left volume button - reverse if going backward....................... LEFT ARROW
    LocoDirection[ActiveAddress] = 1;// !LocoDirection[ActiveAddress] ;
    mp3_play(11);
    dlayPrint();
    lcd.setCursor(6, 0);
    lcd.print(">");
    upFlag = 0;
    dnFlag = 0;
    LocoSpeed[ActiveAddress] = constrain(LocoSpeed[ActiveAddress], 0, 126);
    doDCC();
    old_speed = LocoSpeed[ActiveAddress] ;
    irButton = 0;
    saySpeed();
  }
  // get DCC address
  if (irButton == 18 ) //Mute button.............................................................. MUTE
  {
    getDCCAddress();
  }
  if (irButton == 13 | (irButton == 99 && upFlag == 1)) // UP and repeat key (99).......................UP
  {
    Serial.print("locospeed0 ");
    Serial.println(LocoSpeed[ActiveAddress]);
    (LocoSpeed[ActiveAddress])++;
    upFlag = 1;
    dnFlag = 0;
    irButton = 0;
    Serial.println("found UP 000");
    Serial.print("locospeed1 ");
    Serial.println(LocoSpeed[ActiveAddress]);
    bsy = digitalRead(buusyPin);
    if (bsy == 1) mp3_play(14);
    IRdelay = 20;
  }
  if (irButton == 12)//..................................................................... POWER
  {
    //   Serial.println("found Power ");
    Serial.print("<0>");
    mp3_play(13);
    irButton = 0;
  }
  if (irButton == 17 | (irButton == 99 && dnFlag == 1))  // DOWN..................................DOWN
  {
    IRdelay = 20;  // speed up for repeating keys
    if (LocoSpeed[ActiveAddress] > 0) (LocoSpeed[ActiveAddress])--;
    dnFlag = 1;
    upFlag = 0;
    if (LocoSpeed[ActiveAddress] > 0) {
      bsy = digitalRead(buusyPin);
      if (bsy == 1) mp3_play(15);
    }
    if (LocoSpeed[ActiveAddress] == 0) mp3_play(16);
    irButton = 0;
  }
  if (irButton == 11) //* key  - EXIT does STOP.............................................. EXIT
  {
    if (StopButtonPressedOnceFlag == 1) {
      if (millis() - time <= 1000 )
      {
        Serial.println("double hit on STOP");  // key hit again within 1 second
        Serial.print("<0>");
        delay(100);
        mp3_play(13);
        irButton = 0;
      }
      StopButtonPressedOnceFlag = 0;
    }
    if (StopButtonPressedOnceFlag == 0) {
      time = millis();
      StopButtonPressedOnceFlag = 1;
    }
    LocoSpeed[ActiveAddress] = 0;
    irButton = 0;
    mp3_play(16);
  }
  //Functions will not work without this to limit speed command to only new speeds
  if (LocoSpeed[ActiveAddress] != old_speed)
  {
    LocoSpeed[ActiveAddress] = constrain(LocoSpeed[ActiveAddress], 0, 126);
    doDCC();
    old_speed = LocoSpeed[ActiveAddress] ;
    //   Serial.print("LocoSpeed[ActiveAddress] 4 ");
    Serial.println(LocoSpeed[ActiveAddress] , DEC);
  }
}  //END LOOP

void doPTP() {
  inMenu = 1;
  Serial.println("@ PTP  ");
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Point to Point");
  mp3_play(19);
  dlayPrint();
  irButton = 0;
  lcd.clear();
  lcd.print ("1.time  3.GO");
  lcd.setCursor(0, 1);
  lcd.print ("2.RND   T=");
  lcd.print(PTPTime);
  lcd.print(" sec");
  ReedCheck = digitalRead(ReedEnd);
  do {
    if (irrecv.decode(&results))
    {
      translateIR();
      irrecv.resume(); // Receive the next value
    }
    if (irButton == 3) {
      inMenu = 0; // exit on 3
      PTPFlag = 1; // turn on PTP
    }
    if (irButton == 1 || irButton == 2 ) //............................................................... 1 set time or random time
    {
      if (irButton == 2) {
        PTPRandom = 1;
      }
      else PTPRandom = 0;
      irButton = 0;
      Serial.print("Random = ");
      Serial.println(PTPRandom);
      lcd.clear();
      lcd.print("Enter 3 dig pause");
      inMenu2 = 1;
      getPTPTime();
      Serial.println("Back @ ptp");
      inMenu = 0;
      lcd.clear();
      doMainLCD();
      irButton = 0;
      inMenu  = 0;
      Serial.print("PTP EXIT ?? ");
      PTPFlag = 1;
    }
  } while (inMenu == 1);
  irButton = 0;
  inMenu  = 0;
  Serial.print("PTP EXIT ?? ");
  PTPFlag = 1;
}

void doOKMENU() {
  inMenu = 1;
  irButton = 0;
  // Serial.println("@ doOKMENU  ");
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("1-Swap    3-PTP");
  lcd.setCursor(0, 1);
  lcd.print("2-ID      4-Exit");
  lcd.setCursor(5, 1);
  lcd.print(LocoAddress[ActiveAddress]);
  mp3_play(20);
  dlayPrint();
  do {
    if (irrecv.decode(&results))
    {
      translateIR();
      irrecv.resume(); // Receive the next value
      Serial.print("TOP IR Button ");
      Serial.println(irButton);
      if (irButton > 0 && irButton < 5 || irButton == 15) {
        if (irButton != 15) {
          mp3_play(irButton);
          dlayPrint();
        }
        if (irButton == 2) {
          getDCCAddress();
        }
        if (irButton == 3) {
          doPTP();
        }
        if (irButton == 1) {
          if (ActiveAddress == 0) {
            mp3_play(23);
            dlayPrint();
            ActiveAddress = 1;
          }
          else {
            mp3_play(22);
            dlayPrint();
            ActiveAddress = 0;
          }
          lcd.setCursor(4, 1);
          lcd.print("     ");
          lcd.setCursor(5, 1);
          lcd.print(LocoAddress[ActiveAddress]);
        }
        if (irButton == 4 || irButton == 15)  { // button 4 or menu to exit
          inMenu = 0; // exit on 4
          irButton = 0;
        }
      }
    }
    irButton = 0;
  } while ( inMenu == 1);
  lcd.clear();
  doMainLCD();
}

void doMainLCD() {
  lcd.setCursor(0, 0);
  lcd.print("S=");
  lcd.print(LocoSpeed[ActiveAddress], DEC);
  lcd.print("  ");
  lcd.setCursor(6, 0);
  if (LocoDirection[ActiveAddress] == 1 ) {
    lcd.print(">");
  }
  else {
    lcd.print("<");
  }
  lcd.setCursor(8, 0);
  lcd.print("A=");
  if (LocoAddress[ActiveAddress] <= 9) {
    lcd.print("0");  // add leading zero for single digit addresses
  }
  lcd.print(LocoAddress[ActiveAddress] , DEC);
  lcd.setCursor(14, 0);
  lcd.print("-");
  lcd.setCursor (15, 0);       // go to end of 1st line
  lcd.print(ActiveAddress + 1);
  lcd.setCursor(5, 1); // start of 2nd line
  String temp = "0000" + String(LocoFN0to4[ActiveAddress], BIN);  // pad with leading zeros
  int tlen = temp.length() - 5;
  lcd.print(temp.substring(tlen));
  temp = "000" + String(LocoFN5to8[ActiveAddress], BIN);
  tlen = temp.length() - 4;
  lcd.setCursor(0, 1); // start of 2nd line
  lcd.print(temp.substring(tlen));
  if (PTPFlag == 1) {
    lcd.setCursor(12, 1);
    lcd.print("P");
    if (PTPRandom == 1) {
      lcd.print("R");
    }
  }
}

void saySpeed() {
  int tempSpeed = LocoSpeed[ActiveAddress] ;
  int digHundreds = tempSpeed / 100;
  if (digHundreds >= 1) {
    mp3_play(digHundreds);
    dlayPrint();
    tempSpeed = tempSpeed - digHundreds * 100;
  }
  int digTens = tempSpeed / 10;
  if (digHundreds >= 1 || digTens >= 1 ) {
    if (digTens == 0) {
      mp3_play(10);
    } else {
      mp3_play (digTens);
    }
    dlayPrint();
  }
  tempSpeed = tempSpeed - digTens * 10;
  if (tempSpeed == 0) {
    mp3_play(10);
  } else {
    mp3_play (tempSpeed);
  }
  dlayPrint();
}

int translateIR() // takes action based on IR code received
// describing KEYES Remote IR codes (first) and Sony IR codes (second)
{
  //  Serial.print("value - hex ");
  // Serial.println(results.value, HEX);
  switch (results.value)
  {
    case KeyesUp:   //Keyes remote code for UP
    case SonyUp: //Sony remote code for UP
      //Serial.println(" UP");
      irButton = 13;
      break;
    case KeyesLeft:
    case SonyLeft:
      //Serial.println(" LEFT");
      irButton = 14;
      break;
    case KeyesOK:
    case SonyMenu:
      //Serial.println(" - MENU - ");
      irButton = 15;
      break;
    case KeyesRight:
    case SonyRight:
      //Serial.println(" RIGHT");
      irButton = 16;
      break;
    case KeyesDown:
    case SonyDown:
      //Serial.println(" DOWN");
      irButton = 17;
      break;
    case Keyes1:
    case Sony1:
      //Serial.println(" 1");
      irButton = 1;
      break;
    case Keyes2:
    case Sony2:
      //Serial.println(" 2");
      irButton = 2;
      break;
    case Keyes3:
    case Sony3:
      //Serial.println(" 3");
      irButton = 3;
      break;
    case Keyes4:
    case Sony4:
      //Serial.println(" 4");
      irButton = 4;
      break;
    case Keyes5:
    case Sony5:
      //Serial.println(" 5");
      irButton = 5;
      break;
    case Keyes6:
    case Sony6:
      //Serial.println(" 6");
      irButton = 6;
      break;
    case Keyes7:
    case Sony7:
      //Serial.println(" 7");
      irButton = 7;
      break;
    case Keyes8:
    case Sony8:
      //Serial.println(" 8");
      irButton = 8;
      break;
    case Keyes9:
    case Sony9:
      //Serial.println(" 9");
      irButton = 9;
      break;
    case Keyes0:
    case Sony0:
      //Serial.println(" 0");
      irButton = 10;
      break;
    case KeyesStar:
      //Serial.println(" *");
      irButton = 11;
      break;
    case SonyExit:  //EXIT
      //Serial.println(" *");
      irButton = 11;
      break;
    case KeyesPound:
      //Serial.println(" #");
      irButton = 12;
      break;
    case SonyPower:  //POWER
      //Serial.println(" #");
      irButton = 12;
      break;
    case SonyMute:  //Mute
      //Serial.println(" #");
      irButton = 18;
      break;
    case SonyPIP:
      Serial.println(" PIP");
      irButton = 19;
      break;
    case 0xFFFFFFFF:
      //Serial.println(" REPEAT");
      irButton = 99;
      break;
    default:
      irButton = 99;
  }// End Case
  delay(IRdelay); // Determines auto repeat of IR buttons
} //END translateIR

//START DO FUNCTION BUTTONS
int doFunction() {
  irTemp = irButton - 1;
  lcd.setCursor (14, 1);       // go to end of 2nd line
  ///  lcd.print("FN code ");
  lcd.print(irButton, DEC);
  // Serial.print("got a keypad button ");
  // Serial.println(irButton, DEC);
  if (irTemp <= 4) {
    if (bitRead(LocoFN0to4[ActiveAddress], irTemp) == 0 ) {
      bitWrite(LocoFN0to4[ActiveAddress], irTemp, 1);
    }
    else {
      if (bitRead(LocoFN0to4[ActiveAddress], irTemp) == 1 ) {
        bitWrite(LocoFN0to4[ActiveAddress], irTemp, 0);
      }
    }
    doDCCfunction04();
    Serial.print(LocoFN0to4[ActiveAddress], BIN);
    Serial.println(" LocoFN0to4[ActiveAddress] d ");
    Serial.print(LocoFN0to4[ActiveAddress], DEC);
    Serial.println(" LocoFN0to4[ActiveAddress]");
  }
  if (irTemp >= 5 && irTemp <= 8) {
    irTemp = irTemp - 5;
    if (bitRead(LocoFN5to8[ActiveAddress], irTemp) == 0 ) {
      bitWrite(LocoFN5to8[ActiveAddress], irTemp, 1);
    }
    else {
      if (bitRead(LocoFN5to8[ActiveAddress], irTemp) == 1 ) {
        bitWrite(LocoFN5to8[ActiveAddress], irTemp, 0);
      }
    }
    doDCCfunction58();
    Serial.print(LocoFN5to8[ActiveAddress], BIN);
    Serial.println(" LocoFN5to8[ActiveAddress] d ");
    Serial.print(LocoFN5to8[ActiveAddress], DEC);
    Serial.println(" LocoFN5to8[ActiveAddress]");
  }
  if (irButton == 10)
  {
    lcd.setCursor (14, 1);       // go to end of 2nd line
    ///    lcd.print("FN code ");
    lcd.print(irButton, DEC);
    irButton = 0;
    LocoFN0to4[ActiveAddress] = B00000000; //clear variables for which functions are set
    LocoFN5to8[ActiveAddress] = B00000000;
    doDCCfunction04();
    doDCCfunction58();
    delay(500);
    irButton = 0;
  }
  irButton = 0;
  delay(500);
}
//END DO FUNCTION BUTTONS
void doDCCfunction04() {
  Serial.write("<f ");
  Serial.print(LocoAddress[ActiveAddress] );
  Serial.print(" ");
  int fx = LocoFN0to4[ActiveAddress] + 128;
  Serial.print(fx);
  Serial.print(" >");
}

void doDCCfunction58() {
  Serial.write("<f ");
  Serial.print(LocoAddress[ActiveAddress] );
  Serial.print(" ");
  int fx = LocoFN5to8[ActiveAddress] + 176;
  Serial.print(fx);
  Serial.print(" >");
}

// routine to stay here till busy pin goes low once then goes high after speech item completes
void dlayPrint()
{
  delay(50);// was 100
  int bsyflag = 0;
  Serial.println(" ");
  // Serial.print("buusypin ");
  for ( z = 0; z <= 60 ; z++) { // was 300
    bsy = digitalRead(buusyPin);
    Serial.print(bsy);
    delay(20);
    if (bsyflag == 1 && bsy == 1) {
      break;
    }
    if (bsy == 0) {
      bsyflag = 1;
    }
  }
  Serial.println(" ");
  Serial.println("done");
}

void doDCC() {
  Serial.print("d = ");
  Serial.println(LocoDirection[ActiveAddress] );
  Serial.print("<1>");
  Serial.print("<t1 ");
  Serial.print(LocoAddress[ActiveAddress] );//locoID);
  Serial.print(" ");
  Serial.print(LocoSpeed[ActiveAddress] );
  Serial.print(" ");
  Serial.print(LocoDirection[ActiveAddress] );
  Serial.write(">");
}

void getPTPTime() {
  i = 0;
  int xxx = 1;
  do {
    if (irrecv.decode(&results))
    {
      translateIR();
      irrecv.resume(); // Receive the next value
      mp3_play(irButton);
    }
    if (irButton == 15) {
      //      LocoAddress[ActiveAddress] = DCCAddress1; // restore first address since it was set to 0
      inMenu2 = 0;
    }
    if (irButton > 0 && irButton < 11) {
      if (irButton == 10) irButton = 0;
      xxx = i * 10;
      if (i == 0) xxx = 0;
      if (i >= 1) xxx = 10;
      PTPTime = PTPTime  * xxx;
      PTPTime = PTPTime + irButton;
      irButton = 0;
      ++i;
      lcd.setCursor(0, 1);
      lcd.print("               ");
      if (PTPTime == 0) {
        lcd.setCursor(10 + i, 1);
      } else lcd.setCursor(10, 1);
      lcd.print(PTPTime );
      if (i == 3) inMenu2 = 0; // exit after 3 digits entered
    }
    if (inMenu2 == 0) {
      inMenu2 = 0;
      irButton = 0;
      digits = 0;
      lcd.clear();
      break;
    }
    irButton = 0;
  } while ( inMenu2 = 1);
  IRdelay = 20; //speed up auto repeat
  Serial.println("Exiting getPTPTime");
}

void getDCCAddress() {
  mp3_play(18);
  delay(200);
  dlayPrint();
  delay(500);
  inMenu = 1;
  lcd.clear(); // blank screen
  lcd.setCursor(0, 0);
  lcd.print("Loco Address ");
  if (ActiveAddress == 0) {
    lcd.print("1");
  } else lcd.print("2");
  lcd.setCursor(0, 1);
  lcd.print("Enter 4 digits");
  irButton = 0;
  LocoAddress[ActiveAddress] = 0;
  i = 0;
  int xxx = 1;
  do {
    if (irrecv.decode(&results))
    {
      translateIR();
      irrecv.resume(); // Receive the next value
      mp3_play(irButton);
    }
    if (irButton == 15) {
      //      LocoAddress[ActiveAddress] = DCCAddress1; // restore first address since it was set to 0
      inMenu = 0;
    }
    if (irButton > 0 && irButton < 11) {
      if (irButton == 10) irButton = 0;
      xxx = i * 10;
      if (i == 0) xxx = 0;
      if (i >= 1) xxx = 10;
      LocoAddress[ActiveAddress] = LocoAddress[ActiveAddress]  * xxx;
      LocoAddress[ActiveAddress] = LocoAddress[ActiveAddress] + irButton;
      irButton = 0;
      ++i;
      lcd.setCursor(0, 1);
      lcd.print("               ");
      if (LocoAddress[ActiveAddress] == 0) {
        lcd.setCursor(10 + i, 1);
      } else lcd.setCursor(10, 1);
      lcd.print(LocoAddress[ActiveAddress] );
      if (i == 4) inMenu = 0; // exit after 4 digits entered
    }
    if (inMenu == 0) {
      inMenu = 0;
      irButton = 0;
      digits = 0;
      lcd.clear();
      break;
    }
    irButton = 0;
  } while ( inMenu = 1);
  IRdelay = 20; //speed up auto repeat
  xxx = LocoAddress[0] / 256;
  EEPROM.write(0, xxx);
  xxx = LocoAddress[0] - (xxx * 256);
  EEPROM.write(1, xxx);

  xxx = LocoAddress[1] / 256;
  EEPROM.write(2, xxx);
  xxx = LocoAddress[1] - (xxx * 256);
  EEPROM.write(3, xxx);
}

void all2ZeroSpeeed() {  // set flag to 1 to stop, set to 0 to restore
  for (int tempx = 0; tempx <= 1; tempx++) {
    Serial.print("<t1 ");
    Serial.print(LocoAddress[tempx] );//locoID);
    Serial.print(" ");
    if (ZeroSpeedFlag == 1) {
      Serial.print(0);//LocoSpeed[0] );
    }
    else Serial.print(LocoSpeed[0] );
    Serial.print(" ");
    Serial.print(LocoDirection[1] );
    Serial.write(">");
  }
}