Skip to main content

Hello Everyone!

I am in the process of trying to learn how use Lionel's TMCC and Legacy commands to send information to the legacy base via serial connection. Currently I am trying to understand this just for "TMCC1" as Lionel calls it in their publications. I have been reading the Lionel LCS partner Documentation for Legacy Command Protocol and the older manual for TMCC, which has information in the Basic Computer Control chapter (page 36).

I am very good at programming and believe I have the basic knowledge to do this, but I have never really worked with serial communication using bytes directly. I have read on the forum that some people have played with this and got this to work, but there was never really any indication as how it was done. From what I have read in the manuals just for TMCC commands, they use 3 bytes corresponding to a 16-bit word but can really only figure out the first one. This why I am starting this thread. 

I am currently just looking at sending commands to throw switches as required. In the publications it states that the first byte is 0xFE, which indicates that the command being sent to the Legacy base is a "TMCC1" command. Is the next command the switch address (second byte) followed by the command and data field (third byte)?  How do you figure out what to send the base, will it be in the same format as 0xFE or will it be different. I tried doing a conversion from binary to hexadecimal, but don't get the same format. Here is an example of what I am doing for example:

Lets say I want to throw the switch out for switch with a TMCC ID of 27. We know that 8 bits = 1 byte and 16 buts = 2 bytes. Since we need to send three bytes to the base and the first one is 0xFE, we need to determine the next two bytes (16 bits). 

The second byte is needs to be 8 bits, so in binary for a switch an ID of 27 it will be 01 0011011 which comes out to be a hexadecimal of 4D. The third byte would be 10011111 in binary, which would be 9F in hexadecimal. Is this correct or am I missing something? I feel like I am doing something wrong. Also, is when sending the data over serial do you send all three bytes at once or do they have to be sent independently? 

Thanks in advance for the help. 

Chris 

Original Post

Replies sorted oldest to newest

Hi Chris - 

Your math is correct, though in your post your second byte is 9 bits long. Remember, the TMCC addresses don't align on the byte boundaries.  But 4D 9F is correct for setting switch 27 to through.

To send that command to the Base you would just transmit FE 4D 9F. I'm not clear on what you mean by sending bytes together or independently.... Depending on what kind of hardware you are using, you would usually just dump those three bytes into a serial buffer, and the hardware would take care of sending them out at 9600 baud. What hardware are you using to talk to the base?

Here is a code fragment of how to assemble the two TMCC command bytes:

 TMCCCommand1 = deviceType | (deviceID >> 1); // Byte 2 is combination of type field and first 7 bit of TMCC address
TMCCCommand2 = (deviceID << 7) | (commandField << 5) | dataField; // Byte 3 is last bit of TMCC address, command field (2 bits), plus data field
where these are the deviceTypes:
 ENGType = B00000000
SWType = B01000000
TRType = B11001000
Last edited by Professor Chaos

Chris,

I've just gone through this myself for a project I'm working on, and have become very familiar with how TMCC commands work over serial.  Lots of folks also supplied a bunch of useful help on this thread.  

What I think your issue is, is not exactly understanding how the commands are sent.  The TMCC commands are always 3 bytes long, exactly 24 bits.  the first byte/8bits is meaningless other than to confirm that you do have a TMCC command, and not just garbage data.  (always 0xFE)    The second and third bytes are best used added together into a 16 bit number.  These 16 bits form your actual command word, which contains 4 types of information  A device type (engine, switch, route, etc) , the device's assigned address, (engine 52, switch 16, route 4), a command type, (is this a normal command, a speed command, a programing command, etc) , and lastly a data field( what is the actual command you want done.)

The command type and the data field are always the last 7 bits of the 16 bit command word, but the device type and address are not always the same.  Engines, Switches and Accessories have a 2 bit device type identifier and a 7 bit address.  Trains/Tracks and Routes use a 5 bit device type and 4 bit address.  

Because of this, the very first thing you have to do with the data is have a way to read the first (most significant) 5 bits to see what type of device the command is for, then you tell your program how to deal with each device type.  For example, in pseudo code:

if (first 5 bits) == switch device type :

   then the device being addressed = the last 7 bits of the most significant 9 bits

  and the command type = bits 6 and 7,  and the data field = the first(least significant) 5 bits.  

