Train Modeling Digital Control (DCC)

 

History

    This is a common project with my father, building a train modeling. One more time it could be simple, but I had decided to challenge myself once again.  The train modeling had changed with years, now you can have more than one engines on the same track and each one could have a different speed, direction "watch out".  It's done by sending data on the track by reversing the voltage on the track with an exact timing protocol.  Like you can imagine it's not the output of your MCU who drive directly the track, you need a driver that will at lease give you 3 amps.  Those controllers already existed but it's costly and they don't do what I was looking for. You can find more info on the protocol and how it's work just by typing "+DCC+train" in Google, that's what I have done.

 

Features

puce Control 4 engines simultaneously
puce Control 30 switches
puce Short track detection and power cutoff
puce Side track programming engine registers
puce Train head beam control
puce Firmware upgradeable

 

Pictures

Click to enlarge

DCC Controler

Mount in a box with his driver

All work done!

 

The train table

 

Me and the man who give me all my knowledge, My father!

 

Sources codes & Schematics

-Schematic DCC MCU Control in PDF format

-Schematic DCC Driver in PDF format

-DCC protocol standard 1/2 in PDF format

-DCC protocol standard 2/2 in PDF format

 

//**************************************
// DCC Controller
// Version 1.0 Dec 2003
// Sylvain Bissonnette
//
//**************************************
//
//    Return Stack must be 32
//          Xtal 12Mhz
//
//**************************************
//            I N C L U D E
//**************************************
#include <io8535v.h>
#include <macros.h>
#include <stdlib.h>
#include <string.h>
#include <eeprom.h>
#include "lcd.h"

//**************************************
//            D E F I N E
//**************************************
// User dependent BEGIN
// Keypad
#define ROW1        0x01
#define ROW2      0x02
#define ROW3      0x04
#define ROW4      0x08
#define COL1      0x10
#define COL2      0x20
#define COL3      0x40
#define COL4      0x80

#define KEYPIN    PINB
#define KEYDDR    DDRB
#define KEYPORT      PORTB


// User dependent END

#define TRUE       0x01
#define  FALSE     0x00

#define PROG       1
#define NORMAL     0

#define DCCBIT     0x08
#define DCCPROGBIT    0x04
#define DCCPOWER   0x10

#define TRAINBASE  50
#define SWITCHBASE    52
#define MAXDCC     54

#define VER        10


//**************************************
//           C O N S T A N T
//**************************************
const unsigned char Table[16] = {0x31,0x32,0x33,0x00,
                            0x34,0x35,0x36,0x00,
                         0x37,0x38,0x39,0x00,
                         0x2a,0x30,0x23,0x00};

//**************************************
//          P R O T O T Y P E
//**************************************
void Short(void);
void DoDCC(void);
void DCCReset(unsigned char Prog);
void DCCIdle(unsigned char Prog);
void DCCSend(unsigned char Prog,unsigned char *ptr);
void DCCSend1(unsigned char Prog);
void DCCSend0(unsigned char Prog);
void Delay_95us(void);
void Delay_55us(void);

// KEY
void ScanKey(void);
unsigned char LookKey(void);
void ScanSwitch(void);

// Code
void SlidePot_Switch(void);
void Delay_1ms(int Qte);
void DoKey(void);
void Debug(void);
void GetAddress(unsigned char Item);
void ProgMode(void);
void Redraw(void);
void timer0_ovf_isr(void);
void Init(void);

//**************************************
//   G L O B A L   V A R I A B L E
//**************************************
int Train[4];
char TrainLight[4];

unsigned char Stop = FALSE;

unsigned long TrackSwitch;

signed char Key = 48;
unsigned char KeyFirst = 48;
unsigned char KeySecond = 48;

int TrainBase;
int SwitchBase;
int MaxDCC;

signed char Switch;
char TextBuffer[22];

unsigned char Pot[4];
unsigned char Buzz = FALSE;

int ShortCNT = 0;

