Tank Rover: Arduino and Behind-The-Scenes
In my robotics post I had showed a tank rover that I had built, which comes in two variants—an all-Lego one with limited signal range and driving power, and an Arduino variant with enhanced characteristics. Building the Arduino variant was a thoroughly refreshing experience, and so I thought I would share the journey here. These behind-the-scenes details might be helpful for those attempting similar builds, too.
There's a lot of stuff to write about, and so I will split this post into two main portions—the physical technicalities, and the programming aspects.
I used Arduino Uno R3 microcontrollers for my tank rover, but all components are compatible with other modern Arduino variants. A list of the components involved, besides the usual necessities, is drawn up below.
HC-12 Radio Transceivers
The HC-12 transceiver is a conveniently-packaged half-duplex serial radio communication module, capable of transmitting up to a kilometer in the 433-473 MHz frequency range. It is very much a souped-up version of its cousin, the HC-05 Bluetooth module, and its performance is more than enough for our purposes.
There are five pins that have to be connected on the HC-12 board—the SET, RXD, TXD, GND, and VCC. Glancing over the technical details, the SET pin is hardly used except for passing commands to the board—to change bitrate or transmission frequency, for instance. The RXD and TXD pins carry the received data, and the data to be transmitted, respectively. And the VCC and GND pins carry power to and from the board. With the datasheet as a reference, we can draw up a schematic for the HC-12 as follows.
The datasheet advises that a 1N4007 diode be connected to the VCC pin if the HC-12 is powered with an input in excess of 4.5V; because we intend to use the 5V rail on the Arduino, we ought to include this diode. Its purpose is to avoid overheating of the on-board voltage regulator. Further, the datasheet also recommends that a 22 μF to 1 mF reservoir capacitor be connected in parallel to the VCC/GND pins of the HC-12, presumably to provide burst current when needed. I'd used a 100 μF capacitor in my circuit.
One last point to note—it is advisable that a power supply capable of at least 200 mA be used for the HC-12, according to the datasheet. With an Arduino, the current requirements might be in the 300 mA range. It is therefore a bad idea to attempt to power the entire thing with a single 9V battery. I'd tried it—and the HC-12 fails to transmit fairly rapidly. This is the reason for using a full pack of six AA batteries in my transmitter, as we'll see later.
We can prepare our HC-12 by soldering on wires to the respective pins. The diode can be soldered on directly, for compactness. We also solder the helical antenna to the board; and here we must be aware that the helical antenna that comes with the HC-12 is a normal-mode one, and hence for optimal effect we should mount them vertical, so that the position of its other counterpart is roughly perpendicular to its principal axis. The difference between a axial-mode and normal-mode antenna can be learnt at a glance by looking at its dimensions in comparison with the wavelength of its intended transmission; if its pitch and diameter is small as compared to the wavelength, it is most likely a normal-mode one.
A motor shield is needed to allow the Arduino to interface with motors. For very small motors it might be possible to power the motors directly using the pins of the Arduino—but one risks burning the Arduino out if the motor draws too much current. The motors used in the tank rover build are certainly more than capable of sending the Arduino to its grave if connected directly.
In this build I used the Adafruit Motor Shield V2, which sports four H-bridges capable of 1.2 A sustained current each, and has a dedicated PWM controller for supremely consistent speed control. And it is stackable, so in cases where one needs more than 4 independently-controlled DC motors, or 2 servos, one may simply pile the shields up. I digress a little here, but if anyone's interested in how H-bridges work and how to build a rudimentary one yourself, there's an excellent video by Afrotechmods on this subject. I'd thoroughly enjoyed his videos since ages ago.
Installing the shield is as trivial as matching the pins and pushing it down on the Arduino. I intended to power the shield with an external supply, and not with the Arduino's 5V rail—which almost certainly cannot deliver the driving current required—and so I had to remove the power jumper on the board. Do remember this step if you're doing a similar build!
Here things get a little tricky. We have to interface between Lego's proprietary Power Functions protocol and the Arduino. The easiest way to do this is to modify a Power Functions connector, such that it can connect to the motor shield on one end, and to the motor on the other.
We see that there are four separate lines on a standard Power Functions connector. The technical details of the Power Functions protocol and the purpose of these lines is extensively detailed on this website by Philo; but all we need to know for our purposes is that two of them (+9V and GND lines) distributes power to chained elements, and the other two (C1 and C2 lines) serve as directional controls.
There are some things to clarify here. Calling the +9V and GND lines powerlines would be misleading, for they do not provide power to Power Functions DC motors. In fact the motors do not connect to these lines at all. The +9V and GND lines do, however, provide power to elements such as the IR Receiver, which directs power to C1 and C2 lines as instructed by the received signal. In a way they are general-purpose power distribution elements, meant to support more complex components. To power our motors we therefore need only connect the C1 and C2 lines to the motor shield; but to make our modified connector compatible with other elements, we choose to power all four lines instead, with the +9V and C1 lines, and the GND and C2 lines, in parallel.
We begin by snipping an extension wire into half—one can use any Power Function element for this, but using an extension wire gives two modified connector per expended element, a better deal—and stripping the ends of the two parallel lines we wish to connect. Because we might be passing considerable amounts of current to the motors, I opted to use a 20 AWG wire for the splint. We then solder the wires together and insulate with either electrical tape or heat-shrink tubing, and repeat for the next pair of lines.
And here's a schematic of how everything hooks up on the tank. A picture of the physical setup is also shown—essentially a semi-disassembled version of the tank rover. By design the supports for the Arduino and the HC-12 can be removed from the rest of the tank as shown, facilitating the routing of wires.
There needs to be some way of accepting user input on the remote controller—and of course using joysticks is a common method. There are analog joysticks modules available, but the ones I ordered would take some time to arrive; and so I went with rudimentary tactile switches instead. Perhaps I'll use the joysticks in a later project.
The idea is that a signal line would be powered low when a switch is in its unpressed state, and would be powered high when a switch is pressed. The Arduino would then be able to sense this potential on one of its digital pins, and inform the HC-12 to send instructions accordingly. The schematic for this is shown below, with RR, RF, LR, and LF denoting the push-buttons for right tread forward, right tread reverse, left tread forward, and left tread reverse respectively.
We have placed 1 kΩ resistors on each switch branch to limit the current drawn from the Arduino. Resistors of greater value would also work, and might indeed work better, for it puts less strain on the Arduino. I built a Lego receptacle to hold everything together—CAD models and building instructions are available on my robotics page—and the complete physical setup looks something like this, partially disassembled to reveal hidden components. I had used a solderless breadboard, but if one desires more permanent fixtures, a perfboard or PCB can be used.
The idea for the transmitter is pretty simple indeed. The Arduino would sense which buttons on the breadboard are pressed, and broadcast an instruction—for our purposes a single character to represent the direction of each tread—via the HC-12. This broadcast can then be received by our rover at any arbitrary position and be interpreted accordingly. We have a few possible protocols to choose from.
We can, of course, perform the detection and send the characters as fast as the Arduino can manage, which in essence gives us a theoretical almost-instantaneous response time and almost-zero latency. In practice, however, the HC-12 has a limited baud rate and a rather small buffer; the system would grind to a halt fairly quickly. The power consumption would also be unnecessarily enormous, if one managed to circumvent the above limitations somehow.
On the other extreme, we can broadcast instructions only when a change in button states is detected. For instance, we might send a character to indicate that the right tread is to change direction from forwards to reverse, and another character to indicate a change from reverse to forwards, and so on. Power consumption would be a minimum, and the baud rate and buffer limitations quickly become irrelevant. The problem with this protocol is that if signal is lost midway, say, when the tank drives behind a shielding metallic object, it would continue to move in accordance to its last-received instruction indefinitely. One can imagine that this might be dangerous for the rover. Reception failure can also happen by chance, and the rover would then be perceived as being unresponsive. This will not lead to a good user experience.
We go with a compromise. The transmitter shall perform button state detection and broadcast corresponding instructions with a certain rest period between cycles; this cycle delay can be adjusted to suit usage requirements and preferences. I had set mine to a 100 milliseconds.
We start by defining the pins and the characters we'd like to use as instructions. The characters can be assigned entirely arbitrarily, as long as the same set is used by the transmitter and receiver. We also define our cycle delay, and set up a software serial interface for the HC-12. The setup is done correspondingly—set the various pins as inputs, and launch the HC-12 serial.
And now, inside the loop, we read each button's state, and broadcast accordingly. We implement an additional measure that if there are conflicting commands—if both the forwards and reverse buttons are pressed for each tread—the tread shall be stopped. We wait for the specified delay before continuing with the next iteration.
And that's all for the transmitter. To test it out one can hook up a second Arduino with an HC-12 module attached to a computer, and listen to the broadcasts via serial port as the buttons are pressed. The download link for this program can be found on my robotics post.
On the receiver's end, the Arduino has to interpret the received broadcasts and command the motor shield to power the motors in corresponding fashion. Before attempting to program the Arduino with the motor shield, be sure to download the required library as outlined in the official guide. We declare the same set of instruction characters as used in the transmitter, and we also declare the pins and the software serial for the HC-12, as before. This time we also specify the left and right motors, and a couple of variables to keep track of the tread states.
We'll see the use of lastTime and timeout later. In the setup block we launch the HC-12 serial and the motor shield, and we set both motors to stop. We set their speeds to 255, but they will not run until they are commanded to later.
And in the loop we check continuously for received characters, and attempt to match them to our instruction set. If the received character matches then we command the motors to run; else we do nothing.
And lastly we implement a safety measure, to stop the rover if signal is lost, or if the receiver goes offline. Notice that whenever the Arduino receives a signal we set lastTime to the clock time; we will compare this last-known reception time to current clock time at each iteration, and if the timeout threshold has been reached, we will force the rover to stop. The rover remains responsive to newly acquired signals.
And that completes the code. The download link for this program can, once again, be found on my robotics post.
That's it for now. Until next time, goodbye!