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
|
|
Control 4 engines simultaneously |
|
|
Control 30 switches |
|
|
Short track detection and power cutoff |
|
|
Side track programming engine registers |
|
|
Train head beam control |
|
|
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