//**************************************
//            M A I N
//**************************************
void main(void)
{
int i;
unsigned char Test[4];

Init();
SEI();
InitLCD();
ClrLCD();
GotoXY(1,1);
WriteStrLCDConst("--DCC  Controller-- Sylvain Bissonnette\0");
Delay_1ms(1000);//was 1000
ClrLCD();
GotoXY(1,1);
WriteStrLCDConst(">   <>   <>   <>   <\0");

Sound(1);
Delay_1ms(100);
Sound(0);

for (i=0;i<4;i++)
   {
   Train[i] = 0;
   TrainLight[i] = FALSE;
   }

EEPROMReadBytes(TRAINBASE,&TrainBase,sizeof(int));
EEPROMReadBytes(SWITCHBASE,&SwitchBase,sizeof(int));
EEPROMReadBytes(MAXDCC,&MaxDCC,sizeof(int));

if (TrainBase == 0xffff) TrainBase = 1;
if (SwitchBase == 0xffff) SwitchBase = 128;
if (MaxDCC == 0xffff) MaxDCC = 36;

for (i=0;i<25;i++) DCCReset(NORMAL); // 20 is minimum
for (i=0;i<15;i++) DCCIdle(NORMAL);  // 15 is minimum

while(1)
      {
      _StackCheck();
      WDR();
      DoKey();
      SlidePot_Switch();
      DoDCC();
      if (!(PINA & 0x40))
         {
         ShortCNT++;
         if (ShortCNT > 30) Short();
         }
      else ShortCNT = 0;
      }
}

/**********************************************************

Name:       void _StackOverflowed(char c)

Description:   This function is automaticaly called if
            the stack crash.
           

Input:         none

Output:        PB7

Misc:      
**********************************************************/
void _StackOverflowed(char c)
   {
   ClrLCD();
   GotoXY(1,1);
   WriteStrLCDConst("   STACK CRASH!&?\0");
   while(1) WDR();
   }

/**********************************************************

Name:       void Short(void)

Description:              

Input:         none

Output:       

Misc:      
**********************************************************/
void Short(void)
{
ShortCNT = 0;
PORTA &= ~DCCPOWER;
ClrLCD();
GotoXY(1,1);
Key = -1;
WriteStrLCDConst("--> Track SHORT! <--  Press # to retry  \0");
while (1)
     {
     WDR();
     if (Key != -1) break;
     }
Key = -1;
PORTA |= DCCPOWER;
Redraw();
}

/**********************************************************

Name:       void DoDCC(void)

Description:              

Input:         none

Output:       

Misc:      
**********************************************************/
void DoDCC(void)
{
unsigned char byte[2];
unsigned char Sw;
unsigned char To;
unsigned char Pos;
unsigned char Stat;
static unsigned char i=0;

char text[20];

if (i < 4)
   {
   byte[0] = i+TrainBase;

   // Speed Direction

   if (Train[i] > 0)
        {
        byte[1] = 0x40 + (Train[i] + 1); // 0110SSSS
        }
   if (Train[i] == 0)
        {
        byte[1] = 0x40;
        }
   if (Train[i] < 0)
        {
        byte[1] = 0x60 + (abs(Train[i]) + 1); // 0100SSSS
        }

   if (Stop == TRUE)
        {
        byte[1] = 0x51;
        }
   DCCSend(NORMAL,&byte[0]);
   DCCSend(NORMAL,&byte[0]);  // Don't know why but it's take 2 time this

   // Head light
   if (TrainLight[i] == TRUE) byte[1] = 0x90;
   else byte[1] = 0x80;
   DCCSend(NORMAL,&byte[0]);
   DCCSend(NORMAL,&byte[0]);  // Don't know why but it's take 2 time this
   }
else
   {
   Sw = i - 4;
   if (((TrackSwitch >> (Sw)) & 0x0001) == 0x0001) Stat = TRUE;
   else Stat = FALSE;
   To = Sw / 8;
   Pos = Sw - (To * 8);
   To += SwitchBase;
   byte[0] = ((To >> 4) & 0x0f) + 0x80;
   byte[1] = (To <<  4) + (Stat << 3) + Pos;
   DCCSend(NORMAL,&byte[0]);
   }

i++;
if (i > MaxDCC) i = 0;
}

