Projects Research & Design Hub

Build an IR-Based LEGO Train Controller (Part 1)

Figure 1 First prototypes used Heltec’s ESP8266 MCU with display, a Vishay TSAL6200 IR LED and a TSOP38538 IR demodulator to transmit and receive the LEGO Power Function (LPF) RC protocol.
Written by Jeff Bachiochi

Near-Wavelength IR in Action

Near-wavelength IR is a technology we use every day, but how it actually works in an embedded control application is fascinating. Jeff explains. He then makes use of LEGO’s sets of electronic components and other devices to build an IR-based controller for LEGO IR remote-controlled trains.

  • How does NR IR work in an embedded control application?

  • How to build an IR-based controller for LEGO IR RC trains.

  • Heltec’s HTIT-W8266 (WiFi-Kit-8)

  • Espressif Systems’ ESP 8266

  • M5Stack’s Atom module, I2C display, I2C joystick and IR transmitter

  • TSAL6200, IR LED and TSOP38538 receiver from Vishay

When we deal with communication, especially with wireless comms, we normally want it to reach as far as possible. That includes passing through walls and other objects. Infrared (IR) light is invisible to our eyes, but we can think of it as part of the visible light spectrum because it is reflected off objects just as the rainbow of colors we can see. (A portion of the IR spectrum overlaps with the visible spectrum.) As discussed in my article “How Gun-Style IR Thermometers Work (Part 1)” (Circuit Cellar 374, September, 2021) [1], the IR region of the spectrum is sub-divided into five parts. That series concerned non-contact temperature sensing at one end of the region—the long-wavelength IR (LIR). This project moves to near-wavelength IR (NIR), just below visible red in the spectrum.

You probably use this kind of IR every day, since most television/cable remotes use NIR. Radiated IR can fill a room by bouncing off the walls. Most hand-held remotes use an IR LED (940nm) as a transmitter. A sensor on the video equipment receives remote commands and decodes them into operations such as “change the channel” or “turn up the volume.” Each manufacturer has its own data format. I’ll be discussing one particular format, but this project has nothing to do with those remotes usually hiding between your couch cushions.

STANDARD IR COMPONENTS

When IR is used to communicate with equipment, external interference can totally scramble any data that is simply passed by turning the IR on and off by the data. Although IR filters can lower this interference, the best approach is to modulate the IR and use OOK (On Off Keying). In its simplest form, the presence of a carrier for a specific duration represents a binary 1, while its absence for the same duration represents a binary 0. An IR receiver module detects the modulation and outputs a logic level while the modulation exists. Typical modulation frequencies are 38kHz, 40kHz and 56kHz, with receivers pre-tuned to one of these frequencies.

For this month’s project, building an IR-based controller for LEGO IR remote-controlled trains, I’ll be using a 38kHz modulation frequency. For an IR LED, I’ve chosen to use the Vishay TSAL6200, and for the receiver, the Vishay TSOP38538 (Figure 1). The IR LED can handle 100mA of continuous forward current and 200mA modulated. The demodulating receiver requires only 6 cycles to identify the modulation. Using OOK, a minimum bit time would consist of 6 modulation cycles ON and 6 cycles OFF. The ON part of a bit is a minimum time of 6 × 1/38kHz or 6 × 26µs = 158µs. Thus, a minimum bit time could have an OFF time equal to the ON time, 316µs/bit. If we need to send binary data, then we will need to differentiate bits. If the minimum bit time represents a 0 bit, we could double the OFF time to indicate a 1 bit. In this case a 0 bit takes 316µs, and a 1 bit takes 474µs.

Figure 1 First prototypes used Heltec’s ESP8266 MCU with display, a Vishay TSAL6200 IR LED and a TSOP38538 IR demodulator to transmit and receive the LEGO Power Function (LPF) RC protocol.
Figure 1
First prototypes used Heltec’s ESP8266 MCU with display, a Vishay TSAL6200 IR LED and a TSOP38538 IR demodulator to transmit and receive the LEGO Power Function (LPF) RC protocol.

This works for binary data, but when does the data of interest start and end? Starting is not a real problem, because the modulation indicates the start of a bit. However, the last data bit can’t be determined because it has no modulation that marks the end the OFF time. This is the reason for a third OFF timing, anything greater than a 0 or a 1. We can call this the “start/stop” (S/S) bit. Its OFF time might be anything greater than 3 × 158µs! So, if the ON-OFF time exceeds 632µs, that’s a stop (or start) bit. The actual data length can be any number of bits. Normally, this is fixed by the data’s protocol.

