Model Train DCC Controller

obroni

Member
Merry Christmas everyone. I was wondering if anybody could spare a bit of brain power to see if they can come up with a solution to a little challenge I have set myself.

I am interested if the Picaxe is fast enough to generate a DCC model train bitstream. I've copied the DCC standard to the bottom of this post, but to summarise a logical 1 is made of a +5v pulse for 55-61us and a 0v pulse for the same time. A logical 0 has timings that can be anywhere from double this to up to about 10ms.

I have read the previous threads on the forum about doing this but as far as I could see nobody actually got as far as doing tests connected to a scope. The 0's are no problem and are well within picaxe speeds, my experiments with the 1's are close but come in a few us outside of the spec.

My intention is to calculate the bytes and checksum to be sent and store each bit in the scratchpad. Then whiz through the scratchpad banging out the bits in the right timings. To test the concept I am first trying to generate a simple pulse of a logical 1.

With a 20x2 running at 64mhz I have tried the following (scope connected to b.7):-

Pulsout method
ptr=0
@ptrinc=85
@ptrinc=0
@ptrinc=85
@ptrinc=0
ptr=0
pulsout b.7,@ptrinc
pulsout b.6,@ptrinc
pulsout b.7,@ptrinc
pulsout b.6,@ptrinc

This gave me a +5v pulse for about 55us, but the 0v pulse width was near 80us. This seems to be caused by overheads in the pair of pulsout commands.

Toggle method
ptr=0
@ptrinc=0
@ptrinc=0
ptr=0
toggle b.7
pauseus @ptrinc
toggle b.7
pauseus @ptrinc
toggle b.7

This gives me about 65us on both parts of the pulse, which is just slightly too slow even with the pauseus command set to 0. Again I am guessing the overhead of the pauseus command is causing the problem as with just the toggles I get about 30us pulses.


The fact that I am only about 4us out indicates to me that this must just be in the bounds of being doable on a picaxe and would seem like a good project to be able to do with a picaxe as well.

Is there is a better way of doing this at 64mhz? Or do I need to consider one of the following:-

1. Get a 28x2 and trying clocking it up to 80mhz (or more). This should hopefully reduce the overhead enough that I can use the pauseus command to fine tune the exact pause.
2. String a load shift registers together, slowly clock the bits in (a 0 would just take up 2 bits in the register) and then use pwm to clock them out at the right speed. I might have a problem knowing when to stop shifting though???
3. Give up ;-)


DCC bistream standard
In a "1" bit, the first and last part of a bit shall have the same duration, and that duration shall nominally be
58 microseconds2, giving the bit a total duration of 116 microseconds. Digital Command Station
components shall transmit "1" bits with the first and last parts each having a duration of between 55 and 61
microseconds. A Digital Decoder must accept bits whose first and last parts have a duration of between 52
and 64 microseconds, as a valid bit with the value of "1".


In a "0" bit, the duration of the first and last parts of each transition shall nominally be greater than or
equal to 100 microseconds. To keep the DC component of the total signal at zero as with the "1" bits, the
first and last part of the "0" bit are normally equal to one another. Digital Command Station components
shall transmit "0" bits with each part of the bit having a duration of between 95 and 9900 microseconds
with the total bit duration of the "0" bit not exceeding 12000 microseconds. A Digital Decoder must accept
bits whose first or last parts have a duration of between 90 and 10000 microseconds as a valid bit with the
value of "0". Figure 1 provides an example of bits encoded using this technique.
 

hippy

Technical Support
Staff member
I decided there wasn't enough incentive nor hours in a lifetime for me to be bothered getting my head around the complete DCC specification which was so 'all over the place' that I ranked it as harder to comprehend than the XBee datasheets so you probably know more about it than me :)

For bit-banging I'd suggest head towards over-clocking though it might be easier to buy a serial to DCC interface where someone's already done the hard work ...

http://www.rdgodsey.com/dcc
 

obroni

Member
Thanks for your reply Hippy. I just tried calibfreq 15 and managed to get it 2us nearer the target so I'm pretty sure 80mhz would do the trick. To my surprise I already had a 28x2 and is one of the newer types so all I need is a 20mhz resonator to give it a test.

And thanks for that link, the project is more my interest rather than trying to develop a full featured product. I got my old trainset out the loft over Christmas and thought I would have a go a DCC'ing it on the cheap. But I also thought it would make an interesting forum post if I managed to get it working. When searching the forum I always find several posts from 2005-2007 stating the picaxe is too slow for a certain task, followed by a post in 2010 using a new x2 which handles it beautifully.