What follows if the actual code i have running on the Arduino platform to accomplish this job.  For my purposes I decided to treat the command type field and the data field as one 7 bit field rather than split them into two separate variables.  Arduino uses a language called Processing, which is based on C language.  If you can follow C it should be pretty straight forward.  There are better ways to do this, I'm sure, but using if statements was what worked for me.  

In this code the var currentCommandWord is the 16 bits of data (last 2 of the 3 bytes sent by the TMCC base)

the code returns the following vars to be used elsewhere in the program:

commandType:   What is the command talking to? Engine, Switch, etc.

addressedDevice:  what address is being talked to.  ex, which engine, or which switch.  

assignedCommand:  what is the actual command.  ex, what do you want that engine to do.

 

//layerd command breakdown Set command type-> Set address-> set assigned command.
commandTypeFirstCheck = currentCommandWord>>11; // first check of type = most significnt 5 bits of fullWord.

if (commandTypeFirstCheck < 24) { // if the 5 bits is < 24 it is a 2 bit commandType (0=Engine, 1=Switch, 2=Accessory)
commandType = currentCommandWord>>14; // set command type to two bit type.
// addrsss setup for 7 bit device addresses
commandAddressFirstShift = currentCommandWord<<2; // shift off left most 2 bits
addressedDevice = commandAddressFirstShift>>9; // shift off all but 7 most significant bits.
}
else if (commandTypeFirstCheck == 24) { // if the 5 bits = 24...
commandType = 24; // type = 24 (Group Accessorys)
// address setup for 4 bit addressed
commandAddressFirstShift = currentCommandWord<<5; // shift off left most 5 bits
addressedDevice = commandAddressFirstShift>>12; // shift off all but 4 most significant bits.
}
else if (commandTypeFirstCheck == 25) { // if the 5 bits = 25...
commandType = 25; // type = 25 (Train Command)
// address setup for 4 bit addressed
commandAddressFirstShift = currentCommandWord<<5; // shift off left most 5 bits
addressedDevice = commandAddressFirstShift>>12; // shift off all but 4 most significant bits.
}
else if (commandTypeFirstCheck == 26) { // if the 5 bits are 26 or 27...
commandType = 13; // type = 13 (Route Command)
// address setup for 5 bit addressed
commandAddressFirstShift = currentCommandWord<<4; // shift off left most 4 bits
addressedDevice = commandAddressFirstShift>>11; // shift off all but 5 most significant bits.
}
else if (commandTypeFirstCheck == 27) { // if the 5 bits are 26 or 27...
commandType = 13; // type = 13 (Route Command)
// address setup for 5 bit addressed
commandAddressFirstShift = currentCommandWord<<4; // shift off left most 4 bits
addressedDevice = commandAddressFirstShift>>11; // shift off all but 5 most significant bits.
}
else if (commandTypeFirstCheck == 31) { // if the 5 bits = 31 (all HIGH) set a HALT condition.
haltCommand = 1;
systemHALTcheck();
// this is here as a failsafe on halt if for some reason a halt was missed by earlier code.
}

assignedCommandShift = currentCommandWord<<9;
assignedCommand = assignedCommandShift>>9;

 

If you need more help, let me know.  

JGL

EDIT:  It seems I don't know how to use code tags on this forum... Maybe someone could enlighten me.  <code> doesn't seem to work, and using the option of "code" on the formatting drop down looks like garbage.  Not sure how to get it formatted correctly.

Last edited by JohnGaltLine

Professor Chaos and John, 

Thank you very much for your replies. It seems like I understood it more than I thought, but still have some questions.

Professor Chaos posted:

What hardware are you using to talk to the base?

I am not using anything yet. I am still in the planning stage, but I want to use an arduino. I was thinking of a Arduino mega with serial shield connected to a Lionel SER2. The mega board would like a server and communicate with other boards on the layout via xbee radio, wifi, or ethernet. 

Professor Chaos posted:

 

Here is a code fragment of how to assemble the two TMCC command bytes:

 TMCCCommand1 = deviceType | (deviceID >> 1); // Byte 2 is combination of type field and first 7 bit of TMCC address
TMCCCommand2 = (deviceID << 7) | (commandField << 5) | dataField; // Byte 3 is last bit of TMCC address, command field (2 bits), plus data field
where these are the deviceTypes:
 ENGType = B00000000