/**********************************************************

Name:       void DCCReset(void)

Description:              

Input:         none

Output:       

Misc:      
**********************************************************/
void DCCReset(unsigned char Prog)
{
unsigned char byte[2];

byte[0] = 0x00;
byte[1] = 0x00;
DCCSend(Prog,&byte[0]);
}

/**********************************************************

Name:       void DCCIdle(void)

Description:              

Input:         none

Output:       

Misc:      
**********************************************************/
void DCCIdle(unsigned char Prog)
{
unsigned char byte[2];

byte[0] = 0xff;
byte[1] = 0x00;
DCCSend(Prog,&byte[0]);
}

/**********************************************************

Name:       void DCCSend(unsigned char *ptr)

Description:              

Input:         none

Output:       

Misc:      
**********************************************************/
void DCCSend(unsigned char Prog,unsigned char *ptr)
{
int i;
unsigned char byte1;
unsigned char byte2;
unsigned char byte3;
unsigned char byte4;

byte1 = *ptr++;
byte2 = *ptr++;
byte3 = *ptr++;

if (Prog == NORMAL) byte4 = byte1 ^ byte2;
else byte4 = byte1 ^ byte2 ^ byte3;

if (Prog == NORMAL) for (i=0;i<15;i++) DCCSend1(Prog); // preamble
else for (i=0;i<25;i++) DCCSend1(Prog); // long-preamble

DCCSend0(Prog); // Start Bit

for (i=0;i<8;i++)
   {
   if ((byte1 & 0x80) == 0x80) DCCSend1(Prog);
   else DCCSend0(Prog);
   byte1 <<= 1;
   }

DCCSend0(Prog); // Start Bit

for (i=0;i<8;i++)
   {
   if ((byte2 & 0x80) == 0x80) DCCSend1(Prog);
   else DCCSend0(Prog);
   byte2 <<= 1;
   }

if (Prog == PROG)
   {
   DCCSend0(Prog); // Start Bit

   for (i=0;i<8;i++)
      {
      if ((byte3 & 0x80) == 0x80) DCCSend1(Prog);
      else DCCSend0(Prog);
      byte3 <<= 1;
      }
   }

DCCSend0(Prog); // Start Bit

for (i=0;i<8;i++)
   {
   if ((byte4 & 0x80) == 0x80) DCCSend1(Prog);
   else DCCSend0(Prog);
   byte4 <<= 1;
   }

DCCSend1(Prog); // Stop Bit  
}


/**********************************************************

Name:       void DCCSend1(void)

Description:              

Input:         none

Output:       

Misc:      
**********************************************************/
void DCCSend1(unsigned char Prog)
{
if (Prog == NORMAL) PORTD ^= DCCBIT;
PORTD ^= DCCPROGBIT;
Delay_55us();
if (Prog == NORMAL) PORTD ^= DCCBIT;
PORTD ^= DCCPROGBIT;
Delay_55us();
}

/**********************************************************

Name:       void DCCSend0(void)

Description:              

Input:         none

Output:       

Misc:      
**********************************************************/
void DCCSend0(unsigned char Prog)
{
if (Prog == NORMAL) PORTD ^= DCCBIT;
PORTD ^= DCCPROGBIT;
Delay_95us();
if (Prog == NORMAL) PORTD ^= DCCBIT;
PORTD ^= DCCPROGBIT;
Delay_95us();
}

/**********************************************************

Name:       void Delay_95us(void)

Description:              

Input:         none

Output:       

Misc:       12Mhz
**********************************************************/
void Delay_95us(void)
{
int i;

for (i=0;i<140;i++) WDR();
}

/**********************************************************

Name:       void Delay_55us(void)

Description:              

Input:         none

Output:       

Misc:       12Mhz
**********************************************************/
void Delay_55us(void)
{
int i;

for (i=0;i<80;i++) WDR();
}