LPF RC PROTOCOL

The LEGO Power Functions (LPF) RC protocol [2] is meant for controlling two LEGO motors—two pairs of I/O pins or any of the four I/O pins independently (Figure 2). While the term RC means “Radio Controlled,” in general, this can mean control via any wireless medium. A command packet contains 16 data bits surrounded by start and stop bits. The 16-bit payload is divided into 4 nibbles: Channel, Mode, Data and LRC. The LRC, or Longitudinal Redundancy Check is an exclusive XOR of the first 3 nibbles to provide some check on data integrity. The bit timings for 0, 1 and S/S are given in modulation cycles:

— ADVERTISMENT—

Advertise Here

“0” = 6 (ON) + 10(OFF) or 421µs
“1” = 6 (ON) + 21(OFF) or 711µs
S/S = 6 (ON) + 39(OFF) or 1184µs

Figure 2 Here is a typical LEGO handheld IR transmitter and the IR receiver, battery box and train motor that uses the LPF RC protocol.
Figure 2
Here is a typical LEGO handheld IR transmitter and the IR receiver, battery box and train motor that uses the LPF RC protocol.

Each command is sent out a minimum of five times, to overcome any interference from other transmitters. The system has accommodations for four separate transmitter/receiver pairs, each identified by its own channel number (1-4). To further reduce interference, each channel has its own delay parameters, which are implemented before each of the five command transmissions. You may have four vehicles using the system at the same time. The transmission spacing allows at least one of the five transmissions will get through correctly, no matter how many are transmitting.

There are only four commands listed in V1.2 of the protocol specifications:

Extended Mode—some extended mode functions

Combo Direct Mode—float, brake, forward, backward for both motors A and B

Single Output Mode—Set PWM or Clear, Set, Toggle of a pin or Inc/Dec PWM of A or B

Combo PWM Mode—Set PWM for both motors A and B

You may find you only need the Single Output Mode to control any function. For this project we’ll center on this command. If you would like to review the other commands, download and review the LEGO document [2].

As noted earlier, four output bits are associated with a LEGO IR receiver. These are separated into two pairs, A and B. Each pair’s outputs are labeled C1 and C2, and meant for a DC motor. These form an H-Bridge, and can supply the current necessary for LEGO motors to run both forward and backward. PWM values control speed and direction. Each pin can also be addressed separately using Set/Clear/Toggle commands. They can be used for LEDs or other functions.

— ADVERTISMENT—

Advertise Here

Since a command is transmitted at least five times, the first bit in the Channel nibble is called Toggle and is toggled on each new command. This allows a receiver to determine if a command is being repeated or is just part of the original multi-packet transmission. The second bit in the Channel nibble is the Escape bit, which determines if the command follows the Combo PWM Mode (1) format or is further defined by the Mode nibble (0). The last 2 bits of the Channel nibble determine which of the four channels the command is for. Usually, these bits are fixed by the channel selector slide switch on the transmitter, but we will take control over this.

When the Escape bit is a 1, the Mode nibble determines the PWM value for the channel B, and the Data nibble determines the PWM value for the channel A. When the Escape bit is a 0, the Mode nibble determines which of the other three commands will be used. The Mode nibble has an Address bit and 3 Mode bits. The Address bit will always be a 0 for our Single Output Mode command, with the Mode bits defined as shown in Listing 1.

For this project, using the Single Output Mode command, the Data nibble will operate on the A output when Mode bit0 = 0 and on the B output when Mode bit0 = 1. Mode bit1 determines whether the data is a PWM value or a discreet pin function. A PWM value determines the speed and direction of a motor (Listing 2).

Note that there are only eight discreet speeds for a motor, and the MSB (most significant bit) determines direction. Float is a non-driven output of the two pins, whereas Brake is a driven output of the two pins. When output pins are driven to the same logic level, the motor’s winding is actually shorted, preventing movement and acting as a brake. When the outputs are undriven (floating), the motor can free wheel.

When the Mode nibble’s other function is selected, you have a variety of other ways to control output pins. The functions shown in Listing 3 can affect the PWM values of an output pair, as in the Increment/Decrement functions, or individual pins, as in Clear, Set, Toggle. Note that you can do pretty much anything using this function of the Single Output Mode command.

Listing 1
The Mode nibble has an Address bit and 3 Mode bits. The Address bit will always be a 0 for our Single Output Mode command, with the Mode bits defined as shown here.

