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.