Anyway, If I ever manage to get hold of a 20mhz resonator before I lose interest I will post back with my findings.
 

hippy

Technical Support
Staff member
If it's not already a DCC system you could design a protocol which would be much more PICAXE friendly using simple serial.
 

obroni

Member
@Hippy - Yes this has crossed my mind. But off the shelf DCC decoders (the part that goes in the train) are pretty cheap (£10-20) and more importantly are very small using lots of SMD components. Trying to fit a bridge rectifier, Pic, H bridge and all the necessary support components in a tiny little train would probably be something I wouldn't like to try.

@Eclectic - That is a DC type controller. DCC works in the same way but the motor driver and PWM are split from the controller and are placed in the train. The track becomes a bus and the controller just tells each train which direction and speed to go in. This way you can have multiple trains all running on the same section of track.
 

hippy

Technical Support
Staff member
@ obrini : Do you know the format of the next level of protocol upwards ? That may provide some insights into shortcuts which would allow the output to be generated more easily with a PICAXE.

That is, the output can be thought of as a carrier wave, when present a "1" signal, when not a "0", albeit with the modulation synchronised to the carrier transitions. Control of the modulator can be done at a slower speed than bit-banging carrier and modulation but it would depend on what the protocol is.

Another thought is that, when there's nothing to send, the output should be the "1" signal, the approx 10kHz square wave; how are you going to continue to generate that with a PICAXE while allowing yourself to do other things such as preparing the next data stream to send, without putting undesired "0"'s or corrupt signals on the bus ?

If the PICAXE ran with a 1us command time or faster this would be fairly easy but it's not that fast. Even overclocking won't solve all the problems so, IMO, that leaves some complicated shift registers and hardware which can do things with the timing required or an alternative microcontroller.
 

obroni

Member
Hi Hippy,

Yes I know the format. I've been doing a fair bit of reading :(

http://www.nmra.org/standards/DCC/standards_rps/DCCStds.html

There is several types of baseline packets, but they all revolve around the format of:-

PPPPPPPPPPP 0(start bit) AAAAAAAA 0(start bit) IIIIIIII 0(start bit) EEEEEEEEE 1(end bit)

Where P=Preamble(11 bits) A=Address(8bits) I=Instruction(8bits) E=Checksum(8bits)

The actual voltage that goes to the track is AC so there is always voltage available on the track even when the picaxe is putting out 0v to power the trains. When you are not sending data packets there is an idle packet that should be sent.

The DCC spec expects that there is at least 1 packet every 30ms otherwise the decoder drops back into old style DC control (although this can be turned off). I am guessing that 30ms is enough to either do the controller logic (buttons/pots and LCD) or to check hserin for data from another picaxe which is handling those things and set ptr back to 0. If there are no updates then either send the packet again or start sending idle packets.

The other thing that would be be possible is zero stretching. Zero stretching is used to allow DC trains to be controlled by a DCC controller, it stretches either the positive or negative 0 pulse to control speed and direction. I could potentially use one of the mid packet start bits, which are zero to do the calculation for the next packet coming up. But I think the former idea is simpler.

I see your point about a separate carrier wave, something like PWM going into a hardware interrupt and then and gated?, or did you have something else in mind?
 

obroni

Member
I've got a 20mhz resonator on order, but I have just had another thought. I'm currently using pin b.7, would I get a speed increase using b.0?

I've been reading this post:-

http://www.picaxeforum.co.uk/showthread.php?17782-PICAXE-program-Size-Optimisations-and-Speed

And I see that a "toggle 0" is 30us faster than a "toggle 4". I am guessing that a toggle 7 is even slower, maybe 40us??? at 8x the clock speed this might give me the 4-5us I need. I'm not able to test this for a couple of days but I will update if this works.
 

Goeytex

Senior Member
I hooked up a 20x2 last night and connected an output to a 74Hc221 One Shot. With sequential pulsout commandsI was able to get 58us on and 58us off on the output of the one-shot .

If you want a diagram and the test code let me know and I will take the time draw it up and post it here.
 

obroni

Member
Hi Goeytex,

Yes I would be interested. Are you using pulsout for both the high and low? Or are you using the 74HC221 to create an equal high and low pulse from 1 pulsout command?
 

Goeytex

Senior Member
All this does is shorten the high pulsout pulse to 58us. Assuming an initial period of 116us the high and low will then be equal.
The initial off time is determined by Picaxe overhead. Adjust the pulsout value so that the initial period = 116us.