//*********************************************************
//*********************************************************
//
//  K E Y   F U N C T I O N
//
//*********************************************************
//*********************************************************
/**********************************************************

Name:       void ScanKey(void)

Description:  

Input:         none

Output:        none

Misc:      

**********************************************************/
void ScanKey(void)
{
static unsigned char LastKey;
unsigned char KeyRead;

if (Buzz == FALSE) Sound(FALSE);
else Sound(TRUE);

KeyRead = LookKey();

if ((KeyRead != LastKey) && (KeyRead != 0))
   {
   if (Buzz == FALSE) Sound(TRUE);
   else Sound(FALSE);
   Key = KeyRead;
   }
LastKey = KeyRead;
}

/**********************************************************

Name:       void LookKey(void)

Description:   LookKeyPad

Input:         none

Output:        none

Misc:      

**********************************************************/
unsigned char LookKey()
{
unsigned char i,j;

for (i=0;i<4;i++)
   {
   if (i == 0) KEYPORT = (unsigned char)~ROW1;
   if (i == 1) KEYPORT = (unsigned char)~ROW2;
   if (i == 2) KEYPORT = (unsigned char)~ROW3;
   if (i == 3) KEYPORT = (unsigned char)~ROW4;

   for (j=0;j<255;j++); // Delay

   if ((KEYPIN & (COL1+COL2+COL3+COL4)) != (COL1+COL2+COL3+COL4))
      {
      i = i * 4;
      if ((KEYPIN & COL1) != COL1) i = i + 1;
      if ((KEYPIN & COL2) != COL2) i = i + 2;
      if ((KEYPIN & COL3) != COL3) i = i + 3;
      if ((KEYPIN & COL4) != COL4) i = i + 4;
      return Table[i-1];
      }
   }
return 0;
}

/**********************************************************

Name:       void ScanSwitch(void)

Description:  

Input:         none

Output:        none

Misc:      

**********************************************************/
void ScanSwitch(void)
{
static unsigned char LastSwitch;
unsigned char SwitchRead;

if ((PIND & 0xf0) == 0xf0) SwitchRead = 0;
if ((PIND & 0x80) == 0x00) SwitchRead = 1;
if ((PIND & 0x40) == 0x00) SwitchRead = 2;
if ((PIND & 0x20) == 0x00) SwitchRead = 3;
if ((PIND & 0x10) == 0x00) SwitchRead = 4;

if ((SwitchRead != LastSwitch) && (SwitchRead != 0))
   {
   if (Buzz == FALSE) Sound(TRUE);
   else Sound(FALSE);
   Switch = SwitchRead;
   }
LastSwitch = SwitchRead;
}


//*********************************************************
//*********************************************************
//
//  A / D   F U N C T I O N
//
//*********************************************************
//*********************************************************
/**********************************************************

Name:       void AD_Int(void)

Description:  

Input:         void

Output:        void

Misc:      

**********************************************************/
#pragma interrupt_handler AD_Int:15
void AD_Int(void)
{
static unsigned char ADPtr = 0;

if (ADPtr == 0) Pot[0] = ADC >> 2;
if (ADPtr == 1) Pot[1] = ADC >> 2;
if (ADPtr == 2) Pot[2] = ADC >> 2;
if (ADPtr == 3) Pot[3] = ADC >> 2;

ADPtr++;
if (ADPtr > 3) ADPtr = 0;
ADMUX = ADPtr;
ADCSR |= 0x40;
}

/**********************************************************

Name:       void Delay_1ms(int Qte)

Description:   Delay of x * 1ms

Input:         void

Output:        void

Misc:      

**********************************************************/
void Delay_1ms(int Qte)
{
int i,j;

for (j=0;j<Qte;j++)
   {
   for (i=0;i<1000;i++) WDR();
   }
}

