headerImage

Contents

Introduction

One of the most exciting things to happen in the RCX world in the last few years is Mark Riley's DCC firmware. It allows LEGO train fans to control their trains using waveforms described in the NMRA DCC standard by adding special controllers to their motors.

Mark has a version of the firmware that lets you drive multiple trains using the RCX remote control in conjunction with special firmware that is loaded onto the RCX.

This has inspired me to make a version of pbForth that allows you to send DCC packets from any motor port under direct control of the pbForth programming language. In typical Forth fashion, the basic parts of the DCC driver are available in a simple API, and it's up to the user to expand on the basic API to write a program that is useful.

This article describes how to use the API to create your own program for controlling trains and accessories on a DCC equipped LEGO train layout.

To get the most up to date version of pbForth firmware and the example scripts, you can get the pbForth Scripts as a zip archive or the pbForth Scripts as a tar.gz archive. The scripts you'll need are in the saveSystem directory.

Background Information

This article is too short to go into the details of how DCC works. Besides, the DCC spec is online, and it's quite readable! If you don't understand the basics of how DCC works, then review the spec first.

That being said, a DCC packet is anywhere from 2 to 5 bytes of data plus a checksum. The DCC version of pbForth - or just pbForth in this article - can send arbitrary packets of DCC data with automatic calculation of the checksum byte on any one of the three motor ports. You can even specify the minimum time between packets.

The following sections describe how to put together a simple DCC system that you can run from the pbForth console, followed by a more complicated application that runs completely in the background of pbForth. Here's the basic API....


DCC_SETPORT"d-c-c-setport"RCX EXT[Top]

( idx -- )

Sets the motor port specified by an index of 0-2 to be the one to be used for subsequent DCC commands. Using an invalid port number will cause unpredictable results.


DCC_SEND?"d-c-c-send"RCX EXT[Top]

( addr n -- )

Sends an n-byte packet of data from addr out the DCC port. Note that n does not include the checksum byte. The checksum is calculated and sent automatically.


DCC_BUSY?"d-c-c-busy"RCX EXT[Top]

( -- flag )

Returns non-0 if the DCC system is busy sending out a message and 0 if there is no DCC activity. The result can be used directly by a conditional expression.


DCC_DELAY?"d-c-c-delay"RCX EXT[Top]

( -- )

DCC_DELAY is a pbForth VALUE that holds the delay between the end of one packet and the beginning of the next. The DCC standard says that a controller must be able to send out packets every 30 msec, but the time from the end of one packet to the beginning of the next can be as small as 5 msec.

The delay can never be shorter than 5 msec, so smaller values are silently ignored. To use DCC_DELAY, just put the number of msec between packets on the stack and then execute:

TO DCC_DELAY

in your routine.


These 4 words give you the most flexibility in designing control software for your DCC equipped trains and MindStorms accessories.

A Simple DCC Application

During the debugging of the DCC driver, I wanted to be sure that things were operating correctly, so I made a simple little program to test the driver from the pbForth console. The Forth language encourages this kind of experimentation, so we should take advantage of it!

To make our locomotive go around the track, we'll need to be able to send commands to set the speed and direction of the locmotive. A quick check of the DCC specification tells us that the 2 byte message that does this has the following format:

0AAAAAAA 01DCSSSS  

Where 0AAAAAAA is the address of the controller that we are communicating with and 01DCSSSS is the speed and direction information. The default value for the address on most controllers is 0, so we'll set the entire byte to 0 for simplicity.

The SSSS is 4 bits of speed information - ranging from 0 as the lowest speed to 1111 as the highest speed. My experiments with an unloaded train motor indicate that speeds from 1000(8) to 1111(15) are useful. Lower values barely move the motor.

The C bit can be used to provide an additional bit of speed information or it can control the headlight wire on older controllers. We'll leave it set to 0 for or application.

The D bit is used to specify a direction for the motor. The actual dircetion depends on which way the trin is initially facing. Flipping the bit from 0 to 1 or 1 to 0 will change the motor direction.

Armed with this information, we can now assemble a few messages that can be used to control our train motor. Notice that the speed bits are just the low nybble of the second byte, and that 2 bytes make the standard 16-bit cell that is used in pbForth to hold elements on the stack and in variables. In HEX mode we can use the following values to control our train:
HexDecimalDescription
0x004872Reverse at minimum speed
0x004C76Reverse at medium speed
0x004F79Reverse at maximum speed
0x0068104Forward at minimum speed
0x006C108Forward at medium speed
0x006F111Forward at maximum speed

So all we need now is a place to hold the packet we'll be sending, and a pbForth array fits the bill exactly because it returns the address of the first byte in a format useful for DCC_SEND.

\ -----------------------------------------------------------------------------
\ dcc1.txt - first pass at DCC control for RCX
\
\ Requires:
\
\ This just contains the basics to get started with DCC on the RCX.
\ Use this file with the text in "How to Use DCC with pbForth"
\
\ http://www.hempeldesigngroup.com/lego/pbForth
\ -----------------------------------------------------------------------------
\ Revision History
\
\ R. Hempel   2003-12-26 - Original
\ -----------------------------------------------------------------------------

\ Fetch the current BASE and leave it on the stack and switch to HEX

BASE @
HEX

\ Create a place to store DCC messages. The messages can be
\ up to 5 bytes long, so allocate that many chars

CREATE DCC_PACKET 5 CHARS ALLOT

\ Now make some constants that will be used to control
\ engine 0

0000 CONSTANT DCC_STOP

0048 CONSTANT DCC_REV_MIN 
004C CONSTANT DCC_REV_MED
004F CONSTANT DCC_REV_MAX

0068 CONSTANT DCC_FWD_MIN
006C CONSTANT DCC_FWD_MED
006F CONSTANT DCC_FWD_MAX

\ Make a simple word that sends the desired packet
\ out the DCC port exactly once.

: DCC_ISR  DCC_BUSY? IF ELSE DCC_PACKET 2 DCC_SEND THEN ;

\ Remember to set the port for the DCC driver

0 DCC_SETPORT

\ Then set DCC_ISR to run once every timer tick

' DCC_ISR TO 'UserISR

\ Here's a helper word that sets the speed to the desired
\ value

: SET_SPEED ( value -- )
  DCC_PACKET ! ;

\ Restore the old BASE value

BASE !  

\ -----------------------------------------------------------------------------

Not much to it, is there? The big trick is to make a simple function that sends out DCC commands as fast as possible - and that's what DCC_ISR does. It uses DCC_BUSY? to make sure that the previous message has completed.

To actually change the speed and/or direction of your train, just issue commands like this:

DCC_FWD_MIN SET_SPEED
DCC_REV_MAX SET_SPEED
DCC_STOP SET_SPEED

The 'UserISR value is just a pointer to a user defined subroutine that will run once every timer tick, wich is once every msec. For more information on how the user ISR works, refer to HowTo Play Music on the RCX.

Future articles will cover using some advanced techniques to build DCC messages on the fly, as well as interacting with sensors to control your trains. Have fun!