Jump to content
 

DCC Signal Accessory Decoder using an Arduino


Recommended Posts

Hi,

 

Inspired by other RMWeb members who have been using Arduino devices to control turntables and traversers, I have been doing some experiments to try and use one for Signal Control.

 

I'm involved in the build of an 'N' Gauge exhibition layout and we want to have working 3 aspect signals, but to go along with this, we want to use various feathers and ground signals for realism when operating. It is also our intention that the signals would be automated, so we will have some form of computer control and signal logic based on route and block occupancy. The layout will have around 9 scenic modules when finished and the signals themselves are fairly spread out along them. So when we started looking at the costs to do this using 'off the shelf' DCC Accessory Decoders, things started to look quite costly.

 

So, to the Arduino. There is allot of information on the web and on this forum about how to get an Arduino connected up to your DCC System and surprisingly for me, I found a simple enough circuit here - http://www.mynabay.com/arduino/14-arduino/dcc-monitor/16-arduino-dcc-monitor-2 to isolate the track voltage from the Arduino and get the DCC Packet info to it safely. Then using a slightly modified version of their 'sketch' I did some testing using the Serial Monitor and my Sprog as a Command Station.

#include <DCC_Decoder.h>

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Defines and structures
//
#define kDCC_INTERRUPT            0

typedef struct
{
    int               address;                // Address to respond to
    byte              output;                 // State of output 1=on, 0=off
    int               outputPin;              // Arduino output pin to drive
    boolean           isDigital;              // true=digital, false=analog. If analog must also set analogValue field
    boolean           isFlasher;              // true=flash output, false=no time, no flash.
    byte              analogValue;            // Value to use with analog type.
    int               durationMilli;          // Milliseconds to leave output on for.  0 means don't auto off
    unsigned long     onMilli;                // Used internally for timing
    unsigned long     offMilli;               // 
} DCCAccessoryAddress;