/**********************************************************

Name:       void SlidePot_Switch(void)

Description:  

Input:         none

Output:        none

Misc:      

**********************************************************/
void SlidePot_Switch(void)
{
static int LastTrain[4] = {0xff,0xff,0xff,0xff};
int i;
int Tmp;
char TextBuffer[5];

// Check Switch for HeadLight

if ((Switch == 1) && (TrainLight[0] == FALSE)) TrainLight[0] = TRUE;
else if ((Switch == 1) && (TrainLight[0] == TRUE))  TrainLight[0] = FALSE;

if ((Switch == 2) && (TrainLight[1] == FALSE)) TrainLight[1] = TRUE;
else if ((Switch == 2) && (TrainLight[1] == TRUE))  TrainLight[1] = FALSE;

if ((Switch == 3) && (TrainLight[2] == FALSE)) TrainLight[2] = TRUE;
else if ((Switch == 3) && (TrainLight[2] == TRUE))  TrainLight[2] = FALSE;

if ((Switch == 4) && (TrainLight[3] == FALSE)) TrainLight[3] = TRUE;
else if ((Switch == 4) && (TrainLight[3] == TRUE))  TrainLight[3] = FALSE;

Switch = 0;

// Make Speed for each Loco

Train[0] = 14-((255-Pot[3]) * 28 / 255);
Train[1] = 14-((255-Pot[2]) * 28 / 255);
Train[2] = 14-((255-Pot[1]) * 28 / 255);
Train[3] = 14-((255-Pot[0]) * 28 / 255);

for (i=0;i<4;i++)
   {
   if (Train[i] != LastTrain[i])
         {
      if (i == 0) GotoXY(2,1);
      if (i == 1) GotoXY(7,1);
      if (i == 2) GotoXY(12,1);
      if (i == 3) GotoXY(17,1);

      Tmp = Train[i];
      if (Tmp >= 0) WriteLCD(DATA,' ');
      else WriteLCD(DATA,'-');
      Tmp = abs(Tmp);
      itoa(&TextBuffer[0],Tmp,10);
      Tmp = strlen(&TextBuffer[0]);
      WriteStrLCD(TextBuffer);
      if (Tmp < 2) WriteLCD(DATA,' ');

         LastTrain[i] = Train[i];
         }
   }
}

/**********************************************************

Name:       void DoKey(void)

Description:  

Input:         none

Output:        none

Misc:      

**********************************************************/
void DoKey(void)
{
static unsigned char Value;

if ((Key != -1) && (Key != '*') && (Key != '#'))
   {
   KeySecond = KeyFirst;
   KeyFirst = Key;
   GotoXY(1,2);
   WriteLCD(DATA,KeySecond);
   WriteLCD(DATA,KeyFirst);
   Value = ((KeySecond - 48) * 10) + (KeyFirst - 48);

   if ((Value >= 0) && (Value < 32))
        {
     GotoXY(3,2);
     if (((TrackSwitch >> Value) & 0x0001) == 0x0001) WriteStrLCDConst("->Switch Red #    \0");
     else WriteStrLCDConst("->Switch Green *  \0");
     }

   else if ((Value >= 32) && (Value < 64))
        {
     GotoXY(3,2);
     WriteStrLCDConst("->*Save #Load Swt \0");
     }

   else if (Value == 90)
        {
        GotoXY(3,2);
        WriteStrLCDConst("->Prog Mode #     \0");
        }

   else if (Value == 91)
        {
        GotoXY(3,2);
        WriteStrLCDConst("->Train Base Add #\0");
        }

   else if (Value == 92)
        {
        GotoXY(3,2);
        WriteStrLCDConst("->Acc Base Add #  \0");
        }

   else if (Value == 93)
        {
        GotoXY(3,2);
        WriteStrLCDConst("->Max DCC Devices#\0");
        }

   else if (Value == 96)
        {
        GotoXY(3,2);
        WriteStrLCDConst("->Debug Menu #    \0");
        }

   else if (Value == 97)
        {
        GotoXY(3,2);
        WriteStrLCDConst("->Software Ver:1.1\0");
        }

   else if (Value == 98)
        {
     GotoXY(3,2);
        WriteStrLCDConst("->Restart #       \0");
     }

   else if (Value == 99)
        {
     GotoXY(3,2);
        WriteStrLCDConst("->Emergency STOP #\0");
     }
   else
      {
     GotoXY(3,2);
        WriteStrLCDConst("->                \0");
     }
   Key = -1;
   }

if ((Key == '#') && (Value >= 0) && (Value < 32))
   {
   GotoXY(3,2);
   TrackSwitch &= ~(0x0001 << Value);
   WriteStrLCDConst("->Switch Green *  \0");
   Key = -1;
   }

if ((Key == '*') && (Value >= 0) && (Value < 32))
   {
   GotoXY(3,2);
   TrackSwitch |= (0x0001 << Value);
   WriteStrLCDConst("->Switch Red #    \0");
   Key = -1;
   }

if ((Key == '#') && (Value >= 32) && (Value < 64))
   {
   GotoXY(3,2);
   EEPROMReadBytes(Value - 31,&TrackSwitch,sizeof(long));
   WriteStrLCDConst("->Switch Load!    \0");
   Key = -1;
   }

if ((Key == '*') && (Value >= 32) && (Value < 64))
   {
   GotoXY(3,2);
   EEPROMWriteBytes(Value - 31,&TrackSwitch,sizeof(long));
   WriteStrLCDConst("->Switch Save!    \0");
   Key = -1;
   }

if ((Value == 91) && (Key == '#'))
   {
   GetAddress(TRAINBASE);
   GotoXY(1,2);
   WriteStrLCDConst("91->Train Base Add #\0");
   Key = -1;
   }

if ((Value == 92) && (Key == '#'))
   {
   GetAddress(SWITCHBASE);
   GotoXY(1,2);
   WriteStrLCDConst("92->Acc Base Add #  \0");
   Key = -1;
   }

if ((Value == 93) && (Key == '#'))
   {
   GetAddress(MAXDCC);
   GotoXY(1,2);
   WriteStrLCDConst("93->Max DCC Devices#\0");
   Key = -1;
   }

if ((Value == 96) && (Key == '#'))
   {
   Debug();
   GotoXY(1,2);
   WriteStrLCDConst("96->Debug Menu #    \0");
   Key = -1;
   }

if ((Value == 98) && (Key == '#'))
   {
   Stop = FALSE;
   Key = -1;
   }

if ((Value == 99) && (Key == '#'))
   {
   Stop = TRUE;
   Key = -1;
   }

if ((Value == 90) && (Key == '#'))
   {
   ProgMode();
   GotoXY(1,2);
   WriteStrLCDConst("90->Prog Mode #     \0");
   Key = -1;
   }
}