This could also be done by stretching the off time. In that case the one shot would need to be triggered on the falling edge
of the pulsout signal.

Code:
#picaxe 20x2
#no_data
#no_table
#com 1

setfreq m64 
low b.7

do
 pulsout b.7,100
 pulsout b.7,100
 pulsout b.7,100
 pulsout b.7,100
 pulsout b.7,100
 pulsout b.7,100
 pulsout b.7,100
 pulsout b.7,100 
 pause 100
loop
 

Attachments

Goeytex

Senior Member
The time is adjustable via a resistor and capacitor. It can be fine tuned with a 50 or 100K pot. See the attached schematic. You may also want to look at the Datasheet for the 7HC221. A Google search should pop it right up.

This is just part of the puzzle. Now figure out how to integrate this and also make the "O" pulses on the same line.
 

obroni

Member
I see that chip has two outputs, I guess one could always do the same for the 0 pulse and then diode mix the outputs?
 

Goeytex

Senior Member
The actual 74HC221 IC is a 16 pin "dual" monostable multivibrator. So each IC has two independent sections

If you want to know more, I suggest you take the time to read the datasheet.
 

obroni

Member
Well I'm now the proud owner of a 20mhz resonator and I can easily produce the wave form running at 80mhz. I need about a pauseus 15 to get the right timing for a 58us toggle.

I also tested my previous theory of changing pins on the 28x2. Whilst running at 80mhz I saw about a 4-5us reduction so I'm guessing that at 64mhz that would be nearer 6us. So it would seem that generating the waveform at 64mhz might just be possible, but I like the wiggle room given by running at 80mhz.

I will now configure the 28x2 to output "Idle" packets unless hserptr is above 80 in which case it will send out the contents of the scratchpad. I'm hoping that using background serial doesn't effect the timing of the picaxe code???? I can see in manual2 that background serial does "pause" the running program, but I don't know how big an effect it will have. I may instead have to use hserin with a short timeout each time round the loop instead if it proves to be an issue.

I suppose I could also look at the overhead of running the frontend on the same chip. I think the slowest part will be the lcd screen writing or packet generation. But I guess it would be useful to find out exactly how long this would take before going the two picaxe chip root.
 

obroni

Member
I'm happy to report that my controller is working, thus completing my initial goal of proving a Picaxe is capable of generating the required DCC bit stream. I've used a L298 H Bridge and have happily been driving a train around a test track. I've still got a lot of work ahead to implement control of more than 1 train and programming, but I feel that this is the biggest milestone. I can post code if anybody is interested at this stage or I will do a more complete write up once I have progressed further.
 
hi, you suggest that this is possible on the 20x2 at 64mhz. how would i go about achieving this? as i have several of these chips spare!!
 

obroni

Member
Hi Casper,

I can't confirm that it would work. When I tried with a 20x2 at 64mhz it was too slow by about 4-5us but I was using b.7. When I started working with the 28x2 at 80mhz I noticed using pin b.0 was about 5-6us faster than using pin b.7. If this is the case then it may be possible but its untested. If you have a scope connect it to pin b.o and run the commands:-

start:
toggle b.0
pauseus 1
toggle b.0
pauseus 1
toggle b.0
pauseus 1
goto start

If you see a square wave with a period of somewhere between 55 to 61us then it should work. Some decoders may except a slightly larger range (they should according to standards) but who knows....

Are you interested in the code and schematics? Both are a bit of a mess at the moment, but I can tidy them up and post them if your interested. The code is pretty basic at the moment, it reads two pots to control the speed of two trains and also reads a keypad to set the address of the current loco on the track. In the meantime I would suggest you have a read through the DCC standards I linked in this thread, they are very useful for understanding and troubleshooting.
 
would be appreciated :D also you mentioned 2 picaxes. based of the http://www.rdgodsey.com/dcc/ link that was posted earlier in this thread. they have done a similar method using one chip for encoding the data from another so that idea could be one to look into

casper
 

obroni

Member
Yes I still have in the back of my mind that two picaxes may be needed. Its seems at the moment for doing simple speed control 1 picaxe is more than capable. I'm getting about a 5ms gap between packets but this is fine as with two trains they are still getting 40-50 updates a second. With more trains or a more complex menu driven control, a 2nd picaxe will probably be required.

I will see if I can get the code done tonight and post it, else it might have to wait a few days.
 

obroni

Member
Ok, here's a very quick commented code, its not pretty and I have stripped out a lot of the interface code to just show you how it works