100 = Output A PWM mode
101 = Output B PWM mode
110 = Output A Set/Clear/Toggle/Inc/Dec mode
111 = Output B Set/Clear/Toggle/Inc/Dec mode
Listing 2
Mode bit 1 determines whether the data is a PWM value or a discreet pin function. A PWM value determines the speed and direction of a motor.

0000 = Float 
0001 = PWM forward step 1 
0010 = PWM forward step 2 
0011 = PWM forward step 3 
0100 = PWM forward step 4 
0101 = PWM forward step 5 
0110 = PWM forward step 6 
0111 = PWM forward step 7 
1000 = Brake then float 
1001 = PWM backward step 7 
1010 = PWM backward step 6 
1011 = PWM backward step 5 
1100 = PWM backward step 4 
1101 = PWM backward step 3 
1110 = PWM backward step 2 
1111 = PWM backward step 1
Listing 3
The functions shown in Listing 3 can affect the PWM values of a output pair, as in the Increment/Decrement functions, or individual pins, as in Clear, Set, Toggle.

0000 Toggle full forward (Stop → Fw, Fw → Stop, Bw → Fw) 
0001 Toggle direction 
0010 Increment numerical PWM 
0011 Decrement numerical PWM 
0100 Increment PWM 
0101 Decrement PWM 
0110 Full forward (timeout) 
0111 Full backward (timeout) 
1000 Toggle full forward/backward (default forward) 
1001 Clear C1 (negative logic – C1 high) 
1010 Set C1 (negative logic – C1 low) 
1011 Toggle C1 
1100 Clear C2 (negative logic – C2 high) 
1101 Set C2 (negative logic – C2 low) 
1110 Toggle C2 
1111 Toggle full backward (Stop → Bw, Bw → Stop, Fwd → Bw)
Listing 4
After initialization, the registers can be read by setting the register pointer to 0 and reading 6 bytes.

Wire.beginTransmission(addressI2C); 
Wire.write(0);
Wire.requestFrom(addressI2C,6); // number of bytes to read
for(int i=0; i<6; i++)
{
  CCRegisters[i] = Wire.read();
}
SENDING A COMMAND VIA IR

The IR LED is connected to an I/O pin. It will be turned on by a logic high on this pin. If you wish to supply more than ~20mA to the LED, you must use a transistor to drive the LED. This port pin is configured as an output. Since we are responsible for creating the modulation frequency (38kHz), the set up for the pin is:

int IROUT_pin = 15;
digitalWrite(IROUT_pin,LOW);
pinMode(IROUT_pin,OUTPUT);

Since sending an IR packet is of the highest priority in this application, we can time the IR pulse with simple delays, depending on the byte passed to the pulseIR() routine. You will note that the IR pin will be driven high and low to produce 6 cycles of 38,000Hz. This is the ON state and lasts 158µs. The time between ON states will determine whether the data being sent is an S/S, a 0 or a 1. When the byte = 0, the 0-bit delay is used, and OFF = 260µs. When the byte = 1, the 1-bit delay is used, and OFF = 546µs. When the byte = 2, the S/S bit delay is used, and OFF = 1014µs.

For each command packet, 18 bits are sent. An array of 18 bytes holds the delay value for each bit. For an increment the PWM value of Port A on the Channel 1 command, we set the Channel nibble = 0000, the Mode nibble = 0100, and the Data nibble = 0100. Then we can calculate the LRC as a packet check. We can calculate the LRC by XORing 0xF with the Channel Mode, and Data nibble values.

LRC = “1111” XOR “0000” XOR “0100” XOR “0100”
= “1111”

So, the 18-bit array will look like this: 200000100010011112. We send the packet with a “for. . .next” loop that reads the array and passes the array’s value to the pulseIR() routine (Figure 3). You may remember that the specs call out sending this packet five times with different delays between transmissions. Since the delays are different for each of the four channels, this attempts to create odd packet spacing, so multiple transmitters won’t cause interference on every packet.

Figure 3 My oscilloscope captures a command transmission. There are basically three pulse times, a 0 bit has an OFF time of 260µs, a 1 bit has an OFF time of 546µs, and anything OFF time 1,014µs or greater is a start/stop (S/S) bit.
Figure 3
My oscilloscope captures a command transmission. There are basically three pulse times, a 0 bit has an OFF time of 260µs, a 1 bit has an OFF time of 546µs, and anything OFF time 1,014µs or greater is a start/stop (S/S) bit.