/**********************************************************

Name:       void Debug(void)

Description:  

Input:         none

Output:        none

Misc:      

**********************************************************/
void Debug(void)
{
unsigned char byte[2];
Key = -1;
ClrLCD();
GotoXY(1,1);
WriteStrLCDConst("Debug Menu # to exit\0");
GotoXY(1,2);
WriteStrLCDConst("1-Res 2-Idle 3-55aa \0");
while (1)
     {
     WDR();
     if (Key == '#') break;
     if (Key == '1')
       {
       DCCReset(NORMAL);
       Key = -1;
       }
     if (Key == '2')
       {
       DCCIdle(NORMAL);
       Key = -1;
       }
     if (Key == '3')
       {
       byte[0] = 0x55;
       byte[1] = 0xaa;
       DCCSend(NORMAL,&byte[0]);
       Key = -1;
       }

     }
Key = -1;
Redraw();
}

/**********************************************************

Name:       void GetAddress(unsigned char Item)

Description:  

Input:         none

Output:        none

Misc:      

**********************************************************/
void GetAddress(unsigned char Item)
{
unsigned char Val1 = 48, Val2 = 48, Val3 = 48;
int Val = 0;
char text[10];

Key = -1;
ClrLCD();
GotoXY(1,1);
if (Item == TRAINBASE)
   {
   WriteStrLCDConst("Enter Train Basse   \0");
   GotoXY(1,2);
   WriteStrLCDConst("Address:000   Save* \0");
   }

if (Item == SWITCHBASE)
   {
   WriteStrLCDConst("Enter Accessory base\0");
   GotoXY(1,2);
   WriteStrLCDConst("Address:000   Save* \0");
   }

if (Item == MAXDCC)
   {
   WriteStrLCDConst("Enter Max DCC       \0");
   GotoXY(1,2);
   WriteStrLCDConst("Devices:000   Save* \0");
   }


if (Item == TRAINBASE) itoa(&text[0],TrainBase,10);
if (Item == SWITCHBASE) itoa