Controlling a servo (or 4) from JMRI using LCC messages: We are going to create a fake LCC node to act upon the messages sent by LCC turnouts in JMRI over the CAN bus. I claim fake, since the node does not do even close to what is required by a real LCC node. No configuration, no status, no firmware updates and no CDI at all, simply receiving an 8 byte message and if one of the 8 hard coded, move one of 4 servos CW or CCW. Both Arduinos are connected to MCP2515 SPI CAN transceivers with only the CANHI, CANLO and Ground wires connected between the two sets. *Fritzing drawing of the board hookup...ARduino on the left is also connected to the PC, giving JMRI the LCC serial connection. /*
Sending CAN messages from Serial port as received from JMRI.
JMRI will send a ':'; some bytes; an 'N'; up to 8 data bytes we want
to send and then a ';'. We are going to ignore everything except the data
between 'N' and ';', and then send the bytes out on the CAN bus.
If we did receive more than 8 bytes when ';' comes, we don't send and wait
for the next data.
The data shows up over the serial port as two hexadecimal characters, (nibbles)
0x00 thru 0xFF, so we need to take care of making the two characters into
one 8 bit byte. Of course, a message like:
03.00.01.02.03.02.01.01 does not have A-F, but we are going to avoid running
into the problem when the message ends in something like .02.5F.AE.
Examples :XCACB4000N0300010203020101; or :XCACB4000N5AA500FFDEADBEEF;
Put an LED and 1k resistor between 5V and STATUSLEDPIN to see some action when
sending a message.
Author: Speed Muller, Date: 2017.03.28
*/
#include <mcp_can.h>
#include <SPI.h>
#define VERSION "canJMRISendLCC ver 009"
#define CANPIN 10
#define CANINTPIN 11
#define STATUSLEDPIN 4 // PIN 13 is in use by SPI
#define CANMSGSIZE 8
MCP_CAN myCan( CANPIN ); // CAN object with CS as CANPIN
bool initSuccess = false; // keep track if CAN initialized
unsigned char data[CANMSGSIZE]; // send CANMSGSIZE bytes max
char inChar; // char from Serial port
unsigned char value; // data value with HI and LO nibble
byte charPosition; // track of where next Serial char goes
enum States { BEFORE, COLON, NHI, NLO, WAIT4SEMI, SEMICOLON };
States state = BEFORE;
void setup() {
pinMode( STATUSLEDPIN, OUTPUT );
digitalWrite( STATUSLEDPIN, HIGH );
Serial.begin( 115200 );
// init can bus with a baudrate = 500k
initSuccess = CAN_OK == myCan.begin( CAN_125KBPS, MCP_8MHz );
//.. Serial.println( VERSION ); // avoid talking to JMRI this round
} // setup
void loop() {
if ( !initSuccess ) { // only flash LED if CAN did not initialize
delay( 100 ); digitalWrite( STATUSLEDPIN, HIGH );
delay( 100 ); digitalWrite( STATUSLEDPIN, LOW );
return; // end of loop()
} // if CAN init failed
while ( Serial.available() > 0 ) {
inChar = Serial.read();
switch ( state ) {
case BEFORE: if ( inChar == ';' ) { state = BEFORE;
digitalWrite( STATUSLEDPIN, HIGH ); }
if ( inChar == ':' ) { state = COLON;
digitalWrite( STATUSLEDPIN, LOW ); }
break;
// only when a COLON showed up, we reset the data and position
case COLON: if ( inChar == ';' ) { state = BEFORE; break; }
if ( inChar == ':' ) { state = COLON; break; }
if ( inChar == 'N' ) state = NHI;
for ( int i = 0; i < CANMSGSIZE; i++ )
data[i] = 0x00; // clear data buffer
charPosition = 0; // next char goes to 0
break;
// only when an N showed, do we copy the incoming data, HI nibble first
case NHI: if ( inChar == ';' ) {
if ( charPosition > 0 ) {
state = SEMICOLON;
} else {
state = BEFORE; break;
} // if
} else {
if ( charPosition < CANMSGSIZE ) {
// HEX is 0-9 and A-F (caps assumed) remove '0' or
// 'A' to get value, get HI => val * 16
if ( inChar < 'A' ) value = ( inChar - '0' ) * 16;
else value = ( 10 + inChar - 'A' ) * 16;
state = NLO; // LO Nibble next
} else {
state = BEFORE; break;
} // else
break;
}
// no break here
case WAIT4SEMI: if ( inChar == ';' ) { state = BEFORE; /*break;*/ }
case SEMICOLON: digitalWrite( STATUSLEDPIN, HIGH );
// send CAN data: ID = 127, Standard frame,
// data len = CANMSGSIZE, data: buffer
myCan.sendMsgBuf( 127, 0, charPosition, data );
state = BEFORE;
delay( 100 ); // delay a bit
break;
// copy the incoming data LO nibble
case NLO: if ( inChar == ';' ) { state = BEFORE; break; } // if
if ( charPosition < CANMSGSIZE ) {
// HEX is 0-9 and A-F (caps assumed) remove '0' or
// 'A' to get value, val = val + LO
if ( inChar < 'A' ) value += ( inChar - '0' ) ;
else value += ( 10 + inChar - 'A' );
state = NHI;
data[charPosition++] = value; // add value to buffer
value = 0;
if ( charPosition == CANMSGSIZE ) {
state = WAIT4SEMI; break;
}
} else {
state = BEFORE; break;
} // else
break;
// anything else shows up? we reset
default: state = BEFORE;
break;
} // switch
} // while serial.available
} // loop
/******************************* END OF FILE **********************************/
/* CAN receive from JMRI over LCC When receiving one of 8 messages over CAN from JMRI's LCC turnouts, move one of the 4 servos into the correct position Ctrl-Shift-M to open the Serial Monitor to see your messages coming in. :XCACB4000N0300010203020101; Author: Speed Muller, Date: 2017.03.28 */ #include <mcp_can.h> #include <SPI.h> #include <Servo.h> #define VERSION "canJMRIServoReceiveLCC ver 006" #define OURID 0x03000102 #define DEBUG false #define IMALIVE 3000 #define CHECKIN 300 #define CANINTPIN 2 #define CANPIN 10 #define STATUSLEDPIN 4 #define CANMSGSIZE 8 #define SERVO1PIN 3 #define SERVO2PIN 5 #define SERVO3PIN 6 #define SERVO4PIN 9 /* skipping these until we can send events back to JMRI * #define SERVO1INPIN 2 * #define SERVO2INPIN 4 * #define SERVO3INPIN 7 * #define SERVO4INPIN 8 * #define UPDATETIME 100 */ // could also use 14,15,16 and 17 here: #define SERVO1LEDPIN A0 #define SERVO2LEDPIN A1 #define SERVO3LEDPIN A2 #define SERVO4LEDPIN A3 #define SERVOLEFT 75 #define SERVORIGHT 105 #define SERVOCNTR 90 /* And of course you need to copy the whole previous "class SlowerServo : public Servo { };" in here from before if you want slower moving servos like SlowerServo myServo1( UPDATETIME/2 ); and SlowerServo myServo2( 2*UPDATETIME ); */ Servo myServo1; Servo myServo2; Servo myServo3; Servo myServo4; MCP_CAN myCan( CANPIN ); // CAN object with CS as CANPIN bool initSuccess = false; // keep track if CAN initialized long unsigned int rxId; unsigned char len = 0; unsigned char rxBuf[8]; unsigned long incomingEvent; byte value; bool lightOn = false; int pin1 = HIGH; void setup() { pinMode( STATUSLEDPIN, OUTPUT ); delay( 500 ); digitalWrite( STATUSLEDPIN, HIGH ); delay( 500 ); digitalWrite( STATUSLEDPIN, LOW ); delay( 500 ); digitalWrite( STATUSLEDPIN, HIGH ); delay( 500 ); digitalWrite( STATUSLEDPIN, LOW ); delay( 500 ); digitalWrite( STATUSLEDPIN, HIGH ); Serial.begin( 115200 ); initSuccess = ( CAN_OK == myCan.begin( CAN_125KBPS, MCP_8MHz ) ); // init can bus : baudrate = 125k pinMode( CANINTPIN, INPUT ); // Setting pin CANINTPIN for /INT input pinMode( SERVO1LEDPIN, OUTPUT ); pinMode( SERVO2LEDPIN, OUTPUT ); pinMode( SERVO3LEDPIN, OUTPUT ); pinMode( SERVO4LEDPIN, OUTPUT ); incomingEvent = 0; Serial.println( VERSION ); // since you are not using the serial port to JMRI myServo1.attach( SERVO1PIN ); myServo2.attach( SERVO2PIN ); // attach to pins myServo3.attach( SERVO3PIN ); myServo4.attach( SERVO4PIN ); // attach to pins myServo1.write( SERVOCNTR ); myServo2.write( SERVOCNTR ); // move to middle myServo3.write( SERVOCNTR ); myServo4.write( SERVOCNTR ); // move to middle } // setup void loop() { if ( !initSuccess ) { // flash LED if CAN did not initialize delay( 100 ); digitalWrite( STATUSLEDPIN, HIGH ); delay( 100 ); digitalWrite( STATUSLEDPIN, LOW ); Serial.println( "no CAN ..." ); initSuccess = ( CAN_OK == myCan.begin( CAN_125KBPS, MCP_8MHz ) ); // init can bus : baudrate = 125k return; // end of loop() } // if CAN init failed if ( !digitalRead( CANINTPIN ) ) { // If pin 2 is low, read receive buffer myCan.readMsgBuf( &len, rxBuf ); // Read data: len = data length, // buf = data byte(s) rxId = myCan.getCanId( ); // Get message ID int i = 0; // declare outside "for", we are breaking and continuing... for ( i = 0; i < len; i++ ) { // add each byte to 16 x 16 of the previous incomingEvent = incomingEvent * 16 * 16 + rxBuf[i]; if ( i == 3 ) break; // only need the first 4 bytes here to match ID } // for if ( incomingEvent != OURID ) // is it for us? len = 0; // bail then // it is not our message incomingEvent = 0; // start over, unsigned long int is not big enough for ( i; i < len; i++ ) { // add each byte to 16 x 16 of the previous incomingEvent = incomingEvent * 16 * 16 + rxBuf[i]; } // for Serial.println( ); if ( len == 8 ) { // this node only do 8 byte messages! switch ( incomingEvent ) { // of course the messages are hard coded // here, but you could easily store them // in EEPROM and update them as you // configure your nodes case 0x03020101: // 1st servo Serial.println( "1-closed" ); myServo1.write( SERVORIGHT ); digitalWrite( SERVO1LEDPIN, LOW ); break; case 0x03020100: Serial.println( "1-thrown" ); myServo1.write( SERVOLEFT ); digitalWrite( SERVO1LEDPIN, HIGH ); break; case 0x03020201: // 2nd servo Serial.println( "2-closed" ); myServo2.write( SERVORIGHT ); digitalWrite( SERVO2LEDPIN, LOW ); break; case 0x03020200: Serial.println( "2-thrown" ); myServo2.write( SERVOLEFT ); digitalWrite( SERVO2LEDPIN, HIGH ); break; case 0x03020301: // 3rd servo Serial.println( "3-closed" ); myServo3.write( SERVORIGHT ); digitalWrite( SERVO3LEDPIN, LOW ); break; case 0x03020300: Serial.println( "3-thrown" ); myServo3.write( SERVOLEFT ); digitalWrite( SERVO3LEDPIN, HIGH ); break; case 0x03020401: // 4th servo Serial.println( "4-closed" ); myServo4.write( SERVORIGHT ); digitalWrite( SERVO4LEDPIN, LOW ); break; case 0x03020400: Serial.println( "4-thrown" ); myServo4.write( SERVOLEFT ); digitalWrite( SERVO4LEDPIN, HIGH ); break; default: break; } // switch incomingEvent } // if len == 8 } // if CANINTPIN } // loop /******************************* END OF FILE **********************************/ The files attached below are a from August 2016, and the code shown above is newer, so use that instead until the files below are updated. Enjoy |