Code:
#no_table
#no_data
'b.1 connects to enable pin on h bridge
'b.0 connects to direction pin on h-bridge
symbol onepause=15
symbol zeropause=70
dirsb=%11111111
      
init:
low b.0
setfreq em40 'pause 15us for 1 70us for 0

start:
high b.1 'enable the h bridge

readadc 0,b0 ' Read in ADC value from pot
b10=3 ' set the loco address DCC decoders are factory #3
bit8=bit7 'Store the direction bit in a spare location (this is probably not needed anymore)
if bit7=0 then
  b0=127-b0
endif
bit7=0 ' Clear the bit for now (this is probably not needed anymore)

if b0>0 then ' Limit speed to maximum speed step, then add 1 (speeds 0 and 1 are stop)
  if b0>126 then
    b0=126
  endif
  b0=b0+1
endif

bit7=bit8
b11=%00111111 ' Set the 128 speed step command
b12=b0 ' Set the speed value

b14=b10^b11^b12 ' Calculate parity byte
gosub prepareDCCpacket ' Loop through each byte and store bits into scratchpad
gosub shortpreamble4byte ' Send the 4 bytes (address, action, speed, parity) with a preamble
goto start


prepareDCCpacket:
ptr=0
for b1=0 to 3
if b1=0 then
  b0=b10
elseif b1=1 then
  b0=b11
elseif b1=2 then
  b0=b12
elseif b1=3 then
  b0=b14  
endif
if bit7=1 then
  @ptrinc=onepause
  @ptrinc=onepause
else
  @ptrinc=zeropause
  @ptrinc=zeropause
endif
if bit6=1 then
  @ptrinc=onepause
  @ptrinc=onepause
else
  @ptrinc=zeropause
  @ptrinc=zeropause
endif
if bit5=1 then
  @ptrinc=onepause
  @ptrinc=onepause
else
  @ptrinc=zeropause
  @ptrinc=zeropause
endif
if bit4=1 then
  @ptrinc=onepause
  @ptrinc=onepause
else
  @ptrinc=zeropause
  @ptrinc=zeropause
endif
if bit3=1 then
  @ptrinc=onepause
  @ptrinc=onepause
else
  @ptrinc=zeropause
  @ptrinc=zeropause
endif
if bit2=1 then
  @ptrinc=onepause
  @ptrinc=onepause
else
  @ptrinc=zeropause
  @ptrinc=zeropause
endif
if bit1=1 then
  @ptrinc=onepause
  @ptrinc=onepause
else
  @ptrinc=zeropause
  @ptrinc=zeropause
endif
if bit0=1 then
  @ptrinc=onepause
  @ptrinc=onepause
else
  @ptrinc=zeropause
  @ptrinc=zeropause
endif
next
return

shortpreamble4byte:
ptr=0
'preamble
toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

toggle b.0
pauseus 17
toggle b.0
pauseus 17

'first start bit
toggle b.0
pauseus 75
toggle b.0
pauseus 75
'first byte
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
'second start bit
toggle b.0
pauseus 75
toggle b.0
pauseus 75
'second byte
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
'third start bit
toggle b.0
pauseus 75
toggle b.0
pauseus 75
'third byte
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
'forth start bit
toggle b.0
pauseus 75
toggle b.0
pauseus 75
'forth byte
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
'end bit
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
toggle b.0
return

shortpreamble3byte:
ptr=0
'preamble
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
pauseus 17
'first start bit
toggle b.0
pauseus 75
toggle b.0
pauseus 75
'first byte
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
'second start bit
toggle b.0
pauseus 75
toggle b.0
pauseus 75
'second byte
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
'third start bit
toggle b.0
pauseus 75
toggle b.0
pauseus 75
'third byte
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
toggle b.0
pauseus @ptrinc
'end bit
toggle b.0
pauseus 17
toggle b.0
pauseus 17
toggle b.0
toggle b.0
return
 

obroni

Member
how have u handled the receiving of bits defined in www.nmra.org/standards/DCC/standards_rps/RP-931 2007 Jan.pdf? this is useful to check the decoder has accepted say the change of address
I'm not doing any checking at the moment. I plan to but I won't implement that version of bi-directional coms, my decoders don't support it anyway. I will use the simplier method with the train on the programming track. The train simply pulses the motor for a brief moment. I will monitor the voltage drop across a shunt resistor going into an interupt pin.
 

obroni

Member
Ah excellent, so it does look like it is possible at 64mhz. There will no doubt be a slight overhead of using @ptrinc instead of a static number, but this should still fit into the accepted range.
 
Top