SWType = B01000000
TRType = B11001000

Thanks for the code example, but where does the "B" come from in your deviceType variable? I am sorry these bytes and bits are all new to me. 

JohnGaltLine posted:

Chris,

I've just gone through this myself for a project I'm working on, and have become very familiar with how TMCC commands work over serial.  Lots of folks also supplied a bunch of useful help on this thread.  

What I think your issue is, is not exactly understanding how the commands are sent.  The TMCC commands are always 3 bytes long, exactly 24 bits.  the first byte/8bits is meaningless other than to confirm that you do have a TMCC command, and not just garbage data.  (always 0xFE)    The second and third bytes are best used added together into a 16 bit number.  These 16 bits form your actual command word, which contains 4 types of information  A device type (engine, switch, route, etc) , the device's assigned address, (engine 52, switch 16, route 4), a command type, (is this a normal command, a speed command, a programing command, etc) , and lastly a data field( what is the actual command you want done.)

The command type and the data field are always the last 7 bits of the 16 bit command word, but the device type and address are not always the same.  Engines, Switches and Accessories have a 2 bit device type identifier and a 7 bit address.  Trains/Tracks and Routes use a 5 bit device type and 4 bit address.  

 

John thanks for the link to thread. I can see what you are saying. You would send the 0xFE and the second and third bytes together, so the bytes sent to the base to throw switch 27 out would be 0xFE, 4D9F. Is this correct? 

What does the code look like to send the commands to the base via serial. Is it just a serial write, but how do you send the bytes together?

What I am trying to do is have the arduino monitor several buttons for switches. When the button is pressed the arduino will send the TMCC command to the master arduino as mentioned above, which will then send the command to the base. Can the master arduino also listen to the command base, so that buttons can be updated if the switch is thrown via the cab-2 remote, so the arduino would have to listen and write to its master along with monitor buttons attached. The master would recieve and send commands from the base. This could be done correct or can only send commands to the Legacy base.

Thank you both very much for responding to my post. I feel like I am getting somewhere with this. 

As always thanks in advance for the help.

Chris 

The B is simply a binary number designator used in C/Arduino language, similar to using 0x before a Hex number.  

0xFE4D9F is correct to send a throw out command to switch 27.  

On the arduino you would simple say Serial.write(0xFE4D9F, HEX)  the AtMega will handle the rest with its serial buffer.  I tend to send out the decimal equivalent numbers because I suck at doing HEX math in my head, but can do Binary and Decimal pretty well.  I would make my life simpler by sending the data in two parts, first the 0xFE start byte, then the actual data.  this will let you use an unsigned int as your variable which is real easy to work with.  seems to be what you already have figured out.  Ex:  

> unsigned int switchCommand = 0x4D9F;

>Serial.write(0xFE);

>Serial.write(switchCommand);

The project I am working on only reads from the TMCC base, so I can help quite a bit if you need a subroutine that can do that.  As for your actual connection, if you have an RS232 shield laying around, it is a simple solution.  if not, if you are using an original TMCC base, NOT A LEGACY BASE!, you can use a 7404 inverter to flip the highs and lows and connect directly to the port at logic level.  The original TMCC base uses logic level signals of 0 and 5 volts.  the legacy base uses more or less true RS232 voltages of +/- 8 volts.  

It seems your project should be pretty straight forward with one subroutine that says 'if button pressed, serial.write(thatSwitchThrow) and turn on a light'.  and a second routine that says 'if serial.read = thatSwitchThrow, then turn on the light.'  

If you need help with code, let me know.  

JGL

 

 

 

John,