The sendIRMessage()routine is responsible for selecting the right packet delays, depending on the channel number. The packet is sent five times with the proper delay before each packet. In total, the packets require approximately 16ms, and the delays require approximately 480ms for around 1/2 second per command.

In reality, for control of a LEGO train you need only Increment PWM, Decrement PWM and Brake. Since the IR receiver has two channels and we may only use one motor, we have a second channel we could use for other purposes. Most of the IR transmitters I’ve seen offer control of two motors, but not the individual bits of a port. The commands are in the protocol for allowing this. I think LEGO thought it would make the handheld too complicated. I will add Set and Clear commands for the Port A & B bits, C1 and C2. PWM and Set/Clear are all available via the same Single Output Mode command. My intent was to support all the commands, but I don’t want to drag this out by looking at each command. However, know that once you understand this framework, you can send any command you wish.

CONTROLLER CHOICE

There are not many controllers that you couldn’t use for this project. I began by using a Heltec HTIT-W8266, also known as WiFi-Kit-8. I liked the narrow, 0.6″-wide format with an onboard 128×32 OLED. For about $15, it’s an inexpensive workhorse that you can program with the Arduino IDE. With an Espressif Systems ESP 8266, it has Wi-Fi, which is not used for this month’s project, but could be useful in the future.

I’ve been looking for an excuse to use some products from M5Stack. This relatively new Chinese company based its products on the Espressif Systems ESP32. Not only are their prices reasonable, but also every micro and peripheral has an enclosure, which really neatens up a project. Although the ESP8266 began as a favorite of mine, I think the ESP32 with its twin core processor and support for Bluetooth and Wi-Fi make it a step up in “best bang for the buck.”

— ADVERTISMENT—

Advertise Here

Most of M5Stack’s peripherals are I2C and use Grove-style 4-pin connectors. I’ve chosen the M5Stack’s Atom as the base processor, with an I2C OLED display, an IR module that has both IR out and a demodulated IR in, and an I2C interface for my I2C Classic (Joystick) Controller. I’ve replaced the I2C joystick cable with a Grove connector for compatibility. Figure 4 shows this setup spread out on the bench. An Atom Motion module gives access to two ports, four servos, two motor drivers and a Li-Ion battery for portable use. The package reduces external requirements, even though I won’t be using the servo or motor drivers.

The flow chart in Figure 5 helps explain how these modules work together. Each of the peripheral devices, Display, Joystick and IR all connect to ports on the Atom. Each port has power (+5V/battery), ground, plus two I/Os. Since the display and the joystick use I2C, the ports are set to support I2C. The IR port uses the two I/Os as IROUT and IRIN, of which we only need IROUT.

Figure 4 The M5Stack system I used consists of the Atom module, an I2C display, an I2C joystick, an IR transmitter LED (which includes an IR demodulator that is not being used) and a Motion Module (which is just used for the Li-Ion battery and port expansion).
Figure 4
The M5Stack system I used consists of the Atom module, an I2C display, an I2C joystick, an IR transmitter LED (which includes an IR demodulator that is not being used) and a Motion Module (which is just used for the Li-Ion battery and port expansion).
Figure 5 Flow chart showing the simple routines I used for this IR transmitter application
Figure 5
Flow chart showing the simple routines I used for this IR transmitter application

I2C JOYSTICK AND DISPLAY

The joystick (Figure 6) has six registers. These registers hold the current position of each of the joystick buttons and joysticks. The registers are available in a packed format, shown in Table 1. After initialization, the registers can be read by setting the register pointer to 0 and reading 6 bytes (Listing 4). Next, these six registers are dissected, and each function is given its own variable.

Table 1 The joystick has six registers that hold the current position of each of the joystick buttons and joysticks. As shown here, the registers are available in a packed format.
Table 1
The joystick has six registers that hold the current position of each of the joystick buttons and joysticks. As shown here, the registers are available in a packed format.

Buttons become Boolean values, where 0 means the button is pressed. The four analog joysticks are saved as byte values. Note that the left analog values are 5 bits (0-63), and the right analog values are 4 bits (0-31). For this project, I’m using only the buttons. The left side of the joystick controls the train motors. The left and right buttons set which motor is selected (A/B). The up and down buttons select which channel is selected (1-4). The upper (LT) and lower (LZ) buttons on the front of the joystick send IncrementPWM and DecrementPWM commands. Additionally, the minus button sends a Brake command.

