Project tutorial
Model Rail Digital DCC Command Station Arduino and Free App

Model Rail Digital DCC Command Station Arduino and Free App © LGPL

No need to purchase any propriety systems, this is a stand alone unit operated by a free App called 'LocoMotive'

  • 22,411 views
  • 6 comments
  • 24 respects

Components and supplies

Apps and online services

LocoMotive
This is the free version of the App
LocoMotive DCC
This is the latest version including read / write of cv 1 to 255
Locomotive DCC 2
Latest full App with 50 way roster, CV 1-1024, PoM,

About this project

What is different about this system ?A very simple electronic circuit provide the DCC signal and power for the track, however the App does the real work ! pdate The computer on your phone is utilised to the full by constructing the codes required to form each packet of instructions, thus simplifying Uthe job of the Arduino controller !

Update 17 Jan 2022: New App 'Locomotive DCC 2' with roster for 50 locos with short or long address. CV read/write 1-1024. Program on the main (PoM) where consists or double headers may be set up. Also, modified circuit diagram replace a 10k with a 470 ohm resistor.

Update 16 August 2019: Release of new improved App - with facility to Read / Write CV 1 to 255 This App version costs £5 plus tax and available on the Google Play Store.

Update 18 May 2019: Release of new improved App - up to 28 functions with editable titles, visibility and momentary options. Control up to 4 locos at a timewith speed bars on the screen. This App version costs £3 plus tax and available on the Google Play Store.

Update 17 Feb 2019: You may have to modify the HC-06 Bluetooth module to extend its range of reception.I added a solid copper wire (from mains earth wire) 31.2 mm in length (1/4 of wave length for 2.4 GHz). See attached photo in Step 2.

Update 29 Jan 2019: Addition of momentary option on F2 and F3 for horn sounds Preset max speed for each locoThis is the most efficient DCC command station possible to build. No other system can do so much with so few components.An easy to assemble fully functioning DCC Command station with PCB available on eBay plus requiring only a few parts.The App 'LocoMotive' is designed to operate on Android phones running version 4.1 and upwards.No need to purchase any propriety systems, this is a stand alone unit operated by a free App called 'LocoMotive'Works with NMRA compliant decoders. This system compiles the operating DCC packets within the App which are sent to the Arduino interface to combine with the DCC clock signals. Purchase this PCB on eBay.Get free App LocoMotive on Google Play Store.Features include:

  • Control of 1 to 20 locos
  • Ideal for small to medium size layouts
  • 2 Amps load drives up to 16 of OO/HO locomotives using H-bridge specified
  • Add a higher current compatible h-bridge to extend load capacity
  • Short circuit protected
  • Automatic over current cut-out, adjustable in Arduino code
  • Lights and direction
  • Functions 1 to 8
  • Edit function names, visibility and momentary action
  • Turnout / points / accessories operates up to 8 pairs of outputs
  • Edit turnout names
  • Custom naming of your locos and set max speed for each
  • Programming the CV1 loco address
  • Read / write CV 1 to 255
  • Add your own accessory addresses
  • Choose a DC power source to suit scale used (Z/N/OO/HO/O/G) 12v to 20v

Further to previous work on DCC wireless systems, I have developed a Bluetooth Command Station linked to a receiver Arduino based circuit with a HC-06 BT module and a LMD18200 H-bridge Motor driver delivering 2 Amps.

The overall cost of parts is around £20 with parts purchased from eBay.

Step 1: Arduino Sketch

The full sketch is attached. This portion is the loop and data receiver code. The Android App sends a complete packet to the receiver and is decoded here into an array of data. Depending on whether 3 or 4 bytes are received the code will generate the correct DCC packet format for the train track.

void loop() {if(bluetooth.available() > 0); {inString = bluetooth.readStringUntil('\n');}if (inString.substring(0, 1)== "D") {string();}delay(20);}

The string() void then splits out the data from the text string received and places each instruction into itsappropriate position in the DCC packet.

For example:

A received text string of 'DD, 3, 63, 12, 48, ' translates into a 4 byte Speed packet of 8 bits each :

DD = message IDByte: [1] [2] [3] [4]Decimal: [3] [63] [12] [48] Binary: [0000 0011] [0001 1111] [0000 1100] [0001 1000] Action: [Loco 3] [speed steps] [ loco speed] [ XOR data check]

A received text string of 'DD, 1, 129, 128, ' translates into a 3 byte Function packet of 8 bits each :Decimal: [1] [129] [128] Binary: [0000 0001] [1000 0001] [1000 0000] Action: [Loco 1] [ F1 on] [XOR data check]

For more information on the NMRA packet standard refer to:https://www.nmra.org/sites/default/files/s-9.2.1_2...

The LMD requires the DCC signal on the DIR pin and the PWM pin held HIGH.

Step 2: Fritzing Circuit Diagram

The Arduino Pro Mini receives the Bluetooth data via the HC-06 module. The Arduino generates the NMRA standard clock cycles of data in packet format. The received data interrupts the sequence when new data is received and the DCC packet is updated to send instructions to the train tracks.

List of parts required:

PCB available on eBay.

1 off Arduino Pro Mini Atmega328P 5V/16M

1 off LMD18200T H-bridge

1 off HC-06 Bluetooth module

1 off 0.1 ohm 2W Metal Film Resistor 0.1R 2 Watt (11.5 mm x 4.5 mm)

3 off Capacitor 0.1uf

1 off 10kΩ Resistor

1 off 470Ω Resistor

1 off Capacitor 10uf 25v

1 off Capacitor 220uf 16v

1 off Phoenix Contact MKDS 1/ 2-3, 5 2 Way Screw PCB Terminal Block 13.5A 200V 3.5mm