DCCAccessoryAddress gAddresses[8];

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Decoder Init 
//
void ConfigureDecoder()
{
    gAddresses[0].address = 3;
    gAddresses[0].output = 0;
    gAddresses[0].outputPin = 3;
    gAddresses[0].isDigital = true;
    gAddresses[0].isFlasher = false;
    gAddresses[0].analogValue = 0;
    gAddresses[0].durationMilli = 0;
    
    gAddresses[1].address = 4;
    gAddresses[1].output = 0;
    gAddresses[1].outputPin = 4;
    gAddresses[1].isDigital = true;
    gAddresses[1].isFlasher = false;
    gAddresses[1].analogValue = 0;
    gAddresses[1].durationMilli = 0;
        
    gAddresses[2].address = 5;
    gAddresses[2].output = 0;
    gAddresses[2].outputPin = 5;
    gAddresses[2].isDigital = true;
    gAddresses[2].isFlasher = false;
    gAddresses[2].analogValue = 0;
    gAddresses[2].durationMilli = 0;
    
    gAddresses[3].address = 6;
    gAddresses[3].output = 0;
    gAddresses[3].outputPin = 6;
    gAddresses[3].isDigital = true;
    gAddresses[3].isFlasher = false;
    gAddresses[3].analogValue = 0;
    gAddresses[3].durationMilli = 0;
    
    gAddresses[4].address = 7;
    gAddresses[4].output = 0;
    gAddresses[4].outputPin = 7;
    gAddresses[4].isDigital = true;
    gAddresses[4].isFlasher = false;
    gAddresses[4].analogValue = 0;
    gAddresses[4].durationMilli = 0;
    
    gAddresses[5].address = 8;
    gAddresses[5].output = 0;
    gAddresses[5].outputPin = 8;
    gAddresses[5].isDigital = true;
    gAddresses[5].isFlasher = false;
    gAddresses[5].analogValue = 0;
    gAddresses[5].durationMilli = 0;
    
        // Setup output pins
    for(int i=0; i<(int)(sizeof(gAddresses)/sizeof(gAddresses[0])); i++)
    {
        if( gAddresses[i].outputPin )
        {
            pinMode( gAddresses[i].outputPin, OUTPUT );
        }
        gAddresses[i].onMilli = 0;
        gAddresses[i].offMilli = 0;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Basic accessory packet handler 
//
void BasicAccDecoderPacket_Handler(int address, boolean activate, byte data)
{
        // Convert NMRA packet address format to human address
    address -= 1;
    address *= 4;
    address += 1;
    address += (data & 0x06) >> 1;
    
    boolean enable = (data & 0x01) ? 1 : 0;
    
    for(int i=0; i<(int)(sizeof(gAddresses)/sizeof(gAddresses[0])); i++)
    {
        if( address == gAddresses[i].address )
        {
            Serial.print("Basic addr: ");
            Serial.print(address,DEC);
            Serial.print("   activate: ");
            Serial.println(enable,DEC);
            
            if( enable )
            {
                gAddresses[i].output = 1;
                gAddresses[i].onMilli = millis();
                gAddresses[i].offMilli = 0;
            }else{
                gAddresses[i].output = 0;
                gAddresses[i].onMilli = 0;
                gAddresses[i].offMilli = millis();
            }
        }
    }
    
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Setup
//
void setup() 
{ 
   Serial.begin(9600);
   DCC.SetBasicAccessoryDecoderPacketHandler(BasicAccDecoderPacket_Handler, true);
   ConfigureDecoder();
   DCC.SetupDecoder( 0x00, 0x00, kDCC_INTERRUPT );
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Main loop
//
void loop()
{
    static int addr = 0;
    
        ////////////////////////////////////////////////////////////////
        // Loop DCC library
    DCC.loop();
    
        ////////////////////////////////////////////////////////////////
        // Bump to next address to test
    if( ++addr >= (int)(sizeof(gAddresses)/sizeof(gAddresses[0])) )
    {
        addr = 0;
    }
    
        ////////////////////////////////////////////////////////////////
        // Turn off output?
    if( gAddresses[addr].offMilli && gAddresses[addr].offMilli<millis() )
    {
            // Clear off time
        gAddresses[addr].offMilli = 0;
        
            // Disable output
        if( gAddresses[addr].isDigital )
        {
            digitalWrite( gAddresses[addr].outputPin, LOW);
        }else{
            analogWrite( gAddresses[addr].outputPin, 0);
        }
        
            // If still enabled and a flash type, set on time
        if( gAddresses[addr].output && gAddresses[addr].isFlasher)
        {
            gAddresses[addr].onMilli = millis() + gAddresses[addr].durationMilli;
        }else{
            gAddresses[addr].output = 0;
        }
        
        return;
    }
        
        ////////////////////////////////////////////////////////////////
        // Turn on output?
    if( gAddresses[addr].onMilli && gAddresses[addr].onMilli<=millis() )
    {
            // Clear off time
        gAddresses[addr].onMilli = 0;
        
            // Enable output
        if( gAddresses[addr].isDigital )
        {
            digitalWrite( gAddresses[addr].outputPin, HIGH);
        }else{
            analogWrite( gAddresses[addr].outputPin, gAddresses[addr].analogValue);
        }
        
            // If still enabled and a flash type, set off time
        if( gAddresses[addr].durationMilli )
        {
            gAddresses[addr].offMilli = millis() + gAddresses[addr].durationMilli;
        }
        
        return;
    }
    
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

After some tweaking of the wires on the breadboard, the Arduino sprang to life and started reacting to the accessory command being sent from the Sprog. Now it was time to put some LEDs and resistors (220 Ohm for the Arduinos 5v) onto the board and see what would happen. This is what I ended up with, seen here mocking up two 3 aspect signals displaying 'Proceed'.

 

post-11575-0-26112100-1396037058_thumb.jpg

 

OK, so I had an Arduino Decoder with working LEDS now, but each aspect was being controlled independently and had a separate address. This didn't make for very realistic control from a throttle as the aspects would remain lit or the signal go out while the operator fumbled with the addresses. So what I needed was some control to send a burst of packets, to the signal to 'throw' or 'close' all of the aspect LEDs at a time. I decided on JMRI to do this, as it has an incredible level of support for signal logic, but it's a bit of a steep learning curve, so when I found these tutorials on YouTube it really helped.

 

 

There should be 8 of them, but I could only find 1-7. Even so, Nigel and Kevin really deliver the info well and at a reasonable pace, thanks guys if you read this.

 

So in a very short time, I'd defined my Signal Heads (in my case 3 Aspect), linked them to the Accessory addresses that the Arduino was reacting to and attached them to Signal Masts in JMRI meaning that I could then control them by setting the signals to 'Proceed', 'Caution' and 'Stop'. Here's a short video showing the signals being set to each in turn.

 

 

For the layout, we'll be using Arduino Nanos, which if you shop around can be bought for around £5, add to that some vero board and components for the DCC Bus opto-isolator and it's possible to build a Accessory Decoder, able to operate as either 11 (digital only) or 17 (using the analogue output as digital) individual addresses for £7.

 

Cheers, Mark.

 

  • Like 1
Link to post
Share on other sites

Mark,

smart stuff. 

 

Sorry, video 8 never got done (my fault!).   The tutorials are best followed from the JMRI website (under "clinics", link at bottom of home page) as that contains the sample files and some text to accompany the videos. 

 

One suggestion,  you could investigate Aspect signalling instructions over DCC, rather than sending lots of Accessory thrown-closed instructions.   With an Aspect packet, you send a message to the Accessory Address which includes which aspect (there can be at least 32 within a decoder) is to be displayed.  You might need a few different aspects ( Red, Yellow, Green,  Call-on (red plus call-on light), maybe some flashing versions), but I doubt UK signalling could use up 32 of them !

 

There are a couple of commercial signalling boards which use DCC Aspect Signalling, for UK modellers, the "Signalist" device from Paul Harman is perhaps the best known. 

 

The JMRI signalling capability has come on a lot since the tutorials were written, but the basics still hold.   Kevin wrote a lot of the JMRI code. 

 

 

- Nigel

Edited by Nigelcliffe
Link to post
Share on other sites

Hi Nigel,

 

Sending an Extended Accessory Packet would be of real benefit, as I'm not sure sending bursts of packets is always going to work for us. I can imagine a packet loss and a signal suddenly having two lit aspects or none at all, which wouldn't look good. I've found the NMRA specification for this, but it's going to take another couple of reads before it sinks in :O

 

I'll have to check to see if the DCC_Decoder library I'm using for my Arduino sketch supports the Extended Accessory Packet as a first step. Then have a look at sending the packets from JMRI. The SIgnalist SC1 Manual has a paragraph covering the values it uses so this is something to go on.

 

Cheers, Mark.

Link to post
Share on other sites

Hi,

 

I've been over the code for the DCC_Decoder library and it does support the Extended Accessory Packets. I believe I can also make use of this in the Arduino sketch easily enough. The problem now, is how to send one of these Extended Packets. As I'm using JMRI, can anybody suggest a way to do this. There isn't an awful lot of info about it.

 

Ideally what I'd like to do is create a signal in JMRI, that has a single DCC Address and has a list of these extended values which given the signal type equate to the various display options and dark. Can anybody give me pointers on this?

 

From 'Googling' for info, it also seems that apart from 0 being Dark, all other values for the Extended Packet are defined by the signal controller. So they can be pretty much anything.

 

Cheers, Mark.

Link to post
Share on other sites

Most DCC systems will accept a passed through extended packets generated by JMRI (there's been a very recent conversation on the developer list about a possible issue around NCE, but someone is working on it).
 
So for most systems, the issue is creating a signal within JMRI which uses the extended packet protocol.  This is how I'd do it...

  • Select the Signal Mast table, and "Add" a Signal Mast with the dialogue box.
  • In the Add Signal Mast dialogue, give it a username, select signalling system (eg. BR 2003 for UK), mast type of choice, and the driver is "DCC Signal Mast Decoder".  
  • The "Add Signal Mast" dialogue extends, and you can enter the Accessory Address of the decoder, and the values which are sent for each aspect.  

Once you have a Signal Mast, this can be used on a Panel, and thus operated.  You can optionally disable some aspects (eg. the flashing aspects might be inappropriate for your particular signal on a layout). 

With a few exceptions, its not possible to directly set an aspect from manufacturer's hardware DCC throttle handsets.
 
Yes, the relationship between Aspect values set in JMRI and what happens in your decoder is totally in the hands of the decoder designer.  So, if you want to have 6=red(danger) and 27=yellow(caution), and 25=green(proceed), that's your choice as accessory decoder designer.   You might want a more sensible numbering scheme!  Using a similar scheme to that Paul Harman uses in his Signalist decoders might be a good starting point, his UK scheme goes Red ( 0 ), Yellow ( 1 ), Green ( 2 ), Double Yellow ( 3 ), Flashing Red ( 4 ), Flashing Yellow ( 5 ), Flashing Green ( 6 ), Flashing Double Yellow ( 7 ) and Dark ( 8 ).
 

I've had a Harman Signalist SC1 working with extended accessory packets generated through JMRI to both Sprog and Digitrax hardware;  I've had several firmware versions from Paul Harman to aid in developing the JMRI decoder file for setting up the SC1's. 

I hope that helps.
 
 
- Nigel

Link to post
Share on other sites

  • 6 months later...

Dear all,

 

This is all very interesting and sounds good. I have been given a Romeo to control servos but without any knowledge of programming and working in DCC, I bought a ESU switch pilot for my servos and getting cobalts as well for points control. Those are easy to work with. However I still have this board doing nothing. I would like to use it as a DCC decoder for accessories mainly LED/lightings. 

I was reading through this thread and many other documents on the web and got very confused: how do you program your decoder (arduino)? Note that I have no intention to control my layout via a PC but via my DCC system(s).

Link to post
Share on other sites

  • RMweb Premium

The program in Mark's (Vonzack) original post does all the hard work for you.

 

All you need is to save a copy of the code and "blow" it onto your Arduino. The Arduino software runs on your PC and talks to the microprocessor via a USB cable. Once programmed it can be disconnected and run as a standalone system.

 

The clever bit is done by the DCC_decoder library which takes the raw DCC signal from the track feed and converts it into the individual messages. The code then looks at the addresses being controlled by the DCC system and decides what action it needs to take..

 

The address of each function is defined in the ConfigureDecoder function. The .Address= field links the DCC address to an individual pin (.OutputPin). In the example above changing accessory address 3 will change the output of pin 3 of the Arduino.

 

 

Happy modelling.

 

Steven B.

Link to post
Share on other sites

  • 3 weeks later...
  • RMweb Premium

Which RS part number are you looking at? A quick search on their website shows lots of 6N137's available; There are at least four which are through hole components:

625-7909

691-2325

699-8158

805-1267

 

Farnell have a similar number of parts. Also easily available from eBay.

 

Happy modelling.

 

Steven B.

Link to post
Share on other sites

I'm looking for an alternative to the 6n317 opto-isolator as RS are showing it as discontinued, any suggestion

 

This came up on the MERG forum recently.

 

The 6N137 is multi-source and is not obsolete. Farnell have plenty. It's only some particular Fairchild variants that are discontinued.

 

Andrew

Link to post
Share on other sites

  • 3 weeks later...
  • 2 years later...

I realise that this thread is a bit old, but I am desperately seeking a "how to" build cheaper DCC signal decoders.  There is mention that future building would be by cheap arduinos, which can be programmed and driven by DCC using the Extended Accessory protocol and I was just wondering/hoping that someone had managed to finish the project and would be prepared to publish and "Idiot's Guide" into doing so.  Trying to build four light heads with feathers means buying 1 Signalist per head, which becomes prohibitively expensive!!  I have also found that there are differences across the board between the various decoder brands.  For example, the LDT board cannot use Extended Accessory Protocol and so on!

 

Help please.

 

Joff

Link to post
Share on other sites

I realise that this thread is a bit old, but I am desperately seeking a "how to" build cheaper DCC signal decoders.  There is mention that future building would be by cheap arduinos, which can be programmed and driven by DCC using the Extended Accessory protocol and I was just wondering/hoping that someone had managed to finish the project and would be prepared to publish and "Idiot's Guide" into doing so.  Trying to build four light heads with feathers means buying 1 Signalist per head, which becomes prohibitively expensive!!  I have also found that there are differences across the board between the various decoder brands.  For example, the LDT board cannot use Extended Accessory Protocol and so on!

 

Help please.

 

Joff

Hi Joff

 

Take a look at the DIY Decoder website. The optional enhanced accessory firmware that is available to buy for the DIY accessory or function decoder is the same as is used in the Signalist products and that will allow you to use extended accessory packets for signals.

Link to post
Share on other sites

Hi

I use an Arduino emulating many nmra extended packet decoders controlling a string of ws2812 addressable leds. Each led is a searchlight signal that can display red green yellow blue lunar, flashing or solid. The physical signal construction is described here. http://nscaleaddiction.blogspot.ca/2012/01/cheap-simple-n-scale-signals-using.html.

A single arduino, depending on memory can control up to the maximum 2047 signal heads.

Steve G.

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...