6061

A DIY circuit to wirelessly drive the DFRobot Devastator tank.

img-20260525-223611.jpg

Introduction

As part of a wider project, I needed a way to easily control my DFRobot Devastator tank kit. As a fun challenge, I decided to build the circuit from scratch as a learning exercise. 

These were the requirements I set out at the start of the project:
1. High motor driving efficiency
2. Able to run off NiMH batteries
3. No ICs apart from the microcontroller
4. Wirelessly controlled
5. Can build on perfboard

Initial work

The DFRobot Devastator tank kit is a kit that lets you build a realistic small tank with decent electric motors. It's an excellent kit for electronics beginners, as it already does all of the mechanics for you - you just need the electronics. The tracks have a decent suspension (vertical swing-arm, like Christie suspension but with external springs) and the tank is actually able to traverse over minor obstacles and on unfavorable terrain. 

To design the electronics, we need to focus on the motor parameters. The motors have the following specifications (from the manufacturer's page):
  • Rated Voltage: 6 V
  • Operating Voltage Range 2~7.5V
  • Gear reduction ratio: 45:1
  • D output shaft diameter: 4 mm
  • No-load speed: 133 RPM @ 6 v
  • No-load current: 0.13 A
  • Locked-rotor torque: 4.5 kg.cm
  • Locked-rotor current: 2.3 A
To test whether the motors worked, I connected them first with an L293D, and I was able to move them in any direction. However, the L293D is an ancient IC and breaks my third rule. I wanted to learn about H-bridges, so knowing that I had a working reference to base my design off of, I set out to make one myself.

H-bridge control

With the requirement for a high driving efficiency, my main candidates were MOSFET-based H-bridges. One of the intentions behind no ICs was the idea that I want this to be able to be made by anyone, and I also wanted to use up my old stockpile of MOSFETs. An H-bridge uses a simple and elegant configuration to drive a motor that uses four switching devices to control the voltages at each pole of a motor. You make one pole a high voltage, the other one a low voltage/ground, and the motor turns in one direction. You can then reverse this configuration, and it turns in another. This diagram from Wikipedia shows it best:
1920px-H_bridge.svg.png
With motors, a commonly overlooked issue is the flyback voltage - when you switch the current flowing into a motor (for example, if you go from one switching configuration to the opposite), the motor releases its stored energy in the form of a "flyback voltage". Usually, you solve this with external diodes, but since I decided to use MOSFETs, the internal MOSFET body diode is suitable for such a small motor. If you're building this circuit with BJTs or even relays, you must put such a diode in the opposite direction of the current flow.

For the MOSFETs, I decided to have a bit of fun and make a kind of "H-bridge module" out of three SOIC-8-packaged MOSFETS - two single P-channel MOSFETs and one dual N-channel MOSFET. I chose this configuration since for the same size, P-channel MOSFETs usually have a higher resistance, so to be in the same class of on-resistance, you usually need P-channel to be larger than N-channel MOSFETs. This meant that I could fit all three packages on one SOIC-28-to-DIP-28 adapter. I also made a custom KiCAD symbol for this module explicitly labeling the sources, gates, drains, body diodes, and pairs of the module:
image-20260525211424.png


The driving circuit was one area that required some thought. As I chose no ICs, I didn't have the ability to use an external gate driver to drive P-channel MOSFETs. This led to an issue that many people reach in such circuits - your MCU outputs 3.3V to 0V. When you use any motor of a decently high voltage, the P-channel MOSFET needs to go up to the full supply voltage or even higher to turn off. If you try to control your MOSFET with the MCU pins, you can't actually turn it off.

One traditional solution is to use a gate driver, which allows you to treat the P-channel MOSFET like any other N-channel MOSFET. It handles the voltage translation for you and stands between your P-channel MOSFET and MCU pins. Most gate drivers also have a higher drive power, allowing you to have more precise control over turning your P-channel MOSFET on or off without staying in the linear region, resulting in lower power losses. This is great, but it's also an IC, which I didn't want to use.

The simpler alternative - which trades off performance for simplicity - is to use a small N-channel/NPN transistor with a pull-up resistor. When the MCU wants to turn off the P-channel MOSFET, it outputs a LOW/0V, the N-channel/NPN doesn't turn on so it's an open circuit, so through the pull-up resistor, the gate voltage is equal to the source voltage and the P-channel MOSFET is turned off. When the MCU wants to turn on the MOSFET, it outputs a high voltage, turns on the N-channel/NPN, this brings the gate of the P-channel MOSFET to ground, and so it turns on. This alternative is much slower to turn off because the MOSFET gate charges through a pull-up resistor, so it is not used in circuits where the P-channel MOSFET needs to switch often.

For the N-channel MOSFETs connected to the motor, I chose to drive them from the MCU pins through a 100 ohm resistor to protect the MCU pins from current spikes and ringing. Additionally, the N-channel MOSFETs have their gates connected to ground with a 10k resistor to avoid random voltages accidentally turning them on. In retrospect, the 100 ohm resistor may have been too high. I recommend replacing it with a 10-47 ohm resistor.

This is what the final schematic ended up looking like:
image-20260525213210.png
Note: you may substitute the MOSFETs with any MOSFETs of your choosing. Ensure that their on-resistance is relatively low, they are rated for the full nominal voltage of your voltage source, and the N-channel MOSFET is logic-level-compatible. In my case, the exact model numbers I used were:
1. AOSP21321 for the P-channel MOSFETs (rated 11A continuous)
2. AO9926B for the N-channel MOSFETs (rated 7.6A continuous, 1.8V minimum input logic level)
3. 2N3904 for the NPN
All of the MOSFETs comfortably exceeded both the current and voltage requirements for the motors. Yours should too.

Note 2: the voltage in the circuit is labeled as 9V since that is the absolute highest possible for my configuration of 6x NiMH cells in series. The NiMH voltage curve is very steep at high voltages, so the 9V (more commonly 8.4V) condition during max charge decreases down to 7.2V quickly. A compatible configuration would be 4x 1.5V alkaline cells or 2x Li-Ion cells.

Note 3: you can substitute the STM32 with any microcontroller you want. Initially, I was going to do this with a PIC16, but I wanted much more code and processing capacity to be able to make the algorithm more complex in the future. With the current configuration, even a PIC12 could probably do the job.

Control microcontroller

For the MCU, I wanted to use an old STM32F103 clone board I had laying around. These boards had a mistake in their design (the pin labeled PB8 on the board is actually BOOT0 and you need to connect this to 3.3V to program it), so I got them for 50 cents each on Aliexpress a while ago. This board and project are fully compatible with the STM32F103. I recommend using the official STM32F103 because you unlock the excellent official toolchain and product support, official STM32s are very price-competitive and they are of the highest quality.

Due to the asymmetry of my MOSFETs (P-channel turn on/off very slowly, N-channel rather quickly), I decided to implement a type of PWM control called "sign-magnitude control". This means that instead of switching both the P-channel and N-channel MOSFETs rapidly - as is traditional PWM - I turn on the P-channel MOSFET fully and switch the N-channel MOSFET only. This table shows the tradeoffs of this:
image-20260525214116.png

Note that in the sign-magnitude configuration, the P-channel body diodes do flyback protection, but that recirculates at every PWM cycle during the off-time. This is a regular diode with a 0.7-0.8V voltage drop, which is an inefficiency. This can be mitigated by placing Schottky diodes in parallel, and almost completely eliminated with synchronous rectification (or in other words, the full independent PWM variety).

The code is very standard - it just implements this type of control using the STM32's built-in PWM channels + timers. The PWM operates at 20kHz, which is a frequency just above the audible range, so the motors don't make an annoying sound when driven.

However, here I came into my first major roadblock - I wanted to have a decoupled system where the MCU could either be controlled through a local wired interface or a wireless one. I needed to implement both a second microcontroller with wireless capabilities and a protocol to safely communicate between them. Neither of these are trivial issues - wireless control is a notoriously tricky problem and communication protocols with vehicles always carry the risk of the communication breaking and the vehicle continuing to move and being unable to stop or receiving a malformed command and moving in an incorrect direction. For simplicity, I decided to use UART as the physical layer between the microcontrollers and designed a protocol on the levels above.

My custom designed protocol implemented two major fixes to the aforementioned issues:
1. The secondary microcontroller had to send a heartbeat signal every 500ms. If it didn't, the motors would be shut off. This meant that if the secondary microcontroller shut off and the motors were moving, they wouldn't continue moving after communication stopped.
2. The microcontrollers had to communicate with synchronization bits so that each packet begins with a sync/preamble byte for framing, followed by the payload and a CRC. Essentially, this made it so that the primary microcontroller would be certain that it received what the secondary microcontroller intended to send. If either of these checks failed, the motors would stop.

The choice of the secondary microcontroller was limited to the wireless controller I had available - the wonderful DualShock 4. Despite Sony never publishing information about its operation, the BluePad32 project (https://github.com/ricardoquesada/bluepad32) reverse-engineered all of the necessary code to allow a standard ESP32 to connect and fully read from the DualShock 4 (as well as many other controllers). Pairing to and reading from the controller was trivial. I implemented the custom protocol on the ESP32 end and connected it to the STM32. It worked from the first try. All code is included in this project.

Note: I ported the STM32 code from PlatformIO to STM32CubeIDE and am publishing the code for STM32CubeIDE since I believe most users prefer that workflow and would prefer to have an original MCU. 

Note 2: I use the standard original ESP32(-WROOM) DevKit as my ESP32 board. The Arduino library lists all of the supported variants.

Assembly and soldering

I wanted the project to be easy to DIY as a weekend project, so I didn't want to use a separate PCB and liked the look and feel of perfboard. I used standard 9x15cm perfboard that you can find cheap in many hobby electronics shops. For soldering, I used a Xytronic LF-389D soldering station with the excellent Felder ISO-Core "Clear" Sn100Ni+ solder. I cannot recommend this solder enough! It's better than any leaded solder I've ever tried. 

The soldering process was nothing special. I put on a documentary in the background and followed the principles of making a good, thick ground and voltage rail, minimizing length and using resistor legs to jump over most of the board. I mounted the MCU board on female pin headers to be able to remove it later.
img-20260525-202117.jpg

I used JST-XH connectors for all battery connections (because it's locked and can only be plugged in one direction) and regular pin headers for the motors (to allow for reversing physically). I got both from a Lidl crimping kit, which I sincerely recommend.

For the battery pack, I made a separate board where I connected individual AA battery holders in series and added a 1000uF capacitor to smooth voltage drops when the motor switches direction.
img-20260525-202103.jpg

3D-printed mounting shrouds

Since the perfboards were just sitting loose inside of the tank chassis, I decided to print a mounting shroud for them. The shroud would just screw onto the chassis bottom and then the perfboards would get screwed on the shroud. As a challenge, I decided to design it without looking at any existing DFRobot Devastator mechanical drawings. The way I did it is simple - I took the mounting surface, put a piece of paper below it, and sketched the outline. Then, I scanned the paper and used it as a template in FreeCAD. Then, I just exported as .3mf and printed the shroud with my Bambu Lab A1. This is the bottom template and it worked out well:
image-20260525222510.png

Since there was no space for the ESP32, and it won't be permanently in the design, I designed a separate shroud to sit on the top of the tank. I made an oversight in the printing - the thin and tall features failed to print after a certain height, so I canceled the upper vertical half of the print, but the structural part of shroud had already printed. The design should be revised. If you print this, print up until half of the height or even less. Nevertheless, the bottom part of the top mounting shroud works well:
image-20260525222834.png

The screw holes of the DFRobot Devastator are all 3mm. I used Torx screws with a wide head and the print's holes are nominally 3mm, which in my experience allows for an adequately strong connection. This is what the top came out looking like:
img-20260525-202226.jpg
The large black rectangle is a 10000mAh battery bank for the ESP32. I found that connecting the ESP32's input voltage to the batteries made its regulator heat up too much and turn off, while connecting it to the primary MCU board's voltage regulator made that board's regulator heat up too much and turn off. Again, since this is a temporary addition, I was fine with having this separate battery.

Observations

Measuring with an infrared temperature sensor, the MOSFETs never went above ambient temperature. Measuring with a bench power supply, in standard straight-line motion, the combined current consumption of the motors at 6V was ~0.4A - comfortably lower than the 2.3A stall current, which is when the motor is stuck.

Conclusion

I really enjoyed this project because of the artificial constraints. I feel like when working professionally, it's too easy to get used to the infinite comforts of quality, perfect PCBs, complex ICs that do everything in a tiny package, battery packs with immense burst power, and protocols with hardware data integrity. It's sometimes nice to go back to the basics and have to design an H-bridge from scratch, write a custom protocol, and then have to solder it all manually. When that ends up working, it feels so much more rewarding as a result!