1 off 4.7kΩ Resistor

1 off L7805 CV Positive Voltage Regulator IC

1 off 4 pin Female Header Edge Pins Strip 0.1" 2.54mm

2 off 12 pin Female Header Edge Pins Strip 0.1" 2.54mm

1 off 6 pin 2.54mm PCB Universal Screw Terminal Block

1 off Zener Diode 3.6V; 0.5W 1N4732A

Wire

Power `supply: For OO/HO layouts use a 14v DC power supply (e.g. laptop charger unit from eBay). For example this delivers up to 2.5 Amps. Check the size of panel mounted socket required to fit the DC jack plug on the power supply chosen for this project. https://www.ebay.co.uk/itm/Samsung-AC-DC-Adapter-...

To extend the range of the HC-06 Bt module, solder a wire onto the end of the antenna on the module. I used a solid copper wire (from mains earth wire) 31.2 mm in length (1/4 of wave length for 2.4 GHz). See photo above.Please note - the HC-06 connections are Rx (on PCB) to Rx (on module) and Tx to Tx. Please note, the antenna wire should be covered in sleeving to avoid accidental shorting with other components.

For the use of a LMD18200 h-bridge module (as displayed above this is in module form with heatsink etc), I have added current sense components to provide protection from short circuit or overload issues on the train track, The updated circuit diagram and Arduino code now includes the current sense function.

The code limits the max current to 2 Amps when reached, the LMD18200 h-bridge is shut down by applying a HIGH signal from D13 on the Arduino to the 'Brake' input on the h-bridge.

The use of a 0.1 ohm current sense resistor gives an analog input range of 0 to 200 mv on Arduino sensor pin A0 for up to 2 Amps load. For approx 5mv per analog step input (1023 total steps or divisions) gives 0 to 40 divisions or 2 Amps / 40 = 50mA per division. The measured current precision is 50 mA however we are protecting the h-bridge when 2 Amps is reached. The LMD18200 also has in-built over temperature protection as a back up.if(C >2000){ // 2 amps

Serial.println("Over Current"); digitalWrite(13, HIGH); }

The value of the current drawn is sent to the Android App via Bluetooth every second.void send_c(){t = millis() - lastmillis; if (t >= interval){ bluetooth.write(sensorValue); bluetooth.write('c'); lastmillis = millis(); } }

Step 3: Android App

Download the free App from Google Play Store - 'LocoMotive'

Download the full App (£5.99) from Google Play Store - 'LocoMotive DCC'

Download the full App (long address/PoM/CV 1-1024) )£8.49) from Google Play Store - 'Locomotive DCC 2'

Instructions are attached for the full version App.

Code

Latest Arduino code (v13) 17 Jan 2022Arduino
// v13 3 May 2021 changed 1k to 470 ohm 
// v12 2 April 2021 high CV read/write
// v11 30 March 2021 added bt fail safe
// v10 24/3/21  10k changed for 1k feeding 10uf cap and  Cs = Ccv
// 14/2/21 current set at 2 amps
// 12/10/20 added code for locomotive dcc 2 
// 23/9/20 added Ccv and version
// 9 June 2020
// added Cinst to void loop
// changed SoftwarSerial to AltSoftSerial to avoid conflict with timer 2 (DCC o/p signal goes to DC)
// amended short protection while read / write of CV's
// changes to sketch to enable Hornby decoder CV read - moved valid and reset packets to start of void see //**1
// sketch for use with Android App 'Locomotive DCC'using LMD18200 h-bridge
// changed start up data format

#include <AltSoftSerial.h>

AltSoftSerial bluetooth(8,9);  // RX TX

String Version = "ver: hcv ccv locomotive_dcc_lmd_onboard_v13";

int C;
float Cinst;
int inst_value;
int sensorValue;
int MsbAddr;
int LsbAddr;

long bt_fail;

String inString;
int a[8];
int preamable_type = 0;
int Address;
float CV_VAL;
float cv_val;
float cv_val0;
float cv_val1;
float cv_val2;
float cv_val3;
float cv_val4;
float cv_val5;
float cv_val6;
float cv_val7;
int test_num;
int cv_write_val;
int Cs;
int Ccv;
boolean cv_logic;
boolean ok;
boolean power_off;
int num;

int ops_cv_num1;
int ops_cv_num2;
int ops_cv_val;

// use digital pins 6 and 5 for DCC out

//Timer frequency is 2MHz for ( /8 prescale from 16MHz 
#define TIMER_SHORT 0x8D  // 58usec pulse length 
#define TIMER_LONG  0x1B  // 116usec pulse length 
//#define TIMER_SHORT 0xC6 // 58usec pulse length for 8Mhz clock
//#define TIMER_LONG 0x8D // 116usec pulse length for 8Mhz clock

unsigned char last_timer=TIMER_SHORT;  // store last timer value
   
unsigned char flag=0;  // used for short or long pulse
unsigned char every_second_isr = 0;  // pulse up or down

// definitions for state machine 
#define PREAMBLE 0    
#define SEPERATOR 1
#define SENDBYTE  2

unsigned char state= PREAMBLE;
unsigned char preamble_count = 16;
unsigned char index = 0;  // **
unsigned char outbyte = 0;
unsigned char cbit = 0x80;


// variables

unsigned char  xdata = 0, data = 0;
int locoAdr = 9;   // this is the default address of the loco

// buffer for command
struct Message {
   unsigned char data[7];
   unsigned char len;
} ;

#define MAXMSG 3

struct Message msg[MAXMSG] = { 
    { { 0xFF,0, 0xFF, 0, 0, 0, 0}, 3},   // idle msg
    { { locoAdr, 0x3f, 0,  0x36, 0, 0, 0}, 4},   // locoMsg with 128 speed steps 0x3f
    { { 16, 0, 0, 0, 0, 0, 0}, 3}  // ** added this message to handle preamble 
                                   // and idle message changes as required
  };               // loco msg must be filled later with speed and XOR data byte
                                
int msgIndex=0;  
int byteIndex=0;


//Setup Timer2.
//Configures the 8-Bit Timer2 to generate an interrupt at the specified frequency.
//Returns the time load value which must be loaded into TCNT2 inside your ISR routine.

void SetupTimer2(){
  //Timer2 Settings: Timer Prescaler /8, mode 0
  //Timmer clock = 16MHz/8 = 2MHz oder 0,5usec
  TCCR2A = 0;
  TCCR2B = 0<<CS22 | 1<<CS21 | 0<<CS20; 

  //Timer2 Overflow Interrupt Enable   
  TIMSK2 = 1<<TOIE2;

  //load the timer for its first cycle
  TCNT2=TIMER_SHORT; 
}
//Timer2 overflow interrupt vector handler
ISR(TIMER2_OVF_vect) {
  //Capture the current timer value TCTN2. This is how much error we have
  //due to interrupt latency and the work in this function
  //Reload the timer and correct for latency.  
  unsigned char latency;
  // for every second interupt just toggle signal
  if (every_second_isr)  {
    PORTD = B01100000;  //use this instead of digitalWrite(6,1); digitalWrite(5,1);for LMD18200 ---  5 to 'DIR' DCC signal and 6 to 'PWM' held HIGH
    //digitalWrite(6,1); digitalWrite(5,1);
     every_second_isr = 0;        
     // set timer to last value
     latency=TCNT2;
     TCNT2=latency+last_timer;  
  }  else  {  // != every second interrupt, advance bit or state
     PORTD = B01000000;  //for LMD18200 digitalWrite(6, 1); digitalWrite(5, 0)
     //digitalWrite(6, 1); digitalWrite(5, 0);
     every_second_isr = 1; 
     switch(state)  {
       case PREAMBLE:
           flag=1; // short pulse
           preamble_count--;
           if (preamble_count == 0)  {  // advance to next state
              state = SEPERATOR;
              // get next message
              msgIndex++;
              if (msgIndex >= MAXMSG-1)  {  msgIndex = index;}  // **
              byteIndex = 0; //start msg with byte 0
           }
           break;
        case SEPERATOR:
           flag=0; // long pulse
           // then advance to next state
           state = SENDBYTE;
           // goto next byte ...
           cbit = 0x80;  // send this bit next time first         
           outbyte = msg[msgIndex].data[byteIndex];
           break;
        case SENDBYTE:
           if (outbyte & cbit)  { 
              flag = 1;  // send short pulse
           }  else  {
              flag = 0;  // send long pulse
           }
           cbit = cbit >> 1;
           if (cbit == 0)  {  // last bit sent, is there a next byte?
              byteIndex++;
              if (byteIndex >= msg[msgIndex].len)  {
                 // this was already the XOR byte then advance to preamble
                 state = PREAMBLE;
                 preamble_count = msg[2].data[0];  // **
                 index = msg[2].data[1];           // **
              }  else  {
                 // send separtor and advance to next byte
                 state = SEPERATOR ;
              }
           }
           break;
     }   
     if (flag)  {  // if data==1 then short pulse
        latency=TCNT2;
        TCNT2=latency+TIMER_SHORT;
        last_timer=TIMER_SHORT;
     }  else  {   // long pulse
        latency=TCNT2;
        TCNT2=latency+TIMER_LONG; 
        last_timer=TIMER_LONG;
     }  
  }
}


void setup(){
Serial.begin(115200);
analogReference(INTERNAL);
pinMode(13, OUTPUT);   

Ccv = 60;
bt_fail = 1;

 bluetooth.begin(9600);
 bluetooth.setTimeout(500);
    delay(500); // wait for bluetooth module to start
    //Serial.println("Bluetooth Started");  
    bluetooth.println("Bluetooth Started");

DDRD = B01100000;   //  register D5 for digital pin 5, D6 for digital pin 6 
 //Start the timer 
  SetupTimer2();

digitalWrite(6, LOW);
digitalWrite(13, HIGH); // turn bridge o/p off
power_off = true;
int get_cv_val; 
}


void current(){
int i;
  int value = 0;
  int numReadings = 5;
for (i = 0; i < numReadings; i++){
    // Read sensor data.
    value = value + analogRead(A0);
    // 1ms pause adds more stability between reads.
    delay(1);
  }

  sensorValue = value/numReadings;
  // Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 1.1V) = 1.08mv per division
  // 0.1 ohm resistor on current sense gives 200mv at 2 Amps or, 100mv per Amp
  // 1.08 mv per div for Internal Ref of 1.1v  : 100/1.08 = 92.6 divisions per 1000mA or 1 div = 10.8mA
  C = 10.8 * sensorValue ;  // mA
  
  if(C >2000){  // 2.0 amps
   Serial.println("Over Current");
   bluetooth.println("Short! " && C);
   power_off = true;
   digitalWrite(6, LOW);  // logic control of DCC signal to low
   digitalWrite(13, HIGH); // to brake, shorts out h-bridge o/p  
  }
}


void loop() { 
     if( power_off){
    Serial.println("Over Current");
     if(bluetooth.available() == true);{
      inString = bluetooth.readStringUntil('\n');
        Serial.println(inString);
     }
      if (inString.substring(0,1) == "C"){
      digitalWrite(13, LOW); 
      power_off = false;
      bt_fail = 0;
      bluetooth.println(Version);}
            // reset after over current, logic control of DCC signal on,brake off
    return;}

   inst_value = analogRead(A0);
    Cinst = (10.8 * inst_value)  ;  // mA
    //Serial.println(Cinst);
    //Serial.print("current inst = ");Serial.println((int)Cinst);
       if(Cinst >2000){  // 2.0 amps 
   Serial.println("Over Current");
   bluetooth.println("Short!" && C);
   digitalWrite(13, HIGH); // output off 
   power_off = true; 
  }
      
  if(bluetooth.available() == true);
    {
        inString = bluetooth.readStringUntil('\n');
        Serial.println(inString);
    }

    if (inString.substring(0,1) == "F"){
    bt_fail = 1;
    stringFF(); }
            // incoming DCC packet DCC_2

   if (inString.substring(0,1) == "T"){
    bt_fail = 1; 
    stringTT(); }
            // incoming DCC packet DCC_2 ops mode 
            
    if (inString.substring(0,1) == "D"){
    bt_fail = 1;  
    string(); }
            // incoming DCC packet
            
       if (inString.substring(0,1) == "S"){
        bt_fail = 0;
        get_Ccv();
        bluetooth.println(Version);
        Serial.println(Version);
       }       // CV sense adjust
               
    if (inString.substring(0,1) == "C"){
      bt_fail = 1;
      digitalWrite(6, HIGH); digitalWrite(13, LOW); power_off = false;
      bluetooth.println(Version);}
            // reset after over current, logic control of DCC signal on,brake off
            
    if (inString.substring(0,1) == "G"){
      digitalWrite(6, HIGH); digitalWrite(13, LOW);
           bt_fail = 1;
           current(); 
           bluetooth.print(C);
           bluetooth.println(" mA");
           }
            // request for load current value 
    if (inString.substring(0,1) == "A"){
       digitalWrite(6, HIGH); digitalWrite(13, LOW); 
       power_off = false;
       bt_fail = 0;
       get_cv_data();
       cv1_prog(); 
       digitalWrite(6, LOW); digitalWrite(13, HIGH);
       }  
     if (inString.substring(0,3) == "get"){
       bt_fail = 0;
       power_off = false;
       cv_val = 0;
       digitalWrite(6, HIGH); digitalWrite(13, LOW); 
       get_cv_num();
       cv_read();
       digitalWrite(6, LOW); digitalWrite(13, HIGH);
       }  
    if (inString.substring(0,3) == "add"){
       bt_fail = 0;
       power_off = false;
       cv_write_val = 0;
       digitalWrite(6, HIGH); digitalWrite(13, LOW); 
       get_cv_new_val();
       repeat_cv_write(); 
       digitalWrite(6, LOW); digitalWrite(13, HIGH);
       }  
    if (inString.substring(0,1) == "V"){
      delay(50);bluetooth.print("CV1 updated"); 
      Serial.println(Address);}
            // cv1 write  
    if (inString.substring(0,1) == "E"){
       digitalWrite(6, LOW);  // logic control of DCC signal to low (off)
       digitalWrite(13, HIGH); // to brake, shorts out h-bridge o/p
       power_off = true; // power off
            }   // e-stop

    //Serial.print("bt_fail = " );Serial.println(bt_fail);    
      if (inString == "" && bt_fail >= 1){
        bt_fail = bt_fail + 1;
        if (bt_fail >= 5){
       power_off = true; // power off
       Serial.println("Lost BT connection");
            } 
      }     // bt fail safe 
            
    inString = "";
   }

void get_Ccv(){
    Serial.println(inString);
    String temp ="";
    int x = inString.indexOf(",") + 1; 
    temp = inString.substring(x,8);
    Serial.println(temp);
    Ccv = temp.toInt();
    Serial.print("Ccv: ");Serial.println(Ccv); 
   }

void get_cv_new_val(){
    //Serial.println(inString);
    String cv_w ="";
    int x = inString.indexOf("new,") + 4; 
    cv_w = inString.substring(x,x+3);
    //Serial.println(cv_w);
    cv_write_val = cv_w.toInt();
    //Serial.print("cv_write_val: ");Serial.println(cv_write_val); 

    String addr ="";
    int z = inString.indexOf("addr,") + 5; 
    addr = inString.substring(z,z+3);
    //Serial.println(addr);
    Address = addr.toInt();
    Address = Address -1;
    calc_address();
    //Serial.print("Address: ");Serial.println(Address); 
   }

void get_cv_num(){
    cv_write_val = 0;
    Serial.println(inString);
    String temp ="";
    int x = inString.indexOf(",") + 1; 
    temp = inString.substring(x,8);
    //Serial.println(temp);
    Address = temp.toInt();
    Address = Address -1;
    calc_address(); 
    //Serial.print("Address: ");Serial.println(Address); 
   }
  
 void get_cv_data(){
    unsigned long z = inString.length();
    int y = 0;
    int count = 0;    
    String inChar;
    String temp ="";
   for (int i = 0; i<=z; i++){
    inChar = inString.substring(i,i+1);
    if (inChar == ",") {count++;}
    if (inChar != "," && inChar != "A") {temp += inChar;}
    if (inChar == ",") {Address = (temp.toInt());y = y +1;temp = "";}
       }
    amend_len3(msg[1]); 
    assemble_3_byte(); 
    Serial.println(inString);
   }

 void stringTT(){    // incoming DCC packet DCC_2 ops mode 
    unsigned long z = inString.length();
    int y = 0;
    for (int i = 0; i<=6; i++){
      a[i] = 0;
    }
int count = 0;    
String inChar;
String temp ="";
   for (int i = 0; i<=z; i++){
    inChar = inString.substring(i,i+1);
    if (inChar == ",") {
      count++;
    }
    if (inChar != "," && inChar != "T") {
      temp += inChar;
    }
    if (inChar == ",") {
      a[y] = (temp.toInt());
      y = y +1;
      temp = "";
    }
   }
    a[3] = a[3] -1;
    if (count == 5 && a[2] <=100){  
     Serial.println("tt");
     //print_data();
    ops_cv_num1 = a[3] % 256;
    ops_cv_num2 = int(a[3] / 256);
    ops_cv_val = a[4]; 
    amend_len5(msg[1]);
    assemble_5_byteTT(); 
    //print_data();
    } 

    if (count == 5 && a[2] >100){  
     Serial.println(inString);
     //print_data();
     a[5] = a[2] % 256;
     a[2] = 192 + int(a[2] / 256);
    ops_cv_num1 = a[3] % 256;
    ops_cv_num2 = int(a[3] / 256);
    ops_cv_val = a[4]; 
    amend_len6(msg[1]);
    assemble_6long_byteTT(); 
    //print_data();
    } 
    
   Serial.println(inString);
   Serial.println(a[1]);
   Serial.println(a[2]);
   Serial.println(a[3]);
   Serial.println(a[4]);
   Serial.println(a[5]);
   Serial.println(a[6]);
    }


 void stringFF(){      // incoming DCC packet DCC_2
    unsigned long z = inString.length();
    int y = 0;
    for (int i = 0; i<=6; i++){
      a[i] = 0;
    }
int count = 0;    
String inChar;
String temp ="";
   for (int i = 0; i<=z; i++){
    inChar = inString.substring(i,i+1);
    if (inChar == ",") {
      count++;
    }
    if (inChar != "," && inChar != "F") {
      temp += inChar;
    }
    if (inChar == ",") {
      a[y] = (temp.toInt());
      y = y +1;
      temp = "";
    }
   }
   
    //Serial.println(count);
    if (count == 3 ){  
      //Serial.println(inString);
      //print_data();
    amend_len3(msg[1]); 
    assemble_3_byteFF(); 
    }
    if (count == 4 && a[2] <=100){  
      //Serial.println(inString);
      //print_data();
    amend_len3(msg[1]); 
    assemble_3_byteFF(); 
    }
    if (count == 5 && a[2] <=100){  
     //Serial.println(inString);
     //print_data();
    amend_len4(msg[1]);
    assemble_4_byteFF(); 
    //print_data();
    } 

if (count == 4 && a[2] >100){ 
      //Serial.println(inString);
      //print_data();
    a[5] = a[2] % 256;
    a[2] = 192 + int(a[2] / 256);
    amend_len4(msg[1]); 
    assemble_4long_byteFF(); 
    }
    if (count == 5 && a[2] >100){  
     //Serial.println(inString);
     //print_data();
     a[5] = a[2] % 256;
     a[2] = 192 + int(a[2] / 256);
    amend_len5(msg[1]);
    assemble_5long_byteFF(); 
    //print_data();
    } 
    }

 
 void string(){
    unsigned long z = inString.length();
    int y = 0;
    for (int i = 0; i<=5; i++){
      a[i] = 0;
    }
int count = 0;    
String inChar;
String temp ="";
   for (int i = 0; i<=z; i++){
    inChar = inString.substring(i,i+1);
    if (inChar == ",") {
      count++;
    }
    if (inChar != "," && inChar != "D") {
      temp += inChar;
    }
    if (inChar == ",") {
      a[y] = (temp.toInt());
      y = y +1;
      temp = "";
    }
   }
    
    if (count == 3){  
      //Serial.println(inString);
      //print_data();
    amend_len3(msg[1]); 
    assemble_3_byte(); 
    }
    if (count == 4){  
     //Serial.println(inString);
     //print_data();
    amend_len4(msg[1]);
    assemble_4_byte(); 
    }  
    }

void assemble_4_byteDD() { 
   noInterrupts(); 
   msg[2].data[0] = 14; // 14 x '1's  // **
   msg[2].data[1] = 0;  // **
   msg[1].data[0] = a[1]; 
   msg[1].data[1] = a[2];
   msg[1].data[2] = a[3];
   msg[1].data[3] = ((a[1] ^ a[2])^ a[3]);
   interrupts();
}
void assemble_3_byteDD() { 
   noInterrupts(); 
   msg[2].data[0] = 14; // 14 x '1's  // **
   msg[2].data[1] = 0; // **
   msg[1].data[0] = a[1];
   msg[1].data[1] = a[2];
   msg[1].data[2] = (a[1] ^ a[2]);
   interrupts();
   
}

void assemble_4_byteFF() { 
   noInterrupts(); 
   msg[2].data[0] = 14; // 14 x '1's  // **
   msg[2].data[1] = 0;  // **
   msg[1].data[0] = a[2]; 
   msg[1].data[1] = a[3];
   msg[1].data[2] = a[4];
   msg[1].data[3] = ((a[2] ^ a[3])^ a[4]);
   interrupts();
}

void assemble_3_byteFF() { 
   noInterrupts(); 
   msg[2].data[0] = 14; // 14 x '1's  // **
   msg[2].data[1] = 0; // **
   msg[1].data[0] = a[2];
   msg[1].data[1] = a[3];
   msg[1].data[2] = (a[2] ^ a[3]);
   interrupts();
   
}

void assemble_5_byteTT() { 
  Serial.print("ops_cv_num1 ");Serial.println(ops_cv_num1);
  Serial.print("ops_cv_num2 ");Serial.println(ops_cv_num2);
  Serial.print("loco num ");Serial.println(a[2]);
  Serial.print("consist addr ");Serial.println(a[4]);
   noInterrupts(); 
   msg[2].data[0] = 14; // 14 x '1's  // **
   msg[2].data[1] = 0;  // **
   msg[1].data[0] = a[2]; // loco short address
   msg[1].data[1] = B11101100 | ops_cv_num2; //  write to VV (msb of CV number) 1110CCVV
   msg[1].data[2] = ops_cv_num1; // VVVVVVVV  LSB of CV number
   msg[1].data[3] = a[4]; // DDDDDDDD  value to write
   msg[1].data[4] = msg[1].data[0] ^ msg[1].data[1]^ msg[1].data[2] ^ msg[1].data[3];
   interrupts();
   
}

void assemble_6long_byteTT() { 
   noInterrupts(); 
   msg[2].data[0] = 14; // 14 x '1's  // **
   msg[2].data[1] = 0;  // **
   msg[1].data[0] = a[2]; // loco long address
   msg[1].data[1] = a[5]; // loco long address
   msg[1].data[2] = B11101100 | ops_cv_num2; //  write to VV (msb of CV number) 1110CCVV
   msg[1].data[3] = ops_cv_num1; // VVVVVVVV  LSB of CV number
   msg[1].data[4] = a[4]; // DDDDDDDD  value to write
   msg[1].data[5] = ((((msg[1].data[0] ^ msg[1].data[1])^ msg[1].data[2]) ^ msg[1].data[3]) ^ msg[1].data[4]);
   interrupts();
   
}

void assemble_5long_byteFF() { 
   noInterrupts(); 
   msg[2].data[0] = 14; // 14 x '1's  // **
   msg[2].data[1] = 0;  // **
   msg[1].data[0] = a[2]; 
   msg[1].data[1] = a[5];
   msg[1].data[2] = a[3];
   msg[1].data[3] = a[4];
   msg[1].data[4] = ((a[2] ^ a[5])^ a[3] ^ a[4]);
   interrupts();
   
}

void assemble_4long_byteFF() { 
   noInterrupts(); 
   msg[2].data[0] = 14; // 14 x '1's  // **
   msg[2].data[1] = 0;  // **
   msg[1].data[0] = a[2]; 
   msg[1].data[1] = a[5];
   msg[1].data[2] = a[3];
   msg[1].data[3] = ((a[2] ^ a[5])^ a[3]);
   interrupts();
   
}

  
 void amend_len4 (struct Message & x) 
{ 
 x.len = 4;
   //Serial.println(x.len);
}

void amend_len5 (struct Message & x) 
{ 
 x.len = 5;
   //Serial.println(x.len);
}

void amend_len6 (struct Message & x) 
{ 
 x.len = 6;
   //Serial.println(x.len);
}

void assemble_4_byte() { 
   noInterrupts(); 
   msg[2].data[0] = 14; // 14 x '1's  // **
   msg[2].data[1] = 0;  // **
   msg[1].data[0] = a[1]; 
   msg[1].data[1] = a[2];
   msg[1].data[2] = a[3];
   msg[1].data[3] = ((a[1] ^ a[2])^ a[3]);
   interrupts();
}

void amend_len3 (struct Message & x) 
{ 
 x.len = 3;
  //Serial.println(x.len);
}

void assemble_3_byte() { 
   noInterrupts(); 
   msg[2].data[0] = 14; // 14 x '1's  // **
   msg[2].data[1] = 0; // **
   msg[1].data[0] = a[1];
   msg[1].data[1] = a[2];
   msg[1].data[2] = (a[1] ^ a[2]);
   interrupts();
   
}


void print_data(){
 Serial.print(msg[1].data[0], DEC);
 Serial.print(",");
 Serial.print(msg[1].data[1], DEC);
 Serial.print(",");
 Serial.print(msg[1].data[2], DEC);
 Serial.print(",");
 Serial.print(msg[1].data[3], DEC);
 Serial.println(",");
  }

//CV read

void cv_current(){
for (int i = 1 ; i<=10 ; i++){
    sensorValue = analogRead(A0);
    C = 10.8 * sensorValue ;  // mA
    if (C >= Cs){
      cv_logic = true;
      i = 11;
    }
    delayMicroseconds(500);
    Serial.print("C = ");Serial.println(C);
    if (C >= 350){
        if(bluetooth.available() == true);{
        bluetooth.print("Short!.. " && C);}
        power_off = true;
        digitalWrite(6, LOW);  // logic control of DCC signal to low (off)
        digitalWrite(13, HIGH); // to brake, shorts out h-bridge o/p       
     }
}
}

void calc_address(){
  MsbAddr = 0;
     LsbAddr = Address;
    if (Address >= 255 && Address <= 511){
      MsbAddr = 1;
      LsbAddr = Address - 256;
    }
    if (Address >= 512 && Address <= 767){
      MsbAddr = 2;
      LsbAddr = Address - 512;
    }
     if (Address >= 768 && Address <= 1023){
      MsbAddr = 3;
      LsbAddr = Address - 768;
    }
    Serial.print("Address: ");Serial.println(Address);
    Serial.print("LsbAddr: ");Serial.println(LsbAddr);
    Serial.print("MsbAddr: ");Serial.println(MsbAddr);
}


   
void cv_read(){
  cv_val = 0;
  delay(200);
  current();

    delay(100);     // **1
for (int i = 1 ; i<=20 ; i++){
  valid_packet();
  delay(5);
   }
   reset();
   
Cs = Ccv;  // Cs = C + Ccv -> Cs = Ccv
  Serial.print("Cs = ");Serial.println(Cs);
  CV_VAL = 99;
  for (int f = 1 ; f<=5 ; f++){ //++
  if (CV_VAL == 99){
    cv_val0 = cv_calc(0);
    delay(10);
  } }
  CV_VAL = 99;
  for (int f = 1 ; f<=5 ; f++){ //++
  if (CV_VAL == 99){
    cv_val1 = cv_calc(1);
    delay(10);
  } }

  CV_VAL = 99;
  for (int f = 1 ; f<=5 ; f++){ //++
  if (CV_VAL == 99){
    cv_val2 = cv_calc(2);
    delay(10);
  } }
  
 CV_VAL = 99;
  for (int f = 1 ; f<=5 ; f++){ //++
  if (CV_VAL == 99){
    cv_val3 = cv_calc(3);
    delay(10);
  } }
  
  CV_VAL = 99;
  for (int f = 1 ; f<=5 ; f++){ //++
  if (CV_VAL == 99){
    cv_val4 = cv_calc(4);
    delay(10);
  } }
  
 CV_VAL = 99;
  for (int f = 1 ; f<=5 ; f++){ //++
  if (CV_VAL == 99){
    cv_val5 = cv_calc(5);
    delay(10);
  } }

   CV_VAL = 99;
  for (int f = 1 ; f<=5 ; f++){ //++
  if (CV_VAL == 99){
    cv_val6 = cv_calc(6);
    delay(10);
  } }

 CV_VAL = 99;
  for (int f = 1 ; f<=5 ; f++){ //++
  if (CV_VAL == 99){
    cv_val7 = cv_calc(7);
    delay(10);
  } }
  
  //Serial.print("cv_val0 ");Serial.println(cv_val0);
  //Serial.print("cv_val1 ");Serial.println(cv_val1);
  //Serial.print("cv_val2 ");Serial.println(cv_val2);
  //Serial.print("cv_val3 ");Serial.println(cv_val3);
  //Serial.print("cv_val4 ");Serial.println(cv_val4);
  //Serial.print("cv_val5 ");Serial.println(cv_val5);
  //Serial.print("cv_val6 ");Serial.println(cv_val6);
  //Serial.print("cv_val7 ");Serial.println(cv_val7);
  int cv_val_total = cv_val0+cv_val1+cv_val2+cv_val3+cv_val4+cv_val5+cv_val6+cv_val7;
  cv_val = 0;
  if (cv_val0 == 1){
    cv_val = cv_val + 1;
  }
  if (cv_val1 == 1){
    cv_val = cv_val + 2;
  }
  if (cv_val2 == 1){
    cv_val = cv_val + 4;
  }
  if (cv_val3 == 1){
    cv_val = cv_val + 8;
  }
  if (cv_val4 == 1){
    cv_val = cv_val + 16;
  }
  if (cv_val5 == 1){
    cv_val = cv_val + 32;
  }
  if (cv_val6 == 1){
    cv_val = cv_val + 64;
  }
  if (cv_val7 == 1){
    cv_val = cv_val + 128;
  }

  reset();
    
if(bluetooth.available() == true);{
  if (cv_val_total > 9){
    bluetooth.println("read = error");
  }
  if (cv_val_total < 9){
       bluetooth.print("read = ");
       bluetooth.println(int(cv_val + 0.5));
  }
       }
Serial.print("cv_val ");Serial.println(int(cv_val + 0.5));
 
 }



void cv1_prog(){
for (int i = 0 ; i<=5 ; i++){
  reset_packet();
  delay(5);
 }
for (int i = 0 ; i<=8 ; i++){
  page_preset_packet();
  delay(5);
 }

for (int i = 0 ; i<=10 ; i++){
  reset_packet();
  delay(5);
 } 
for (int i = 0 ; i<=6 ; i++){
  cv1_write_packet();
  delay(5);
 } 
for (int i = 0 ; i<=11 ; i++){
  reset_packet();
  delay(5);
 } 
}


void reset_packet() {
   amend_len3(msg[1]);
   noInterrupts();
   msg[2].data[0] = 24; // 14 x '1's // **
   msg[2].data[1] = 1; // **
   msg[1].data[0] = B00000000;
   msg[1].data[1] = B00000000;
   msg[1].data[2] = B00000000; 
   interrupts();
}
void page_preset_packet() {
   amend_len3(msg[1]);
   noInterrupts();
   msg[2].data[0] = 24; // 24 x '1's // **
   msg[2].data[1] = 1; // **
   msg[1].data[0] = B01111101;
   msg[1].data[1] = B00000001;
   msg[1].data[2] = B01111100; 
   msg[1].data[3] = B00000000;
   interrupts();
}
void cv1_write_packet() { 
   amend_len3(msg[1]);
   noInterrupts();
   msg[2].data[0] = 24; // 24 x '1's // **
   msg[2].data[1] = 1; // **
   msg[1].data[0] = B01111000; // Address only mode
   msg[1].data[1] = B00000000 | Address;
   msg[1].data[2] = (msg[1].data[0] ^ msg[1].data[1]);
   interrupts();
}

void cv_verify1_packet() { 
   amend_len4(msg[1]);
   noInterrupts();
   msg[2].data[0] = 24; // 24 x '1's // **
   msg[2].data[1] = 1; // **
   msg[1].data[0] = B01111000 | MsbAddr ; // bit manipulation mode  + 2 most sig bits of Address
   msg[1].data[1] = B00000000 | LsbAddr; 
   msg[1].data[2] = B11101000 | num;
   msg[1].data[3] = (msg[1].data[0] ^ msg[1].data[1]) ^ msg[1].data[2];
   interrupts(); 
}


void cv_verify0_packet() { 
   amend_len4(msg[1]);
   noInterrupts();
   msg[2].data[0] = 24; // 24 x '1's // **
   msg[2].data[1] = 1; // **
   msg[1].data[0] = B01111000 | MsbAddr ; // bit manipulation mode + 2 most sig bits of Address 
   msg[1].data[1] = B00000000 | LsbAddr; 
   msg[1].data[2] = B11100000 | num;
   msg[1].data[3] = (msg[1].data[0] ^ msg[1].data[1]) ^ msg[1].data[2];
   interrupts(); 
}

void cv_write_packet() { 
   amend_len4(msg[1]);
   noInterrupts();
   msg[2].data[0] = 24; // 24 x '1's // **
   msg[2].data[1] = 1; // **
   msg[1].data[0] = B01111100 | MsbAddr ; // write mode + 2 most sig bits of Address
   msg[1].data[1] = B00000000 | LsbAddr; 
   msg[1].data[2] = B00000000 | cv_write_val;
   msg[1].data[3] = (msg[1].data[0] ^ msg[1].data[1]) ^ msg[1].data[2];
   interrupts(); 
}

void valid_packet() { 
   amend_len4(msg[1]);
   noInterrupts();
   msg[2].data[0] = 14; // 16 x '1's // **
   msg[2].data[1] = 0;  // **
   msg[1].data[0] = B00000011; 
   msg[1].data[1] = B00111111;
   msg[1].data[2] = B00000001;
   msg[1].data[3] = (msg[1].data[0] ^ msg[1].data[1]) ^ msg[1].data[2];
   interrupts();
}

void idle(){
   amend_len3(msg[1]); 
   noInterrupts(); 
   msg[2].data[0] = 14; // 16 x '1's // **
   msg[2].data[1] = 0; // **
   msg[1].data[0] = B11111111; 
   msg[1].data[1] = B00000000;
   msg[1].data[2] = B11111111;
   interrupts();
}
//CV write
void repeat_cv_write() {
  current();
 Cs = Ccv;  // Cs = C + Ccv -> Cs = Ccv
  Serial.print("C = ");Serial.println(C);
  Serial.print("Cs = ");Serial.println(Cs);
   if(bluetooth.available() == true);{
    bluetooth.print("writing..");
  }
  
  ok = false;
  for (int f = 1 ; f<=10 ; f++){
  if (ok == false){
    cv_write();
    delay(10);
  }
  } 
}

void reset(){
   for (int j = 1 ; j<=12 ; j++){
  reset_packet();
  delay(5);
 }
}

void cv_write(){
   delay(100);
for (int i = 1 ; i<=20 ; i++){
  valid_packet();
  delay(5);
   }
   
reset();

   cv_logic = false;
for (int i = 1 ; i<=10 ; i++){
  cv_write_packet();
  cv_current();
  Serial.print("C= ");Serial.println(C);
  if (cv_logic){
    ok = true;
    cv_logic = false;
    i = 11;
      reset();
      
 if(bluetooth.available() == true);{  
            bluetooth.print("write = ");
            bluetooth.println(cv_write_val);
            Serial.print("cv_write_val ");Serial.println(cv_write_val);
         }
  }
  }
 if(bluetooth.available() == true);{
         if (ok == false){
            bluetooth.println("write = error");  
            }
   }

}

int cv_calc(int test_num) {
//Serial.println("test for 1 ");
 
  num = test_num; 
  
    cv_logic = false; 
    delay(2);
reset();
    
for (int m = 1 ; m<=10 ; m++){
  cv_verify1_packet();
  cv_current();
if (cv_logic){
    cv_logic = false;
    CV_VAL =  1;
    m=11;
    Serial.print("test_num = ");Serial.println(test_num);
    Serial.println(CV_VAL);
    delay(5);
   reset();
}
}

//Serial.println("test for 0 ");
delay(100);
     reset();
     
    cv_logic = false; 
    delay(2);
for (int m = 1 ; m<=10 ; m++){
  cv_verify0_packet();
  cv_current();

if (cv_logic){
    cv_logic = false;
    CV_VAL = 0;
    m=11;
    Serial.print("test_num = ");Serial.println(test_num);
    Serial.println(CV_VAL);;
     reset();
}
}
Serial.println(CV_VAL);
return CV_VAL;  
}

Custom parts and enclosures

Enclosure
Complete circuit within enclosure
20180928 144253 rhycbiqspc

Schematics

Circuit diagram
Bluetooth android dcc lmd18200 on board filter pcb d3c3dett9p
Locomotive DCC 2 - instructions
LocoMotive DCC
App instructions

Comments

Similar projects you might like

Simple Automated Model Railway Layout | Arduino Controlled

Project tutorial by Kushagra Keshari

  • 22,029 views
  • 1 comment
  • 27 respects

Arduino Bluetooth RC Car

Project tutorial by ardumotive

  • 7,871 views
  • 1 comment
  • 18 respects

Rail Road Model IR Remote Control (REMOTINO NANO)

Project showcase by pautax

  • 3,440 views
  • 2 comments
  • 8 respects

RC Car Hack With Android And Arduino

Project showcase by danionescu

  • 20,216 views
  • 5 comments
  • 22 respects

Create a People Counter Controlled by an Android App

Project tutorial by Kutluhan Aktar

  • 14,687 views
  • 5 comments
  • 9 respects

Mobile Water Level Tracker

Project tutorial by Kutluhan Aktar

  • 9,461 views
  • 1 comment
  • 7 respects
Add projectSign up / Login