Thanks for the quick response back. Since I want to have several switches controlled by the Arduino. Can I do something like this (please note that I am not showing the for loop and if statements that checks the button state. Also note that I am using arrays to match a button to switch ID:

Initial Variable setup

[code]

int TMCC1 = 0xFE;
byte TMCC_SwitchID[4]= {B0000001, B0000010, B0000101, B0001010}; /* Binary Code for each switch used by the program */
                                        /*          1                2                5                10 */
byte TMCC_Thru = B0000000; /* Binary Code for throwing the switch in the through position */
byte TMCC_Out = B0011111; /* Binary Code for throwing the switch in the out position */

[/code]

Lets say that we want to throw switch 5 to the switch out position. (Again note the rest of the code in the loop function is not shown:

[code]

Serial.write(TMCC1);
Serial.write(TMCC_SwitchID[3]); /* Switch 5 is in the third position of the array */ 
Serial.write(TMCC_Out);

[/code]

I wrote my code this way because I needed away to store the switch ID and felt it would be better to do this way. Will this way I wrote the code still send the correct command to the base?

Thanks, 

Chris 

Last edited by crood58

Chris, you really don't have to worry about sending bytes together vs independently.  When you do a serial write, the byte gets deposited in an internal buffer. The Arduino can execute tens of thousands of other instructions in the time it takes to send 3 bytes at 9600 baud. So your code puts bytes in the buffer and moves on to to other things;  Arduino routines running in the background will send those bytes out the serial line with the right timing for the selected baud rate.

However, JGL, I am not sure your approach of writing two bytes as an unsigned int will work. Serial.write is listed as taking a single byte, a string, or a pointer to a buffer. I just tried passing an unsigned int to Serial.write and only the second byte was printed.

So the simplest way is to use three writes:

Serial.write(0xFE);
Serial.write(commandByte2);
Serial.write(commandByte3);

 

One thing to keep in mind: if  you are sending multiple 3-byte commands, you have to wait ~ 30 milliseconds between commands or the Base will ignore them. The approach I take is to deposit TMCC  commands into a circular buffer.  Then every pass through loop(), I check to see if at least 30 milliseconds have elapsed since the last command transmitted to the Base. If so, the next command is read from the buffer and sent to the base.

If you don't want to build your own level shifter circuit, you can just buy a MAX232-based adapter like this one.  It will invert the RS232 voltages and take care of the negative voltage issue for you.

The Arduino could monitor the base - I believe the base echoes serial commands that are sent from the CAB.  But you can't be certain that this represents the current state of the switch - what if a locomotive tripped the non-derail feature?

A better (though more complex) approach is to direct monitor the switch state.  But how easy that is depends on what kind of switch machine you are using.

Professor Chaos posted:

Chris, you really don't have to worry about sending bytes together vs independently.  When you do a serial write, the byte gets deposited in an internal buffer. The Arduino can execute tens of thousands of other instructions in the time it takes to send 3 bytes at 9600 baud. So your code puts bytes in the buffer and moves on to to other things;  Arduino routines running in the background will send those bytes out the serial line with the right timing for the selected baud rate.

However, JGL, I am not sure your approach of writing two bytes as an unsigned int will work. Serial.write is listed as taking a single byte, a string, or a pointer to a buffer. I just tried passing an unsigned int to Serial.write and only the second byte was printed.

So the simplest way is to use three writes:

Serial.write(0xFE);
Serial.write(commandByte2);
Serial.write(commandByte3);

 

 

So the way I show it in my previous post is ok?

Professor Chaos posted:

 One thing to keep in mind: if  you are sending multiple 3-byte commands, you have to wait ~ 30 milliseconds between commands or the Base will ignore them. The approach I take is to deposit TMCC  commands into a circular buffer.  Then every pass through loop(), I check to see if at least 30 milliseconds have elapsed since the last command transmitted to the Base. If so, the next command is read from the buffer and sent to the base.

 Is there a tutorial on how to do this? I would to use this on the master so it would the commands so they would not be ignored.

Professor Chaos posted:

If you don't want to build your own level shifter circuit, you can just buy a MAX232-based adapter like this one.  It will invert the RS232 voltages and take care of the negative voltage issue for you.

The Arduino could monitor the base - I believe the base echoes serial commands that are sent from the CAB.  But you can't be certain that this represents the current state of the switch - what if a locomotive tripped the non-derail feature?

A better (though more complex) approach is to direct monitor the switch state.  But how easy that is depends on what kind of switch machine you are using.

Thank you for the info on the Max232. 

Yes, you are right about the current state of the switch. I am hoping that Lionel comes out the switch monitor soon, which would update the base if non-derailing is activated. I am planning on using z-stuff switch machine, the DZ-255 which is a TMCC machine.

Chris 

 

 

Chris, your code would not work.  Remember, the TMCC addresses are NOT aligned on the byte boundaries.  As shown on page 36 of the TMCC manual, a switch command is:

01AAAAAAACCDDDDD  (where AAAAAAA is the TMCC id)

splitting that into two bytes gets you

01AAAAAA ACCDDDDD

so part of the TMCC id is in the third byte.  As in my code fragment above, what you want to do is shift and OR the bits to assemble the bytes:

Byte2 = B01000000 | (TMCC_SwitchID[3] >> 1);
Byte3 = (TMCC_SwitchID[3] << 7) | TMCC_Out;

Serial.write(0xFE);
Serial.write(Byte2);
Serial.write(Byte3);



I would look at the Arduino Reference page, specifically the parts about bitwise OR and bitwise shifting, to understand how the byte assembly works.

Last edited by Professor Chaos

I realized that the Sparkfun page I linked to is not actually a MAX232 circuit - you might want to use one that is a real MAX232 as some people report problems with the Sparkfun adaptor.

You can look at my code here (the Train Control sketch) to see how I did the TMCC buffer thing - it's at the top of the _interface tab.  But I am afraid the code is very complex and probably not too easy to understand.  It also depends on some libraries that are in the libraries folder.

I am reading the state of DZ-2500 switch machines by tapping into the line that normally lights red or green LEDS on the pushbutton controller.  If I recall correctly, I stuck a few diodes in series with that line, and fed it into an optocoupler. That gave me a logic level signal representing the state of the switch machine.  But that may be getting more complex than you want to try at first.

Chaos, You re probably right on how serial.write works.  

I have to pass out  at the moment but will catch up on this thread when I get back up.  

Chris, I think Chaos has outlined some of your problems pretty well.  just keep it in mind that TMCC doesn't talk in bytes, but in bits.  your program needs to translate the bytes to bits and bits to bytes.  bitshift will be your friend here.  

I'm going to dump some code here for fun.  this is my routine to read incoming data.  

<code>

if (Serial1.available() > 2) { // check if there is enough data on serial buffer for a command.
inByte = Serial1.read(); // read the serial data
if (inByte == 0xFE) { // Check if the data is a vaild TMCC command starting with 0xFE
systemHALTclear(); // Clear HALT condition if any new commands are sent from TMCC Base.
byteTwo = Serial1.read(); // Read next byte of serial data as byteTwo
byteThree = Serial1.read(); // Read next byte of serial data as byteThree
}
else{
checkNextByte(); // If the data recived is not a valid command skip to next byte and try again.
}

byteTwoShifted = byteTwo<<8; // shift byteTwo 8 bits left.
fullWord = (byteTwoShifted + byteThree); // Combine the bytes into one 16 bit packet of data
}

void checkNextByte() {
int trashData = 0;
trashData = Serial1.read();
Serial.println(" ");
Serial.println("T R A S H D A T A :");
Serial.println(trashData, HEX);
Serial.println(" ");
//delay(500);
}

 

</code>

JGL

 

Professor Chaos posted:

Chris, your code would not work.  Remember, the TMCC addresses are NOT aligned on the byte boundaries.  As shown on page 36 of the TMCC manual, a switch command is:

01AAAAAAACCDDDDD  (where AAAAAAA is the TMCC id)

splitting that into two bytes gets you

01AAAAAA ACCDDDDD

so part of the TMCC id is in the third byte.  As in my code fragment above, what you want to do is shift and OR the bits to assemble the bytes:

Byte2 = B01000000 | (TMCC_SwitchID[3] >> 1);
Byte3 = (TMCC_SwitchID[3] << 7) | TMCC_Out;

Serial.write(0xFE);
Serial.write(Byte2);
Serial.write(Byte3);



I would look at the Arduino Reference page, specifically the parts about bitwise OR and bitwise shifting, to understand how the byte assembly works.

Geez! I got so got up in the method of sending the commands that I forgot. My bad. I updated my code as you suggested, which I like because I would like to just add the switch id and let the code handle the rest. It would be a pain to have to put all the different code for the different switch ids in the code. Here is my updated code: 

The variables: 

// ---- TMCC Variables
int TMCC = 0xFE;
byte TMCC_Switch = B01000000;
byte TMCC_SwitchID[4]= {B0000001, B0000010, B0000101, B0001010}; /* Binary Code for each switch used by the program */
/* 1 2 5 10 */
byte TMCC_Thru = B0000000; /* Binary Code for throwing the switch in the through position */
byte TMCC_Out = B0011111; /* Binary Code for throwing the switch in the out position */

// ---- Bytes to Send to Base
byte Byte2;
byte Byte3;

 

The code: 

 Byte2 = TMCC_Switch | (TMCC_SwitchID[3] >> 1);
Byte3 = (TMCC_SwitchID[3] << 7) | TMCC_Out;
Serial.write(TMCC);
Serial.write(Byte2);
Serial.write(Byte3);

 

Professor Chaos posted:

I realized that the Sparkfun page I linked to is not actually a MAX232 circuit - you might want to use one that is a real MAX232 as some people report problems with the Sparkfun adaptor.

 

Professor Chaos would this be the MAX232 you are referring to? 

Professor Chaos posted:

You can look at my code here (the Train Control sketch) to see how I did the TMCC buffer thing - it's at the top of the _interface tab.  But I am afraid the code is very complex and probably not too easy to understand.  It also depends on some libraries that are in the libraries folder.

Thank you. I will take a look at this. 

Professor Chaos posted:

I am reading the state of DZ-2500 switch machines by tapping into the line that normally lights red or green LEDS on the pushbutton controller.  If I recall correctly, I stuck a few diodes in series with that line, and fed it into an optocoupler. That gave me a logic level signal representing the state of the switch machine.  But that may be getting more complex than you want to try at first.

I will have to keep this in mind at least I know there is away to keep the state of the switch updated. I was thinking that if Lionel ever released this. That it would update the base the state of the switch, I am not sure that the base would transmit that over "serial" or if the arduino would be able to read the commands the switch monitor was sending to the base. 

What I want to do now is get a arduino and SER2 and try this out to see how the base is outputting the commands. Do either you know if the commands outputted from the base are the same as the ones that go in or is it different?

My next question is probably more Arduino related, but if I have arduino monitoring the output from the command base, sending to the command base, and monitoring buttons attached to the arduino. Is it best to use if statements for each? I think it would be better to check the output from the base first, then check the buttons, and then send commands to base. For example. 

if(Serial.readBytes() != 0){
//do something
}
if(Button = HIGH) {
//do something and then do a serial write
}

 

Thanks, 

Chris 

Professor Chaos, 

I had the chance to look at your TMCC buffer in your _interface file. 

You use the following code in your code to send and buffer the TMCC commands it seems. 

void sendTMCCCommand (SysTypes deviceType, byte deviceID, byte commandField, byte dataField, byte repeats) // deposit TMCC command in buffer
{
const byte TMCCCommand0 = 0xfe;
byte TMCCCommand1;
byte TMCCCommand2;

TMCCCommand1 = deviceType | (deviceID >> 1); // Byte 2 is combination of type field and first 7 bit of TMCC address
TMCCCommand2 = (deviceID << 7) | (commandField << 5) | dataField; // Byte 3 is last bit of TMCC address, command field (2 bits), plus data field

while (repeats > 0) {
TMCCBuffer.put(TMCCCommand0);
TMCCBuffer.put(TMCCCommand1);
if (TMCCBuffer.put(TMCCCommand2) == 0) { // write data to buffer; returns 0 if buffer capacity exceeded
debug << F("TMCC command buffer overflow") << endl;
FAULT();
}

repeats--;
}

processTMCCBuffer(); // attempt immediate execution
}

void processTMCCBuffer() // if minimum interval has elapsed, transmit one TMCC command
{
if (TMCCBuffer.getSize() == 0) return;


if ((millis() - lastTMCCTransmit) > TMCCMinInterval) {
TMCCPort.write(TMCCBuffer.get());
TMCCPort.write(TMCCBuffer.get());
TMCCPort.write(TMCCBuffer.get());
lastTMCCTransmit = millis();
}
//else debug << "waiting for TMCC interval" << endl;
}

 

My question is are you using the send function to try to send the command first and then buffer it? It seems in your train control code you are just using the buffer function. 

Thanks in advance, 

Chris

Professor Chaos,

I have finally got back to my arduino project and have built a byte/serial buffer similar to the one you use. I was wondering if you could answer me a question I have about your train control code in reference to the variable repeats in your send TMCC command. How does this come to play and how does it become greater than zero?  I have posted what I'm talking about below. I'm just not sure where the value for this variable is coming from originally. 

void sendTMCCCommand (SysTypes deviceType, byte deviceID, byte commandField, byte dataField, byte repeats) // deposit TMCC command in buffer
{
const byte TMCCCommand0 = 0xfe;
byte TMCCCommand1;
byte TMCCCommand2;

TMCCCommand1 = deviceType | (deviceID >> 1); // Byte 2 is combination of type field and first 7 bit of TMCC address
TMCCCommand2 = (deviceID << 7) | (commandField << 5) | dataField; // Byte 3 is last bit of TMCC address, command field (2 bits), plus data field

while (repeats > 0) {
TMCCBuffer.put(TMCCCommand0);
TMCCBuffer.put(TMCCCommand1);
if (TMCCBuffer.put(TMCCCommand2) == 0) { // write data to buffer; returns 0 if buffer capacity exceeded
debug << F("TMCC command buffer overflow") << endl;
FAULT();
}

repeats--;
}

processTMCCBuffer(); // attempt immediate execution
}

 

As always thanks for the help.

Chris

Hello everyone, sorry for reviving an old thread, but I'm now trying my hand at a small program using Excel VBA to throw switches.  I think I understand the basic concepts, but have yet to get anything to really work.

My code does send something through the com port as I see the red and green lights flash on the SER when I try.  But, nothing happens to the engine.  I'm trying a simple fire rear coupler command.

So, I think what I am sending is most likely incorrect.  I've got a couple of questions if anyone could please help me out:

1 - Do you send the 3 bytes as binary or as hex?  Seems like binary as the port settings call for 8 data bits.

2 - Do you add a single bit ("1") to the end as the stop bit?  I don't think so, but figured I'd ask.

3 - Can you just send this as a string to the com port, or does it have to be in some other variable type (such as byte)?

4 - If it's byte type, then do I declare it with a size of 24? Or 3?

I could post my code if anyone would be willing to give it a look.

Thanks for any help!

BillYo, yes, I test fire it with the Legacy remote before I try to trigger it with my program.

Here's the current version of my core code, and still no luck.   There's alot of other code, mostly superfluous I think (borrowed from Google searches), but since I'm getting a response in the SER when I try to write to the com port, I think the rest is fine.  My guess is the error lies in this part of my code.  I shamelessly borrow from public code snippets, so no credit for coming up with what I do have all on my own!  I've also gotten some coaching from Harvy Ackermanns, many thanks to him!

Sub TestEngine66RearCoupler()

intPortID = 3
Call Terminate 'make sure port is available
Call Initialize(intPortID) 'open port
TMCC1String = "11111110"
EngineNum = Dec2Bin(66, 7) 'convert engine number to binary
CommandVal = "00110"
CmdString = "00" & EngineNum & "00" & CommandVal
CmdStringLeft = Left(CmdString, 8) 'parse string into two bytes, number 1
CmdStringRight = Right(CmdString, 8) 'parse string into two bytes, number 2
CmdByte(1) = bin2Byte(TMCC1String) 'try to convert byte string into a byte array - THIS IS WHERE I THINK MY ERROR IS
CmdByte(2) = bin2Byte(CmdStringLeft) 'try to convert byte string into a byte array - THIS IS WHERE I THINK MY ERROR IS
CmdByte(3) = bin2Byte(CmdStringRight) 'try to convert byte string into a byte array - THIS IS WHERE I THINK MY ERROR IS
Call ComPortCmd(intPortID, CmdByte) 'Call the routines that send the message
Call Terminate

End Sub


Function bin2Byte(ByVal s As String) As Byte() 'THIS FUNCTIONS CONVERTS THE STRING INTO THE BYTE ARRAY
    Dim bitsIn As Long
    bitsIn = 8

    Dim i As Long
    'pad with zeros
    If Len(s) Mod bitsIn <> 0 Then
        For i = 1 To bitsIn - Len(s) Mod bitsIn
            s = "0" & s
        Next i
    End If
     
    i = Len(s)
    Dim bytes() As Byte
    Dim byteCount As Long
    byteCount = -1
    Dim sByte As String
    Do While LenB(s) > 0
        byteCount = byteCount + 1
        ReDim Preserve bytes(byteCount)
         
        sByte = Mid$(s, Len(s) - bitsIn + 1)
        'sByte = Mid$(s, 1, bitsIn)
        For i = 0 To 7 Step 1
            bytes(byteCount) = bytes(byteCount) + CLng(Mid$(sByte, 8 - i, 1)) * 2 ^ i
        Next i
        s = Mid$(s, 1, Len(s) - bitsIn)
        's = Mid$(s, bitsIn + 1)
    Loop
    bin2Byte = bytes
End Function

I'm on the road so I can't reference my source, but my handling 24-bit commands goes something like this:

To build the command, initialize a 32-bit unsigned int _command with 0xFE << 16; // high order byte of 3
// now build the low order 16 bits
_command |= (k_TMCC_SwitchType + (_addressValue << k_address_shift) + k_TMCC_ThroughThrough));

// at this point you have an unsigned int with the TMCC1 command that easily passed around your code.
// Using shifts (and bit masks), convert the unsigned into into a byte array and output to the UART.

For parsing an incoming steam of 3-byte TMCC data, convert the bytes to an unsigned int.  Extract the address and data bits using masks and shifts.  Zero the address and data bits in the unsigned int so the unsigned int contains bits for only the top level TMCC type (i.e. eng, train, switch, acc) and command.  Use this value to index a struct table with additional info such as text description of the command, pointers to action functions, etc.  Beats the ugly if-else parsing of TMCC 24-bit words.

I'm still having no luck with this.  So, trying to break it down a little for troubleshooting.  Could someone confirm for me that I have the correct string and then three bytes necessary to fire the rear coupler on engine id 66:

String:  111111100010000100000110
Byte 1: 11111110
Byte 2: 00100001
Byte 3: 00000110

Reference:


0xFE (Hex) = 11111110 (Binary)
66 (Decimal) = 1000010 (Binary)

Thanks!!!

Attachments

Images (3)
  • Excerpt from LCS Protocol
  • Excerpt from LCS Protocol
  • Excerpt from LCS Protocol

EUREKA!

I got it all working.  Below is a screenshot of what I've done so far.  If anyone is interested in the code or the Excel file, please just drop me a message with your email.

I really want to thank Harvy Ackermans for all of his help and encouragement!  I highly recommend his product (eTCC).  I've used it for a couple of years, and it inspired me to want to make a GUI for switch control.  Thanks again Harvy!

Attachments

Images (1)
  • Yard Master screenshot

Folks,

Sanity check please.  OK, i've got the FE (orF8) 1st byte and the 00 for engine command.  It was calculating the 2nd byte with the TMCC ID that was driving me crazy.  The TMCC ID is calculated using 7 bits 64-32-16-8-4-2-1. The 2nd byte LSB bit would get shifted right 1 bit to the MSB of the 3rd byte.  Calculate the 2nd byte using 8 bits.  Add the bits for command and action and calculate the 3rd byte including the 2nd byte LSB as the 3rd byte MSB.  Am I'm finally getting this?  Geez, I hope so or I'm breaking out the extra strength Ibuprofen again.  Thanks for any info, insights, pointers or suggestions.

bd

Last edited by barnun

BillYo, I wish I could tell you precisely.  It got to the point I was just trying all sorts of permutations of the setup and such.  The key though was I used some free software to monitor the strings being sent to the com port.  I found with it that I was sending "00" in between the bytes needed.  Then, I just started experimenting with every way I could google to get VBA to open a port and communicate without sending those in between bytes.  Eventually, I just got lucky!

Barnun, here is my VBA code to parse a basic engine command and send it.  Maybe this will help.

Dim CmdByte(2) As Byte

TMCC1String = "11111110"
EngineNum = Dec2Bin(EngNumber, 7)
CmdString = "00" & EngineNum & "00" & CommandVal
CmdStringLeft = Left(CmdString, 8)
CmdStringRight = Right(CmdString, 8)
CmdByte(0) = WorksheetFunction.Bin2Dec(TMCC1String)
CmdByte(1) = WorksheetFunction.Bin2Dec(CmdStringLeft)
CmdByte(2) = WorksheetFunction.Bin2Dec(CmdStringRight)
Open "COM3:9600,N,8,1,X" For Binary Access Read Write As #1
Put #1, , CmdByte
Close #1

Add Reply

Post

OGR Publishing, Inc., 1310 Eastside Centre Ct, Suite 6, Mountain Home, AR 72653
800-980-OGRR (6477)
www.ogaugerr.com

×
×
×
×
Link copied to your clipboard.
×
×