1/8 Scale Electric Locomotive
About 3 years ago I was introduced to the hobby of "Live Steam Railroading." I decided I was going to build one of these, and so began this project. I decided electric was the way to go for me. So it didn't take long for me to decide I wanted to have a microprocessor running the show. I decided for the BS2 as I have has experience with it in the past. The following were the objectives I was going for:
1. Prototypical controls, I wanted a switch to control the direction and knob or lever to control the throttle.
2. The BS2 would control acceleration, to prevent bad things from happening if there are rapid control changes
3. I wanted a visual readout on the control box of the throttle settings and status.
4. When the unit was powered up, make sure all the controls were in a home position, do it doesn't just take off on you
5. Traction control, speed control.
6. Derail/collision detection, automatically set the throttle to 0 and apply the airbrakes
7. Determining the proximity of objects in front of the locomotive and apply brakes and 0 the throttle
I know this may be ambitious with this processor (I'm using the BS2e). I see it as a challenge to try and use the limited resources to accomplish this.
The physical dimensions, are as follows. It is 4 ft long, 16 inches wide and about 22 inches tall and weighs in at 350 pounds; yes you can ride it, well in a rider call right behind hit. I am using 2 12v deep cycle marine batteries hooked to a Dimension Engineering SyRen 50 controller, which takes a rc servo signal from the BS2e. Everything else runs off another 12v deep cycle battery.
Currently, 1,2,3 are done. 4 will be done shortly, I just resolved a reset issue today (Thank's Saffire!) and can put that code back in.
I have the accelerometer added and the program access it, but I haven't added the logic to control the systems based on the readings. That will require taking it out and determining the threshold for what is a derailment, etc.
While it may never be 100% complete, I hope to have it pretty much wrapped up this year. I will keep this updated as things move along. Comments and thoughts are welcome!
Here is the current code and I hope to have the schematic ready in a few days and I will post it here. Keep in mind this code is still in development, so don't laugh too hard!
Thanks
Andy
1. Prototypical controls, I wanted a switch to control the direction and knob or lever to control the throttle.
2. The BS2 would control acceleration, to prevent bad things from happening if there are rapid control changes
3. I wanted a visual readout on the control box of the throttle settings and status.
4. When the unit was powered up, make sure all the controls were in a home position, do it doesn't just take off on you
5. Traction control, speed control.
6. Derail/collision detection, automatically set the throttle to 0 and apply the airbrakes
7. Determining the proximity of objects in front of the locomotive and apply brakes and 0 the throttle
I know this may be ambitious with this processor (I'm using the BS2e). I see it as a challenge to try and use the limited resources to accomplish this.
The physical dimensions, are as follows. It is 4 ft long, 16 inches wide and about 22 inches tall and weighs in at 350 pounds; yes you can ride it, well in a rider call right behind hit. I am using 2 12v deep cycle marine batteries hooked to a Dimension Engineering SyRen 50 controller, which takes a rc servo signal from the BS2e. Everything else runs off another 12v deep cycle battery.
Currently, 1,2,3 are done. 4 will be done shortly, I just resolved a reset issue today (Thank's Saffire!) and can put that code back in.
I have the accelerometer added and the program access it, but I haven't added the logic to control the systems based on the readings. That will require taking it out and determining the threshold for what is a derailment, etc.
While it may never be 100% complete, I hope to have it pretty much wrapped up this year. I will keep this updated as things move along. Comments and thoughts are welcome!
Here is the current code and I hope to have the schematic ready in a few days and I will post it here. Keep in mind this code is still in development, so don't laugh too hard!
Thanks
Andy
' {$STAMP BS2e}' {$PBASIC 2.5}
' ------------------------------------------------------------------------------
' Program Description
' ------------------------------------------------------------------------------
' Version: 0.4
' Date: 3/17/2014
' Changes: This version switchs to using a SyRen50 controller as opposed to
' the scooter controller. Including accell and decel routines as
' the controller now has dynamic braking built in.
VerMajor CON 0
VerMinor CON 4
' ------------------------------------------------------------------------------
' I/O Definitions
' ------------------------------------------------------------------------------
SyRen PIN 0 ' ping to SyRen 50 controller
TX CON 1 ' serial output to LCD
' Accelerometer
CLKPin PIN 4 ' Clock Pin
DATAPin PIN 6 ' Data Pin
CSPin PIN 8 ' Chip Select Pin
DirFwd CON 12 ' set of Direction is set to forward
DirRev CON 13 ' set if Direction is reverse
Key CON 14 ' set if keyswitch is on
Pot CON 15 ' input from throttle
' ------------------------------------------------------------------------------
' Constants
' ------------------------------------------------------------------------------
OnTime CON 10 ' 10 milliseconds, BS2
MaxSpeed CON 500 ' Maximum Speed
' init the accelerometer
GOSUB InitAccel
' for the LCD
T2400 CON 396
T9600 CON 84
T19K2 CON 32
LcdBaud CON T19K2
LcdBkSpc CON $08 ' move cursor left
LcdRt CON $09 ' move cursor right
LcdLF CON $0A ' move cursor down 1 line
LcdCls CON $0C ' clear LCD (use PAUSE 5 after)
LcdCR CON $0D ' move pos 0 of next line
LcdBLon CON $11 ' backlight on
LcdBLoff CON $12 ' backlight off
LcdOff CON $15 ' LCD off
LcdOn1 CON $16 ' LCD on; cursor off, blink off
LcdOn2 CON $17 ' LCD on; cursor off, blink on
LcdOn3 CON $18 ' LCD on; cursor on, blink off
LcdOn4 CON $19 ' LCD on; cursor on, blink on
LcdLine1 CON $80 ' move to line 1, column 0
LcdLine2 CON $94 ' move to line 2, column 0
LcdCC0 CON $F8 ' define custom char 0
LcdCC1 CON $F9 ' define custom char 1
LcdCC2 CON $FA ' define custom char 2
LcdCC3 CON $FB ' define custom char 3
LcdCC4 CON $FC ' define custom char 4
LcdCC5 CON $FD ' define custom char 5
LcdCC6 CON $FE ' define custom char 6
LcdCC7 CON $FF ' define custom char 7
' ------------------------------------------------------------------------------
' Variables
' ------------------------------------------------------------------------------
curLevel VAR Word
setLevel VAR Word
Time VAR Word
char VAR Byte
idx1 VAR Byte
isFwd VAR Bit
isRev VAR Bit
keyOn VAR Bit
counter VAR Nib
error VAR Nib
throtpercent VAR Word
firstTime VAR Nib
' Error codes
' 0 - no error good to go
' 1 - Throttle on at start up
' 2 - Rapid dir change
' 3 - Reverser on at startup
' 4 - Keyswitch on at startup
' ------------------------------------------------------------------------------
' Initialization
' ------------------------------------------------------------------------------
Reset:
HIGH TX ' setup serial output pin
PAUSE 100 ' allow LCD to initialize
SEROUT TX, LcdBaud, [LcdBLon]
' ------------------------------------------------------------------------------
' Program Code
' ------------------------------------------------------------------------------
Boot:
' Accel
Address = MCTL: SendData = %01100101: GOSUB DataOut 'Set the Mode control register
'DATA ready status is NOT OUTPUT TO INT1 PIN
'3-wire SPI mode
'Self Test NOT enabled
'+/-2g sensitivity mode
'Measurement mode
Address = XOFFL: SendData = XCal& $FF: GOSUB DataOut 'Write X-Axis Calibration Value ; LOWBYTE
Address = XOFFH: SendData = XCal >> 8: GOSUB DataOut ' ; HIGHBYTE
Address = YOFFL: SendData = YCal& $FF: GOSUB DataOut 'Write Y-Axis Calibration Value ; LOWBYTE
Address = YOFFH: SendData = YCal >> 8: GOSUB DataOut ' ; HIGHBYTE
Address = ZOFFL: SendData = ZCal& $FF: GOSUB DataOut 'Write Z-Axis Calibration Value ; LOWBYTE
Address = ZOFFH: SendData = ZCal >> 8: GOSUB DataOut ' ; HIGHBYTE
DEBUG CLS 'Clear Display
GOSUB TextField 'Draw TEXT field (stationary TEXT that does not get updated)
' Set custom Chars
SEROUT TX, LcdBaud, [248, ' Define Custom Character 0 Arrow Up
%00100, ' *
%01110, ' * * *
%10101, ' * * *
%00100, ' *
%00100, ' *
%00100, ' *
%00100, ' *
%00100] ' *
SEROUT TX, LcdBaud, [249, ' Define Custom Character 1 Arrow Down
%00100, ' *
%00100, ' *
%00100, ' *
%00100, ' *
%00100, ' *
%10101, ' * * *
%01110, ' * * *
%00100] ' *
SEROUT TX, LcdBaud, [250, ' Define Custom Character 2 Stop
%00100, ' *
%01110, ' * * *
%00100, ' *
%00100, ' *
%00100, ' *
%10101, ' * * *
%01110, ' * * *
%00000] '
'Run start up checks
SEROUT TX, LcdBaud, [LcdCls]
SEROUT TX, LcdBaud, [LcdOn1]
' Reverser Check
' SEROUT TX, LcdBaud, [LcdCls]
' SEROUT TX, LcdBaud, ["Reverser..."]
' RevCheckTop:
' IF IN12 = 1 AND IN13 = 1 THEN
' GOTO RevGood
' ENDIF
' SEROUT TX, LcdBaud, [LcdCls]
' SEROUT TX, LcdBaud, [LcdLine1,"Set Reverser",LcdLine2,"To off!"]
' SEROUT TX, LcdBaud, [210,220]
' PAUSE 250
' GOTO RevCheckTop
' RevGood:
' PAUSE 250
'Throttle Check
' SEROUT TX, LcdBaud, [LcdCls]
' SEROUT TX, LcdBaud, ["Throttle..."]
' ThrCheckTop:
' HIGH Pot
' RCTIME Pot, 1, Time
' IF Time < 2 THEN
' GOTO ThrGood
' ENDIF
' SEROUT TX, LcdBaud, [LcdCls]
' SEROUT TX, LcdBaud, [LcdLine1,"Set Throttle",LcdLine2,"To off!"]
' SEROUT TX, LcdBaud, [210,220]
' PAUSE 250
' GOTO ThrCheckTop
' ThrGood:
' PAUSE 250
'Keyswitch Check
' SEROUT TX, LcdBaud, [LcdCls]
' SEROUT TX, LcdBaud, ["Keyswitch..."]
' KeyCheckTop:
' IF IN14 = 1 THEN
' GOTO KeyGood
' ENDIF
' SEROUT TX, LcdBaud, [LcdCls]
' SEROUT TX, LcdBaud, [LcdLine1,"Set Ketswitch",LcdLine2,"To off!"]
' SEROUT TX, LcdBaud, [210,220]
' PAUSE 250
' GOTO KeyCheckTop
' KeyGood:
' PAUSE 250
SEROUT TX, LcdBaud, [LcdCls]
' PAUSE 250
counter=0
error = 0
curLevel=750
firstTime = 1
Main:
' Get key switch position
IF IN14 = 0 THEN
keyOn = 1
ELSE
keyOn = 0
' Issue the stop command without deceleration, emergency stop
curLevel = 750
PULSOUT SyRen, curLevel
ENDIF
' Get direction
isRev = 0
isFwd = 0
IF IN12 = 0 THEN
isFwd = 1
ENDIF
IF IN13 = 0 THEN
isRev = 1
ENDIF
' If the Keyswitch is off or no direction, then no need to get throttle
IF keyOn = 1 AND isFwd <> isRev THEN
' Get pot Position
HIGH Pot
RCTIME Pot, 1, Time
ELSE
Time = 0
ENDIF
'Cap at 400
'IF Time > 400 THEN
'Time = 400
'ENDIF
'Convert for voltage
setLevel = Time / 2
throtpercent = Time / 5
IF isRev = 1 THEN
setLevel = 750 + setLevel
ELSE
setLevel = 750 - setLevel
ENDIF
' This should help prevent jerking if the processor resets itself while moving
IF firstTime = 1 THEN
curLevel = setLevel
firstTime = 0
PULSOUT SyRen, curLevel
SEROUT TX, LcdBaud, ["Version: ",DEC VerMajor,".",DEC VerMinor]
PAUSE 2000
ENDIF
'DEBUG ? curLevel
' This gradually speeds up and slows down to prevent jerking
IF curLevel > setLevel THEN
curLevel = curLevel - 1
ENDIF
IF curLevel < setLevel THEN
curLevel = curLevel + 1
ENDIF
' Send the signal to the controller
PULSOUT SyRen, curLevel
' Update display every 16 iterations
IF counter = 0 THEN
' If the key switch is off we aren't going anyware
IF keyOn = 0 THEN
SEROUT TX, LcdBaud, [LcdLine2,"Throttle: ",DEC3 throtpercent,"%"]
SEROUT TX, LcdBaud, [163,2]
SEROUT TX, LcdBaud, [LcdLine1,"Controller Off"]
ELSE
' Print current speed
SEROUT TX, LcdBaud, [LcdLine2,"Throttle: ",DEC3 throtpercent,"%"]
' Print Direction
IF isFwd = isRev THEN
SEROUT TX, LcdBaud, [163,2]
ENDIF
IF isFwd = 1 THEN
SEROUT TX, LcdBaud, [163,0]
ENDIF
IF isRev = 1 THEN
SEROUT TX, LcdBaud, [163,1]
ENDIF
' Overspeed
IF Time > MaxSpeed THEN
SEROUT TX, LcdBaud, [LcdLine1,"OVERSPEED! "]
SEROUT TX, LcdBaud, [208,220]
ELSE
SEROUT TX, LcdBaud, [LcdLine1," "]
ENDIF
ENDIF ' End keyOn=0
ENDIF ' End counter=0
' Check Acceleration every 16 iterations
IF counter = 0 THEN
' Accel
Address = XOUT8:GOSUB DataIn 'Read in X-Axis Acceleration Value
XAccel = ReceiveData|($FF00*ReceiveData.BIT7) 'Sign extend the two's complement byte so
'negative numbers can be properly displayed
Address = YOUT8:GOSUB DataIn 'Read in Y-Axis Acceleration Value
YAccel = ReceiveData|($FF00*ReceiveData.BIT7) 'Sign extend the two's complement byte so
'negative numbers can be properly displayed
Address = ZOUT8:GOSUB DataIn 'Read in Z-Axis Acceleration Value
ZAccel = ReceiveData|($FF00*ReceiveData.BIT7) 'Sign extend the two's complement byte so
'negative numbers can be properly displayed
DEBUG CRSRXY,50,3, SDEC XAccel, " ", 'Display the RAW X, Y, and Z Accelerometer values
CRSRXY,50,4, SDEC YAccel, " ",
CRSRXY,50,5, SDEC ZAccel, " "
ENDIF
counter = counter + 1
'PAUSE 10
GOTO Main ' do it again
InitAccel:
'Offset values FOR each axis:
XCal CON 25 'VAR Word
YCal CON 50 'VAR word
ZCal CON 0 'VAR Word
XOUTL CON $00 ' 10 bits output value X LSB XOUT[7] XOUT[6] XOUT[5] XOUT[4] XOUT[3] XOUT[2] XOUT[1] XOUT[0]
XOUTH CON $01 ' 10 bits output value X MSB -- -- -- -- -- -- XOUT[9] XOUT[8]
YOUTL CON $02 ' 10 bits output value Y LSB YOUT[7] YOUT[6] YOUT[5] YOUT[4] YOUT[3] YOUT[2] YOUT[1] YOUT[0]
YOUTH CON $03 ' 10 bits output value Y MSB -- -- -- -- -- -- YOUT[9] YOUT[8]
ZOUTL CON $04 ' 10 bits output value Z LSB ZOUT[7] ZOUT[6] ZOUT[5] ZOUT[4] ZOUT[3] ZOUT[2] ZOUT[1] ZOUT[0]
ZOUTH CON $05 ' 10 bits output value Z MSB -- -- -- -- -- -- ZOUT[9] ZOUT[8]
XOUT8 CON $06 ' 8 bits output value X XOUT[7] XOUT[6] XOUT[5] XOUT[4] XOUT[3] XOUT[2] XOUT[1] XOUT[0]
YOUT8 CON $07 ' 8 bits output value Y YOUT[7] YOUT[6] YOUT[5] YOUT[4] YOUT[3] YOUT[2] YOUT[1] YOUT[0]
ZOUT8 CON $08 ' 8 bits output value Z ZOUT[7] ZOUT[6] ZOUT[5] ZOUT[4] ZOUT[3] ZOUT[2] ZOUT[1] ZOUT[0]
STATUS CON $09 ' Status registers -- -- -- -- -- PERR DOVR DRDY
DETSRC CON $0A ' Detection source registers LDX LDY LDZ PDX PDY PDZ INT1 INT2
TOUT CON $0B ' "Temperature output value" (Optional) TMP[7] TMP[6] TMP[5] TMP[4] TMP[3] TMP[2] TMP[1] TMP[0]
' CON $0C ' (Reserved) -- -- -- -- -- -- -- --
I2CAD CON $0D ' I2C device address I 2CDIS DAD[6] DAD[5] DAD[4] DAD[3] DAD[2] DAD[1] DAD[0]
USRINF CON $0E ' User information (Optional) UI[7] UI[6] UI[5] UI[4] UI[3] UI[2] UI[1] UI[0]
WHOAMI CON $0F ' "Who am I" value (Optional) ID[7] ID[6] ID[5] ID[4] ID[3] ID[2] ID[1] ID[0]
XOFFL CON $10 ' Offset drift X value (LSB) XOFF[7] XOFF[6] XOFF[5] XOFF[4] XOFF[3] XOFF[2] XOFF[1] XOFF[0]
XOFFH CON $11 ' Offset drift X value (MSB) -- -- -- -- -- XOFF[10] XOFF[9] XOFF[8]
YOFFL CON $12 ' Offset drift Y value (LSB) YOFF[7] YOFF[6] YOFF[5] YOFF[4] YOFF[3] YOFF[2] YOFF[1] YOFF[0]
YOFFH CON $13 ' Offset drift Y value (MSB) -- -- -- -- -- YOFF[10] YOFF[9] YOFF[8]
ZOFFL CON $14 ' Offset drift Z value (LSB) ZOFF[7] ZOFF[6] ZOFF[5] ZOFF[4] ZOFF[3] ZOFF[2] ZOFF[1] ZOFF[0]
ZOFFH CON $15 ' Offset drift Z value (MSB) -- -- -- -- -- ZOFF[10] ZOFF[9] ZOFF[8]
MCTL CON $16 ' Mode control LPEN DRPD SPI3W STON GLVL[1] GLVL[0] MOD[1] MOD[0]
INTRST CON $17 ' Interrupt latch reset -- -- -- -- -- -- CLRINT2 CLRINT1
CTL1 CON $18 ' Control 1 -- THOPT ZDA YDA XDA INTRG[1] INTRG[0] INTPIN
CTL2 CON $19 ' Control 2 -- -- -- -- -- DRVO PDPL LDPL
LDTH CON $1A ' Level detection threshold limit value LDTH[7] LDTH[6] LDTH[5] LDTH[4] LDTH[3] LDTH[2] LDTH[1] LDTH[0]
PDTH CON $1B ' Pulse detection threshold limit value PDTH[7] PDTH[6] PDTH[5] PDTH[4] PDTH[3] PDTH[2] PDTH[1] PDTH[0]
PW CON $1C ' Pulse duration value PD[7] PD[6] PD[5] PD[4] PD[3] PD[2] PD[1] PD[0]
LT CON $1D ' Latency time value LT[7] LT[6] LT[5] LT[4] LT[3] LT[2] LT[1] LT[0]
TW CON $1E ' Time window for 2nd pulse value TW[7] TW[6] TW[5] TW[4] TW[3] TW[2] TW[1] TW[0]
' CON $1F ' (Reserved) -- -- -- -- -- -- -- --
XAccel VAR Word ' Variables to store incoming RAW data from the accelerometer
YAccel VAR Word
ZAccel VAR Word
'XMax VAR Word
'YMax VAR Word
'ZMax VAR Word
Address VAR Word ' Variables for reading and writing data to the acclerometer
SendData VAR Byte
ReceiveData VAR Byte
'
' MCTL - Mode control register
' +---------------------------------------+
' ¦ D7 ¦ D6 ¦ D5 ¦ D4 ¦ D3 ¦ D2 ¦ D1 ¦ D0 ¦
' +----+----+----+----+----+----+----+----¦
' ¦ -- ¦DRPD¦SPI3¦STON¦GLVL¦GLVL¦MODE¦MODE¦
' +---------------------------------------+
'
'
' D7 - don't care 0
'
' D6(DRPD) - DATA ready status 0 - OUTPUT TO INT1 PIN
' 1 - is NOT OUTPUT TO INT1 PIN
'
' D5(SPI3W)- Wire Mode 0 - SPI is 4-wire mode
' 1 - SPI is 3-wire mode
'
' D4(STON) - Self Test 0 - NOT enabled
' 1 - enabled
'
' D3(GLVL[1]) - g-SELECT 00 - 8g ; 16 LSB/g in 8-Bit format
' D2(GLVL[0]) - g-SELECT 10 - 4g ; 32 LSB/g in 8-Bit format
' 01 - 2g ; 64 LSB/g in 8-Bit format
'
' ; Note: When reading g in 10-Bit
' ; format, resolution is fixed
' ; at 64 LSB/g
' 10-Bit g register
' +-------------------------------------------------+
' ¦ D9 ¦ D8 ¦ D7 ¦ D6 ¦ D5 ¦ D4 ¦ D3 ¦ D2 ¦ D1 ¦ D0 ¦
' +-------------------------------------------------+
' ¦<------------------------------------->¦ ; These 8 bits are READ in 8g mode
' ¦<------------------------------------->¦ ; These 8 bits are READ in 4g mode
' ¦<------------------------------------->¦ ; These 8 bits are READ in 2g mode
'
' D1(MODE[1]) - Mode SELECT 00 - Standby
' D0(MODE[0]) - Mode SELECT 01 - Measurement
' 10 - Level Detection
' 11 - Pulse Detection
RETURN
DataOut:
LOW CSPin 'Pull chip select pin low to start transmission
SHIFTOUT DATAPin, CLKPin, MSBFIRST, [(Address|%1000000)<<1] 'Select register Address
SHIFTOUT DATAPin, CLKPin, MSBFIRST, [SendData] 'Write value to Address
HIGH CSPin 'End transmission
RETURN
DataIn:
LOW CSPin 'Pull chip select pin low to start transmission
SHIFTOUT DATAPin, CLKPin, MSBFIRST, [Address<<1] 'Select register Address
SHIFTIN DATAPin, CLKPin, MSBPRE, [ReceiveData] 'Read value from Address
HIGH CSPin 'End transmission
RETURN
TextField:
DEBUG CRSRXY,40,0,"MMA7455 3-Axis Accelerometer BS2 DEMO #1", 'Display all stationary TEXT that does not get updated
CRSRXY,48,2,"8-Bit 2g Mode ; 64 LSB/g",
CRSRXY,48,3, "X=",
CRSRXY,48,4, "Y=",
CRSRXY,48,5, "Z="
RETURN

Comments
I could suggest use an accelerometer to limit speed around corners, when possible. Also, same to limit throttle and braking so you don't lose traction. Since you're using a pot, maybe a servo or stepper could apply force feedback letting you know you're exceeding programmed limits?
And then you have to puff steam and smoke... and blow the whistle.