The joystick would be overkill if we wanted to control just a single train motor. However, its many buttons make it great for more complicated tasks. Without some feedback, it would be impossible to use this effectively. The two directional keypads allow this one transmitter to command many receivers. Not only do we have four channels (1-4), but also each channel has two motor outputs (A/B). By the protocol specs, that’s the extent of the possibilities. We’ll use the directional keypad to select these items, and the OLED display to keep track of this for you.

The display has a resolution of 128×64 pixels (Figure 7). That’s room for lots of text or graphics! It easily connects via I2C, and when initialized, it sets up its own I2C bus. Simple commands are available for locating text and choosing text size. When a selection key is pressed, the channel and port are updated on the display, so you always know which device your commands are going to.

Figure 6 The Wii classic controller I2C joystick is used as an input device, because it easily interfaces and has plenty of input buttons. All the buttons/functions are labeled.
Figure 6
The Wii classic controller I2C joystick is used as an input device, because it easily interfaces and has plenty of input buttons. All the buttons/functions are labeled.
Figure 7 The small I2C display can present enough feedback so a user always knows to which train and/or switch they are sending commands.
Figure 7
The small I2C display can present enough feedback so a user always knows to which train and/or switch they are sending commands.
EXTRA FUNCTION

Up to this point, I’ve discussed the left side of the joystick. A duplicate set of buttons on the right side is used to control switch tracks. LEGO makes only manually set switch tracks. A train heading into a switch can be directed either straight through or detoured off to the side. The position does not affect a train coming from the opposite direction. A spring mechanism will keep the train from being derailed if it is in the wrong position. This is unlike an actual railroad switch, which can derail a train if left in the wrong position.

A small lever sets the position of the switch. When the lever is open, the side track is not connected; and when closed, the side track is connected. Many enthusiasts have found ways of motorizing these switches, and as such, these also can be controlled via the IR transmitter. A popular way of controlling these is through a servo. Thus, the state of the switch can be changed by alternating between two servo angles. Since a servo can be controlled by a single bit, up to four servos can be controlled by each channel.

The right direction pad, which is labeled “xyab,” is used to select the channel (1-4) and output (1-4)—”y” (up) and “b” (down) select the channel number (1-4), and “x” (left) and “a” (right) select the output (A-C1, A-C2, and B-C1 , B-C2). Again, these selections are displayed, so you know which switch you will be controlling using the upper (RT) and lower (RZ) trigger switches. When a command is sent to either Set or Clear an output pin, it is shown on the display during the transmission.

USE IT

While we’ve not discussed the receiver, this can be used with the existing RC trains. Next time, in Part 2, we’ll look at receiving these commands and controlling a train and switch with some similar hardware. You’ll note that what is being discussed here is meant for use with the LEGO RC IR trains. However, if you wish to deviate from the LEGO IR protocol, the hardware exists in this project to do some alternate communication via Bluetooth or Wi-Fi. This is something for you to contemplate over the next month, in case you have any free time. As for me, there is entirely too much to learn, and so little time. 

RESOURCES

References:
[1] “How Gun-Style IR Thermometers Work (Part 1).” Circuit Cellar 374, September, 2021
[2] LEGO Power Functions RC, Version 1.20 specifications:
https://www.philohome.com/pf/LEGO_Power_Functions_RC_v120.pdf

Espressif Systems | www.espressif.com
Heltec Automation | https://heltec.org
M5Stack | www.m5stack.com
Vishay Intertechnology | www.vishay.com

PUBLISHED IN CIRCUIT CELLAR MAGAZINE • JANUARY 2022 #378 – Get a PDF of the issue

Keep up-to-date with our FREE Weekly Newsletter!

Don't miss out on upcoming issues of Circuit Cellar.


Note: We’ve made the May 2020 issue of Circuit Cellar available as a free sample issue. In it, you’ll find a rich variety of the kinds of articles and information that exemplify a typical issue of the current magazine.

Would you like to write for Circuit Cellar? We are always accepting articles/posts from the technical community. Get in touch with us and let's discuss your ideas.

Sponsor this Article
Website | + posts

Jeff Bachiochi (pronounced BAH-key-AH-key) has been writing for Circuit Cellar since 1988. His background includes product design and manufacturing. You can reach him at: jeff.bachiochi@imaginethatnow.com or at: www.imaginethatnow.com.

Supporting Companies

Upcoming Events


Copyright © KCK Media Corp.
All Rights Reserved

Copyright © 2022 KCK Media Corp.

Build an IR-Based LEGO Train Controller (Part 1)

by Jeff Bachiochi time to read: 15 min