Lizard: 3D Printed RC Race Car

by TRDB in Circuits > Remote Control

404 Views, 7 Favorites, 0 Comments

Lizard: 3D Printed RC Race Car

Lizard: 3D printed RC race car
Lizard front-side view.jpg
Lizard rear-side view.jpg
FEWRVUCMAGT4159.jpg
F8LS2XLM98IGG1V.jpg

I have always been fascinated by remote-controlled vehicles. Yet, to be quite honest, I was never a very skilled driver and always more eager to modify and tune them rather than mastering the perfect lap – with little success back in the day. Nowadays, I fully accredited my preferences and, therefore, switched to building RC cars on my own rather than purchasing a ready-to-run car, allowing me to quickly replace or modify parts in case of an accident or failure. Previously, I designed an RC tank as well as an overly complex all-wheel drive RC car. Both vehicles work decently, but they aren’t designed for fast driving but rather to bring complex mechanical concepts into operation. Hence, for this project, I decided to build something purely for driving: an on-road race car, designed to combine agility with robustness. However, to keep it challenging, I remained within the boundaries of 3D printing standard filaments: A 3D-printed RC car with fully customizable electronics that allow a plethora of tweaking the drive characteristics to match the pilot’s preferences.

Facts about this project

  1. Lizard is a race car that is almost fully 3D-printed, except for the wheels (TPU does not offer enough traction). The chassis as well as the powertrain are printed. Therefore, damaged parts can be replaced cheaply and quickly.
  2. Lizard is equipped with rear-wheel drive and an open differential. It uses independent suspension and double Cardan joints to transmit power to the wheels.
  3. The car and the remote control use custom electronics based on the ESP32 microcontroller, which allows virtually unlimited options for customizing the control and making use of telemetry data.
  4. The car includes sensors for battery voltage, current draw, temperature of motor and motor driver, and speed (motor rpm).
  5. Advanced control options: The remote control’s throttle input can either be directly converted to PWM duty cycle (open loop) or to closed-loop control either of speed or torque. Further, current limiting can be activated to protect the drivetrain from excessive stress.
  6. Advanced telemetry: a separate ESP32 receiver can be used to transmit Lizard’s sensor data to a laptop computer for a detailed analysis of the car’s characteristics.
  7. Lizard reaches a top speed of 48 km/h (GPS-verified) and a runtime of at least 30 min with a 5 Ah 3S LiPo battery.

The car has a length of 46.6 cm and a width of 31.2 cm. The body height at rest is around 11.3 cm (maximum height at the rear spoiler is 14.8 cm) with a ground clearance of 2.5 cm. Lizard’s wheelbase is 30 cm and its track width is 26.9 cm. Including the 5 Ah 3S LiPo battery, it has a weight of 3.4 kg with a distribution of 44 % on the front axle and 56 % on the rear axle. The weight distribution between left and right is balanced (less than 1 % difference between left and right). Lizard uses a gear ratio of ~4.86:1, which allows it to reach up to 48 km/h according to GPS (translating to 11.250 rpm of the motor under load).

Notably, most of the time spent on this project was not on the initial mechanical design or the electronics and programming, but on fine-tuning the car’s powertrain parts and wheel assembly to render it sturdy enough to withstand the forces from fast-paced driving as well as to allow it to handle the motor’s torque. The design iterations resulted in a comparably stable chassis (yet it is still 3D printed!), and I spent quite some time optimizing the signal stability of the remote control as connection losses at such high speeds typically result in severe crashes. This car may not be able to keep up with commercial RC cars, but it is a lot of fun to build and to drive. In particular, this car is great for everyone who is interested in tweaking and tuning an RC vehicle thanks to the fully customized electronics. Alternatively, if you don’t want to build your own electronics and remote control, you can only prepare the chassis and the drivetrain (steps 1 to 4) and use standard commercial RC components for controlling the car. Either way, I can highly recommend that you make use of this Instructable to build your own Lizard!

Disclaimer

This project includes high-power electronics, and the car reaches quite high speeds. Hence, this car is not suited as a toy for young kids. Building and driving this car must be done with caution to prevent harming you and others! In particular, the high-power density LiPo batteries used for modern RC cars harbor health risks in case of accidents or improper use, in addition to the general risk of losing control over an RC car.

Supplies

Making your own Lizard requires the following tools:

  1. FDM 3D printer capable of printing PETG and TPU
  2. 2.5 mm and 3 mm ball head hex keys
  3. Angle grinder, sandpaper
  4. Soldering iron, brass wool, desolder braid, flux pen
  5. multimeter, battery chargers (for AA NiMH and 3S LiPo), lab power supply
  6. Wire stripper, wire cutter, crimping pliers

Printing parts of Lizard

Most of the parts of Lizard are printed from PETG. Only some parts that require flexibility are made from TPU. All parts are printed with a layer height of 0.2 mm and, if not stated otherwise in the description of the different components, they are printed with 15 % infill. Parts that need to be printed from TPU are colored in black in the model and are labelled accordingly. The grey and green parts are printed from PETG. The parts are connected using M3 screws and either nuts or threaded inserts, as is shown in the 3D models in the different steps.

The print parts are provided in my GitHub account as .stp and .stl files so that you can either print them plug and play as an .stl, or you can customize tolerances and designs in the .stp files. The parts carry an identifier as a prefix in their name in the style of x-yy-z, where x denotes the step the part belongs to, yy is the consecutive number of the parts within a step, and z indicates how many times a part must be printed.

All parts are designed so that they fit in the desired way (tight or loose fit) with the default print settings of a Prusa i3 MK3S. For most parts, the orientation on the print bed follows the general concept of FDM printing: Round parts are printed upwards to allow truly round shapes. However, the part strength is superior along the print lines compared with the z direction. Therefore, parts like the connectors between the gears and the plungers of the shock absorbers are printed horizontally. Additional information on printing is provided in the different steps. Notably, to keep this Instructable short, print and assembly information is kept brief. If something is unclear, please leave a comment and I’ll add further explanations.

Identification of metal parts and BOM

The number and size of the required screws, nuts, bearings, and steel rods are provided in the different steps of this Instructable, and their location within the components can be checked in the CAD view of each step. If the embedded view isn’t large enough, click on the header of the CAD model, which allows you to open it in a new tab at full window size. The CAD model of the full assembly of the car can be found at the bottom of the Instructable (Conclusion).

Below is the bill of materials for Lizard. Notably, this DIY RC car is significantly more expensive than a standard commercial RC car. One of the cost drivers is the fact that many parts can’t be purchased cheaply when only a small amount is needed (like a lot of parts for electronics), or that they can only be purchased in larger quantities than needed (like the screws). On the other hand, this car provides a lot more additional software and telemetry options than a cheap RC car out of the box. The BOM comes up to around 950 € if you do not have any of the required parts in stock. You can save a lot if you already have filament available (140 €), all the required wires, screws, and nuts (almost 180 €), and batteries (75 €).

Downloads

Main Body

Main body overview.png
Main body bottom.png
Main body top.png
Main body front.png
Main body rear.png
Main body battery holder.png
20250430_224527.jpg
20250430_224915.jpg

The main body is not only the anchor point for the electronics, the motor, and the wheels, but it also determines the car’s overall stability. Therefore, one of the main design choices for the body is to either keep it easily accessible (typically at the cost of reduced stiffness), or to design it stably, but at the cost of less simple maintenance. Notably, many DIY RC cars omit this trade-off completely by only building a chassis and omitting the car’s body. This approach has the obvious advantage of saving material and print time. On the other hand, the car’s components are then exposed to dust and dirt.

Thus, I tried to combine the best out of these considerations by designing a race car that doesn’t need a separate body: the chassis also serves as the body, thereby protecting the electronics, the battery, and the powertrain. Although replacing parts of the gearbox requires removing multiple screws, the most routine maintenance can be performed tool-free: Lizard’s battery is stored in a hatch that is held in place by magnets so that the battery can be swapped quickly.

Design considerations

The overall design is meant to resemble a race car, but you may notice that the ratio between wheelbase and wheel track (300 mm to 269 mm) is much smaller than that of real race cars, which profit from a long wheelbase since it supports a car’s stability at high velocity. Here, I did not want to increase the wheelbase any more to keep the car roughly in the 1:8 scale size, but I faced a restriction for reducing the wheel track: The printed differential consumes comparably much space, and the wishbones of the suspension required a certain length to allow the desired suspension travel. Hence, I ended with this compromise for the car’s dimensions, and as far as I can tell, it drives perfectly stable at its maximum speed.

The main body consists of a base plate that is used to mount the electronics and the motor/gearbox/differential unit. Further, spoilers are added in the front and on the rear. I assume that the car does not require much downforce to maintain traction at the speed it can reach, but some downforce can’t harm, and the spoilers also serve aesthetic purposes. The hood scoop, on the other hand, is form-follows-function: It is connected to an intake fan that pulls fresh air into the car to keep the motor and motor driver cool. Further, a second fan is installed internally in front of the motor to directly cool the motor, and the right body part allows hot air to exit the car’s interior. Active motor cooling is strongly recommended since the difference between motor and ambient temperature without cooling can exceed 50 °C, which may then soften the printed parts. On the other hand, the installed cooling capacity keeps the motor at less than 20 °C above ambient temperature.

Lights and battery hatch

Lizard has a single LED headlight, which is the front part of a small LED flashlight. I opted for this choice because flashlights direct the light toward the desired direction whereas a freestanding LED illuminates a much larger cone. The flashlight that I dismantled for this purpose is powered by two AAA batteries (designed for 3 V) and draws a bit less than 1 A at this voltage. You can check the LED’s current draw by running it from a lab bench power supply, and you can calculate the required series resistance using Ohm’s law (R = 0.3 V drop * current draw in A). Note that the PCB allows the installation of two resistors in parallel so that you can combine them to achieve the desired value. When using two resistors in parallel, the effective resistance Rtot can be calculated by 1/Rtot = 1/R1 + 1/ R2. Make sure to use resistors that are rated for the power that they need to dissipate (P = U*I).

The taillight consists of a thin red LED strip with a very high LED density (480 LEDs/m). The assembly of the taillight is tedious as wires must be soldered to the center of five LED strip segments (don’t forget to insulate the connections to prevent short circuits). The different LED strip segments are then connected in parallel (again by soldering) and are supplied with 5 V from a single connector on the car’s PCB. However, I believe the visual impression of the taillight is worth this work!

The battery of the car is stored in a hatch that uses M3 screws as a joint on the rear side and disc magnets in the front to secure it in place. The magnets are strong enough to keep the hatch closed while driving but allow the battery hatch to be opened without tools whenever needed. The battery cover also includes the name tag of the car, which is assembled by placing a color plate into the rectangular pocket of the battery holder’s top and welding the parts together at their seam with a soldering iron at around 240 °C.

Printing and assembly

Most parts are printed horizontally so that they are printed quickly and without the need of much support. The two sides of the car’s body must be printed with the outer sides facing upward to prevent warping of the parts. The front and rear spoilers as well as the hood scoop are printed with their front facing upwards. The spoilers don’t require support, but the brim should be activated to improve print bed adhesion. The hood scoop profits from organic support at the round cut-out for the fan’s air intake. The front spoilers are not fixed by screws but are pushed into the cut-outs of the car’s snout (part 1-15-1). When joining the car’s front, they are clamped in place by the 2nd snout component (part 1-14-1).

Required non-printed parts:

  1. 2x 50x50x15 mm 12 V axial fan
  2. 1x front part of 15 mm diameter LED flashlight
  3. 1x 100 mm, 2x 75 mm, and 2x 50 mm segments of red LED strip with 4 mm width
  4. 1x On/Off-Switch
  5. 1x WiFi antenna (length < 110 mm)
  6. 1x brushed DC motor driver
  7. 1x assembled Car PCB (see step 5)
  8. Screws and nuts: 23x M3x8 mm, 9x M3x10 mm, 8x M3x10 mm countersunk, 29x M3x12 mm, 2x M3x16 mm, 5x M3x20 mm, 4x M3x22 mm, 2x M3x50 mm, 77x M3 threaded insert, 34x M3 nut

Gearbox and Differential

Gearbox overview.png
Gearbox open.png
20250430_225534.jpg
20250430_225604(0).jpg

Lizard’s gearbox is designed as a single unit consisting of the motor mount, the gearbox, and the differential. This design was chosen to allow a change of the motor or the gear ratio without having to reprint the whole body of the car. The gearbox went through many design iterations as it is the part of the powertrain that is most prone to damage due to the acting forces when accelerating and driving at high speeds.

Designing a reliable gearbox and differential

The final gearbox setup consists of herringbone gears, which allow a more silent operation than standard spur gears. All gears are built with a height of at least 15 mm to distribute the force over a large area. Further, the smallest gear size in the gearbox is 15 teeth (module 1), and the number of teeth increases toward the output of the gearbox, which helps with reducing the load per tooth compared with smaller gears. The overall gear reduction ratio is ~4.86:1. This odd ratio stems from another means to improve the durability of the gearbox: Each set of gears has a tooth combination without common denominator (15-34, 21-26, 26-45), which distributes the wear between the teeth as equally as possible (even tooth counts would result in pairs of teeth grinding each other). Also, the gears are connected with TPU linkers instead of stiff PETG that can act as torque dampers to absorb abrupt speed changes between the wheels and the motor. Finally, the car’s electronics can use current limiting for the motor to prevent excessive torque.

All these measures were taken to increase the lifetime of the gearbox. Yet, the gearbox is still prone to being damaged, especially when drifting. This durability issue stems from the comparably weak plastic gears in combination with the soft rubber tires that offer excellent traction. If the tires offered less traction, the maximum torque that the powertrain must withstand until tire slipping occurs would be lower. On the other hand, more durable gears made from high-performance filaments or even metal could easily withstand the occurring torque in this setup. However, I wanted to stick with standard filaments for the whole car, which is why I opted for the abovementioned measures to strengthen Lizard’s powertrain. Another reason for the weakness of the gearbox is the general layout of the car: The wheels have a comparably large diameter of around 11 cm, which means that accelerating requires more torque than smaller wheels would need.

Lizard employs a standard open differential between the two rear wheels, which improves traction in turns compared to a locked differential. The differential uses four bevel gears between the output shafts to distribute the load as well as possible, and it is quite durable (so far, I only had one failure of the differential, and that was before I switched from two to four bevel gears).

Driveshafts with double Cardan joints

The power transmission from the gearbox unit to the wheels is enabled by double Cardan joints. These are an uncommon choice for RC cars, which usually employ constant velocity driveshafts such as dogbone joints. The advantage of constant velocity joints is that the input and output rotational speeds are equal, irrespectively of the angle of the car’s wishbones. That is not necessarily the case for Cardan joints, which cause a sinusoidal distortion in rotational speed between input and output, which becomes worse with increasing angle of the joint. However, when using double Cardan joints, as used here, the velocity-distorting effects of both Cardan joints cancel each other out, thereby resulting in a constant velocity transmission.

Notably, the geometry of the wheel assembly of Lizard (a short arm long arm double wishbone arrangement) results in a small deviation from this perfect constant velocity: When moving a wheel up and down, the camber angle of the wheel changes slightly (up to around ±3°). Nonetheless, I chose the double Cardan joint design for the driveshafts, which allows all motions to be supported by ball bearings, thereby minimizing friction-induced wear. When considering how the cups of printed dogbone joints can suffer from wear, the non-ideal constant velocity driveshaft appears more appealing to me than a quickly degrading driveshaft.

The driveshafts include a TPU-based torque damper, which is another means to improve the lifetime of the powertrain. Abrupt speed changes between a wheel and the overall differential’s output can be absorbed by this component, which reduces the stress on the gears. This linker between the input and the output of the driveshaft can also serve as a predetermined breaking point: This part can be reprinted and replaced more quickly than one of the gears inside the gearbox, and by reducing the diameter of the torque damper on purpose, it can be designed to fail first.

Measuring motor rpm

Lizard is supposed to measure its speed by determining the motor rpm. To this end, various types of sensors can be used, the most common being Hall effect sensors installed directly at the motor. These sensors not only allow the measurement of rotational speed but also the direction of the rotation, which is, for example, used to precisely control the speed and position of large servo motors. Here, however, only the rotational speed is needed, which is why I opted for a simpler optical sensor. A photoelectric switch is used together with a sensor wheel, and the microcontroller counts the pulses per time to calculate the motor’s rpm. This setup needs to be balanced between measurement resolution (more teeth on the sensor wheels results in a more precise measurement) and a reliable measurement (less teeth on the sensor wheel means the microcontroller is less often interrupted in its main loop).

The rpm measurement setup of Lizard uses a 20-teeth sensor wheel, and the microcontroller’s code accumulates sensor reads for 25 ms intervals to determine the motor’s rpm. Consequently, the resolution equals 120 rpm per count of the sensor (counts/20 to get the number of revolutions, multiplied by 60*1000/25 to convert the measurement interval into minutes). This setup results in a maximum interrupt frequency of around 4 kHz (100 counts per 25 ms at 12.000 rpm). The ESP32 is supposed to support even more than this interrupt frequency, but a higher accuracy is not needed. Therefore, to avoid any errors in the speed measurement, I did not attempt triggering the interrupt at higher frequencies.

Notably, the speed measurement is very precise, with a deviation between the internal tachometer based on the rpm sensor and external measurements using a GPS tracker of only around 1 % (see step 7). If you encounter a larger deviation or nonsense readouts from the speed sensor, you should check the alignment of sensor wheel and photoelectric switch and consider replacing the sensor with another model as yours may not support such high pulse rates.

Printing and assembly

The gearbox requires comparably little time for printing but more time than all the other steps for preparing non-printed parts. The gears are built around metal shafts to prevent them from bending under load, which is a typical cause for broken printed gears. Hence, 3 mm steel bars must be cut to the right length with an angle grinder to build shafts for the gearbox, the differential, the driveshafts, and the Cardan joints. Also, some of the parts for the gearbox need to be printed with 100 % infill to render them as sturdy as possible. All the gears and the TPU-based linkers between the gears and the Cardan joints shall be printed solid. For the Cardan joints, increasing the number of walls from two (default) to four is recommended, which adds more strength to them, too.

The motor gear is mounted to the motor shaft by using one half of a claw clutch. This claw clutch uses a clamping hub that enables a higher clamping force than the typically used clamping screws that are pushed against the motor shaft. I switched to this concept since I was tired of tightening a clamping screw again and again. The motor is equipped with a cooling block to improve heat dissipation. For the installation of the cooling block, you need to remove the metal clamp (also known as a flux ring or torque ring) from the motor. This ring increases the motor’s torque and efficiency but reduces its max speed. Hence, you can also decide to keep the motor as it is to get some more torque, but then you should closely monitor the motor temperature.

The Cardan joint at the differential (part 2-21-2) is connected to the differential’s output shaft (part 2-18-2) by pushing the two parts into each other and securing them with a 1.5 mm² single-stranded copper wire, which prevents these parts from moving axially (analogously to a snap ring).

Required non-printed parts:

  1. 16x 3x8 mm steel rod
  2. 2x 3x16 mm steel rod
  3. 1x 3x35.5 mm steel rod
  4. 2x 3x38 mm steel rod
  5. 1x 3x49.5 mm steel rod
  6. 1x 3x52 mm steel rod
  7. 21x 683ZZ (3x7x3 mm) ball bearing
  8. 4x MR85ZZ (5x8x2.5 mm) ball bearing
  9. 4x 6802ZZ (15x24x5 mm) ball bearing
  10. 2x 6804ZZ (20x32x7 mm) ball bearing
  11. 4x 0303 (3x4.5x3 mm) sleeve bearing
  12. 1x claw clutch
  13. 1x 775 brushed DC motor
  14. 1x cooling block for 42 mm motor
  15. 1x photoelectric switch
  16. Screws and nuts: 2x M3x6 mm, 6x M3x8 mm, 5x M3x10 mm, 2x M3x25 mm, 9x M3x30 mm, 4x M3x35 mm, 8x M3 threaded insert, 10x M3 nut, 5x M3 locknut, 2x M4x8 mm

Rear Wheels

Rear wheels overview.png
Left wheel.png
20250510_135400.jpg

The rear wheels transmit the motor’s power, which means they are not only responsible for accelerating but also for slowing down the car. Unlike real cars, RC model cars typically stop by reducing the motor’s speed as they aren’t equipped with mechanical brakes. This concept results in an inherent challenge regarding the controllability of a vehicle. In a real car, the front wheels receive most of the braking force to avoid locking the rear wheels upon braking. If they lock (=lose grip) due to braking, the car turns around involuntarily. Therefore, tires with very good grip are needed for rear-wheel drive RC cars, and that is why Lizard uses commercial wheels instead of printed TPU tires.

Wishbone design considerations

The connection between the car’s main body and the wheels only consists of 3D printed parts, which require attention in the design. Firstly, the wishbones and shock absorbers are not directly attached to the wheel mount plates but with the help of mounting parts. The mounting parts are printed separately so that their layer orientation aligns with the load direction. Thus, the mounting points don’t break too easily. Further, the mounting parts for the wishbones are printed from TPU, which allows them to absorb impacts rather than shattering like PETG. However, the flexibility of TPU also results in poor rigidity of the parts, which can destroy any carefully designed wheel geometry (like camber and toe angle). Therefore, the wishbones are printed from PETG and only contain inserts at the mounting points that are made from TPU. This composite design tries to achieve a trade-off between stable wheel geometry and sufficient flexibility to survive impacts.

Notably, this compromise is not perfect, which is why each of the rear wheels has two shock absorbers (whereas the front wheels only require one shock absorber per wheel). The shock absorber cannot be mounted in the center of the wheel, since the driveshafts occupy this space. Mounting a single shock absorber shifted to the front or to the rear messes with the car’s toe angle because the TPU-based mounting parts add enough flexibility to allow a change in toe angle when imbalanced pressure acts on the wishbones. Therefore, two shock absorbers are installed on each rear wheel, with equal spacing relative to the wheel’s center, thereby resulting in balanced forces.

Lizard relies on short-arm long-arm double wishbone suspension, which is a type of independent suspension that tries to optimize traction when cornering at high speeds. In this configuration, the wheel’s camber angle becomes more negative when the shock absorber is compressed, which occurs in the outer wheels during a turn. This effect compensates for the negative effect of a car’s body roll during fast cornering onto the contact area between tire and tarmac by keeping the wheel more upright than if the camber angle would remain neutral. Lizard’s wishbone geometry is designed for a neutral camber angle by default, up to -2.1° when the shocks are fully compressed, and +2.4° when the shock absorbers are fully extended.

3D printed shock absorbers

The printed shocks include springs but no dampening element, which results in severely underdamped behavior of the shock absorbers. Consequently, Lizard is only suited to driving on plain tarmac because bumps aren’t perfectly absorbed but rather converted into an oscillating movement of the car’s body. Especially at high speeds, this behavior can lead to a loss of control. A commercial hydraulic shock absorber is superior – but the concept of Lizard is to keep it DIY as much as possible. The springs for Lizard’s shock absorbers should be rather stiff since the car is not meant for off-road driving. The rear shock absorbers use springs with a spring rate of 1.812 N/mm (half of the spring rate of the front shock absorbers, to account for the 2nd shock absorber at the rear wheels).

Printing and assembly

When printing the parts for the wheel assembly, the parts should be oriented on the print bed according to their expected load direction. The wheel mount plates (parts 3-01-1 and 3-02-1) can be printed flat on the print bed, but the mounting parts (shock mounts and wishbone mounts (parts 3-03-2, 3-04-2, 3-05-2, 3-06-2, 3-07-2, and 3-08-2) should be printed sideways so that the central screw that connects the wishbone/shock absorber is facing upwards. The wishbone inserts need to be printed in the same direction (parts 3-10-2, 3-13-2, and 3-14-2). The shock plungers (3-28-4) need to be printed flat on the print bed to allow them to withstand the acting forces during operation. The rest of the parts should be in the direction that allows fastest printing and/or minimum support.

Required non-printed parts

  1. 4x 683ZZ (3x7x3 mm) ball bearing
  2. 4x 6802ZZ (15x24x5 mm) ball bearing
  3. 12x 0306 (3x4.5x6 mm) sleeve bearing
  4. 4x 0X-DF1709 (1x10x31.6 mm) spring
  5. 2x Carson 1:8 buggy wheel
  6. Screws and nuts: 54x M3x8 mm, 12x M3x10 mm, 8x M3x12 mm, 16x M3x16 mm, 6x M3x18 mm, 8x M3x22 mm, 12x M3 threaded insert, 56x M3 nut, 26x M3 locknut

Front Wheels

Front wheels overview.png
Left wheel and steering mechanism.png
20250430_225008.jpg
20250430_225018.jpg

The front wheels are designed similarly to the rear wheels. However, instead of transmitting power, the front wheels are responsible for steering the car. Therefore, this step includes the steering mechanism. An important lesson that I learned while going through the design iterations of the steering assembly is the choice of the right servo: The strongest servo motor is useless if it has poor mechanics (gear backlash), poor electronics (lets the shaft move for an increment before it tries to fix its position), and slow speeds. For an on-road car like Lizard, a servo with around 80-90 N*cm is powerful enough. However, the speed should be better than 0.17 s/60° (at 5 V) to enable fast steering.

A strongly recommended add-on for the steering mechanism is a servo saver. This component is required to protect the steering servo in the case of accidents. The energy of an impact to a front wheel, together with the inertia of the servo’s mechanics, can result in destroying gears in the servo – even when hitting an obstacle at low speed and if the servo uses a metal gearbox (one steering servo of Lizard died like that). A servo saver prevents damage to the servo by uncoupling it from the steering assembly with a spring that can absorb the energy of such an impact. This spring is stiff enough to allow the servo to steer the car without noticeable additional backlash during normal operation, but it protects it in case of excessive forces, such as in an accident. Hence, the additional costs for a servo saver are a good investment (particularly when using a high-quality servo motor).

Wishbone design considerations

The wishbone assembly follows the rear wheels’ design closely, with the same short-arm long-arm double wishbone concept. However, for the front wheels, a caster angle of +10° was chosen to improve the stability of the car (the rear wheels have a neutral caster angle). Further, the steering axis follows the wishbone layout, which results in a steering axis inclination of 12.5°. This angle helps to move the turning point of each wheel below the tire instead of next to the tire (low, but still positive scrub radius) and it results in a negative camber angle for the outer wheel of a turn, which can improve the traction during cornering.

3D printed shock absorbers

The front wheels only use one shock absorber per wheel, which is possible because the shock absorber can be mounted centrally, preventing any imbalanced force distribution (as compared to the rear wheels). Since there is only one shock absorber, the chosen spring has a spring rate of 3.521 N/mm, roughly twice the spring rate of the rear shock absorbers, to match the stiffness of the rear wheels’ suspension. Notably, the shock absorbers do not include adjustment options to adjust the car’s body height, and the shock absorber is not only responsible for the correct resting position of the car but also determines the minimum and maximum travel of the wishbones. If you want to change the car’s clearance from ground, or the maximum suspension travel, you need to adjust the design of the shock absorber accordingly.

Steering mechanism

Lizard’s steering mechanism is a printed assembly that is meant to be sturdy and to keep the play of all joints minimal. For the joints that only need to transmit rotation along a single direction, this can be achieved by combining 3 mm steel rods and standard ball bearings. However, ball joints are needed at the tie rod ends to allow rotation along two directions, which is needed to allow steering of a wheel with independent suspension. To this end, I developed a 3D-printed ball joint design that adds the balls of the ball joints to the end of the tie rods, supported by metal screws, and that uses massive sockets that can withstand the acting forces without breaking.

The ball as well as the socket are each printed in two separate parts, which allows printing these components without support, resulting in parts with very tight tolerances and a good surface finish. Thus, the ball joints are very durable and have minimal backlash. So far, none of them have failed. Since the balls of these joints are screwed into the tie rods, the toe angle of the front wheels can be adjusted by adding washers between the tie rod and the lower end of the ball (preferentially on the outer end of the tie rod as this side can be (dis)assembled more easily). The toe angle should be slightly positive (minimal toe-in) at the resting height of the car to improve its stability at high speeds. It is important to adjust the toe angle at the resting height (fully assembled car, including battery) since there is some unwanted bump steer: The toe angle changes to more toe-in when the shocks are compressed, and more toe-out when the shocks are extended.

The combination of wishbone geometry and steering assembly is designed to obtain around 60 % Ackermann steering at resting height. Given that the toe angle changes when cornering due to bump steer, and assuming that the joints in the wheel assembly still allow some play, this adjustment likely does not affect the maneuverability of Lizard significantly. However, some value must be chosen in the design, and I opted for this gut-feeling based compromise between perfect Ackermann steering for best traction at slow turns and anti-Ackermann steering of high-speed race cars. The maximum possible steering angle of Lizard is between 30° and 35°, depending on the steering calibration in the remote control, which results in a turning circle of around 1.2 m.

Printing and assembly

The front wheels require the same printing setup as the rear wheels, meaning that the mounting parts of the wishbones as well as the TPU inserts in the wishbones need to be printed sideways to maximize their strength relative to the acting forces. The front wheel assembly requires fewer metal parts than the rear wheels, but a few 3 mm steel rods are still needed to support the steering mechanism. The steering mechanism is the trickiest part of the assembly: It must be assembled before installing the wishbones to the wheel mount plates, and only after completing the installation of the wishbones and the shock absorbers, the whole assembly can be fixed at the car’s body. The whole assembly is then attached to the bottom part of the main body and the servo motor should be installed after the wheel assembly. Finally, the top part of the main body can be added.

Required non-printed parts

  1. 2x 3x9.5 mm steel rod
  2. 2x 25.5 mm steel rod
  3. 9x 683ZZ (3x7x3 mm) ball bearing
  4. 4x 605 2RS (5x14x5 mm) ball bearing
  5. 4x 6802ZZ (15x24x5 mm) ball bearing
  6. 8x 0303 (3x4.5x3 mm) sleeve bearing
  7. 2x 0306 (3x4.5x6 mm) sleeve bearing
  8. 2x 0X-DF1957 (1.5x12.5x40.6 mm) spring
  9. 2x Carson 1:8 buggy wheel
  10. 1x 40x20 mm servo
  11. Screws and nuts: 2x M3x6 mm, 70x M3x8 mm, 26x M3x10 mm, 8x M3x12 mm, 16x M3x16 mm, 11x M3x18 mm,12x M3x25 mm, 32x M3 threaded insert, 80x M3 nut, 17x M3 locknut

Electronics

Drawing.png
Lizard PCB.png
Zwischenablage01.jpg
20250430_225055.jpg
20250430_225626.jpg

An RC car requires electronics to enable the wireless transmission of inputs and the translation of these inputs into actuator movements. For Lizard, the goal is to keep the car DIY. Thus, it doesn’t employ off-the-shelf RC transmitter-receiver systems but instead runs on custom electronics. It can be powered by commercial systems, too, but the custom solution offers the advantage of flexibility: The logic between receiving inputs from the remote control and powering the motors is fully customizable.

Circuit and component considerations

Lizard uses the ESP32 as a receiver for the remote-control inputs and as a controller for all its motors and lights. Therefore, the electronics are chosen to be as compatible as possible with this microcontroller. The peripherals communicate via I2C at 3.3 V, which avoids the necessity of a logic level shifter. Further, I tried to add as many electronic parts as possible onto the main PCB, except for the main motor driver, to keep everything sorted and compact.

Besides the ESP32 microcontroller, the circuit includes an ACS712 current sensor to measure the car’s current draw, a PCA9685 PWM driver to uncouple the steering servo control from the ESP32, an ADS1115 ADC to avoid having to use the crappy internal analog-digital converters of the ESP32, an optical speed sensor, and some MOSFETs to drive LEDs and fans.

The ADS1115 chip reads all analog sensor data of the car: It reads the battery voltage, the output of the current sensor, and the temperature of two NTC thermistors. Only the speed sensor data is directly collected by the ESP32 because it measures digital pulses instead of an analog signal. Thus, data of five sensors is available as telemetry data to the operator. Note that the PCB does not support the use of a higher voltage than supplied by a 3S LiPo battery (do not exceed 13 V).

On the output side, the PCB has connectors to a 3 V front LED, 5 V supply for the rear LED strip and the steering servo, and a 12 V output for cooling fans. The latter is powered directly by the battery (3S LiPo: between 11.1 and 12.6 V). 3.3 and 5 V are generated by DC-DC converters that are each capable of providing a continuous output of 2 A, which is more than enough for the connected loads. Note that the front LED output includes series resistors that are meant to drop the 3.3 V output of the DC-DC converter to 3 V for the LED, which have to be adjusted when using an LED with different current draw.

Assembly, soldering, and wiring

The main PCB is designed to allow soldering all parts by hand. As far as possible, I opted for easy-to-use THT components such as resistors and transistors. However, for some parts, only SMD components were available, like the PWM controller and the current sensor. These two ICs are the trickiest parts since they use small leads and tiny gaps between the leads. It is recommended to start with soldering these components onto the PCB to avoid having any obstacles on the PCB while soldering them, and if something goes wrong, only one part instead of a fully packed PCB is broken.

With only a soldering iron and no hotplate or oven at hands, soldering the tiny TSSOP and SOIC chips works best by first attaching two pins at the opposite ends of the two pin rows. Then, once the IC sits firmly on its desired position, the two rows of leads can be soldered onto the PCB. Here, you don’t need to be overly cautious to prevent solder bridges between the pins. Instead, you can remove excess solder afterwards by gently pushing a de-solder braid onto the pins with your solder iron and moving it along the pin row. Then, with a magnifying glass, you need to thoroughly check that each pin is connected to its pad on the PCB, and that there is no solder bridge left between any of the pins. Once you have completed soldering these two chips onto the PCB, the rest of the parts can be added without further problems.The PCB has mounting points for two optional resistors (I2C pullup). They only need to be populated if the ADS1115 breakout board does not include pullup resistors (typically 4.7 or 10 kOhm) between SDA/SCL and VCC.

The connections to the LEDs and the fans should be built with 0.25 mm² stranded wire, which allows the usage of the Molex SL connectors as indicated in the BOM. These connectors offer reverse polarity protection and have a lock to prevent them from disconnecting. If you can’t source them, you can also use standard 2.54 mm DuPont connectors (they use the same spacing). For the main motor, which draws more than 10 A, a larger wire diameter is needed. Here, I opted for 2.5 mm² stranded wire. The same wire is also used to connect the main PCB to the battery with an XT60 connector. This connector can be soldered most easily by plugging a male-female pair of connectors into each other prior to soldering. When connected, the gold plugs cannot move within the plastic housing during soldering (because the plastic of the connector may reach its melting point close to the plugs).

The two NTC thermistors need to be attached to the motor driver and the motor. Use a high-quality adhesive tape to glue them onto these components (to ensure that they do not fall of during operation). The thermistor for the motor driver can be placed on an empty area of the driver’s PCB, and the thermistor for the motor should be placed on its metal can at an area where it does not interfere with its cooling holes or the mounted cooling block. You can identify the two thermistors when the car and the remote control are online: by touching a thermistor, you should see an instant rise in its temperature, and you can swap the two thermistors (Temp1/Temp2) if motor and driver temperature are interchanged.

One adjustment in the code of the remote control and the car is recommended to improve the accuracy of the sensor data. The ACS712 current sensor may have a slight offset in its zero-current-draw output value. The car’s idle current draw should be around 0.1 to 0.15 A, with fans and LEDs switched off. If the readout in your remote control differs from this value, you can correct this readout by slightly increasing or decreasing the value of the “acs712” variable in both codes. You can also verify the current reading by powering the car from a lab bench power supply that displays the current draw.

Required parts

  1. 1x custom PCB
  2. 1x ESP32 development board, 1x ADS1115 breakout board with 2.54 mm pin sockets for mounting
  3. 1x PCA9685PW (TSSOP package), 1x ACS712 (30 A version; SOIC package)
  4. 1x Gaptec LC78_05_2.0, 1x Gaptec LC78_03_2.0 DC/DC converters
  5. 2x 100 µF (electrolytic), 4x 22 µF, 2x 10 µF (1x 16 V, 1x 50 V), 1x 1 µF, 2x 100 nF, 1x 1 nF (ceramic) capacitors
  6. 1x 1N5819 diode, 3x IRLZ44N transistor, 1x IRFP7430PBF transistor, 1x IMAXX H7810 fuse holder with IMAXX F7030 30A fuse
  7. 2x 100 kOhm, 2x 33 kOhm, 2x 22 kOhm, 3x 10 kOhm, 3x 1 kOhm, 1x 220 Ohm, 2x 1 Ohm resistors
  8. 1x Wago 2604-3104 connector block, 6x 2pin Molex SL socket, 3x 3pin Molex SL socket
  9. 2x NTC thermistor

Remote Control

Remote control overview.png
Remote control open.png
Drawing.png
PCB.png
Zwischenablage01.jpg
Remote control inputs.jpg
20250430_224230.jpg

Lizard’s remote control uses a very similar layout compared with the one for my previous project. It uses two custom-built joysticks since the commercially available cheap thumb sticks have a large discrepancy between the available signal angle versus the possible mechanical travel (they are maxed out after moving less than half the possible angle from the resting position). With the DIY joysticks, this problem can be completely circumvented, and the design offers a sturdy joystick with very little backlash.

The joysticks are based on two high-quality potentiometers with 6 mm shaft. This shaft must be shortened and grinded to form a double-D shape that equals the mating part of the joystick assembly. Don’t remove too much material when grinding the shafts since there must be no play between the joystick and the potentiometer. Also, pay attention to the orientation when grinding the potentiometers’ shafts, as they need to be flat at the correct angle. The potentiometer must be at its center position when the joystick is in its neutral position to make sure the full travel is available.

Improved energy efficiency with OLED displays

The remote control relies on the ESP32 as a microcontroller, equal to the car. Further, two 0.96” OLED displays are used to show telemetry data to the operator. These displays require significantly less energy than the LCD display that I used previously for remote controls. That’s because an OLED display does not require any backlight but only needs energy for pixels that are actively driven. In consequence, the remote control has a very long battery runtime as its power consumption is only 0.6 W (the last generation with its 2004 LCD display has a power draw of around 1.3 W). If at least 50 % of the capacity of the 2500 mAh NiMH batteries can be used, this power draw results in a runtime of more than 15 h. A disadvantage of the OLED displays is their very small size, but they make up for it by providing a lot of usable screen area.

I2C as a bottleneck

When programming the remote control, I encountered an issue regarding the components that are connected to the ESP32 using I2C. While this protocol is very easy to use, it is not the fastest data protocol available. My goal for the remote control was to send a data package every 20 ms to the car (50 Hz), which allows a lag-free real-time control of Lizard (at least no noticeable lag). However, with default settings, refreshing one of the 128x64 pixel displays requires around 28 ms, and reading a single input of the ADS1115 chip, which is required twice to read each joystick, adds another 9 ms. Hence, the desired transmission frequency to the car can’t be achieved.

However, there are tweaking options available that allow faster communication. Firstly, the data rate of the I2C protocol can be increased. The default clock rate for I2C when calling Adafruit’s SSD1306 library is 400 kHz, and the framerate can be improved by increasing this value. The highest value I could enter that resulted in an improved refresh rate was 1 MHz, which dropped the time to refresh a display from 28 to 13 ms. The same trick can be employed when reading the ADS1115. However, here, you need to be careful regarding the interference between the different libraries: When calling Adafruit’s SSD1306 library, you specify two I2C clock rates: One applies when communicating with the display and the other (second) one applies for the rest (like reading the ADS1115). Here, I opted for 400 kHz instead of the default 100 kHz, which reduces the time required to read one input from 9.1 to 8.5 ms. A bigger improvement can be achieved when increasing the sampling rate to the chip’s maximum (860 samples per second), which then results in approx. 1.6 ms for reading one channel.

With these adjustments, I was able to keep the ESP32’s loop time below 20 ms even when reading the two joysticks and updating a display, which allows the desired transmission rate of 50 Hz. Notably, my code is far from efficient, but all the calculations and floating-point variables don’t affect the loop time significantly. The main bottleneck is the serial connection to the ADC and the display.

Poor hardware choices

In retrospect, the choice of the analog-digital converter and of the OLED displays was not the best: The ADS1115 is a 16bit converter, which offers great accuracy, but at the cost of a rather low sampling rate (860/s, plus the additional time for readout via I2C). For instance, the similar ADS1015 has a reduced bit depth (12 bit) but provides up to 3.3k samples per second. This lower accuracy would not pose a problem (the data from the joysticks is anyway reduced to 8 bit within the code), but the faster sampling rate could improve the loop time of the ESP32. Equally, for the OLED displays, there are faster options available: If a connection via SPI instead of I2C is chosen, the achievable frames per second can apparently reach the three-digit range, whereas I could not exceed 75 fps.

Another hardware-related issue that caused problems is the voltage regulator that I used to generate the 3.3 V for the ESP32 and the I2C components. First, I chose the Gaptec LME78_03-1.0, which should be suited for this task. However, the remote control always had to be switched on twice with a short time between the two “starts” to boot the ESP32 correctly. After a frustrating search for the problem, I was finally able to solve this issue by switching to another DC-DC converter (a Traco TSR 1-2433E), which runs flawlessly. I’m still not sure why the Gaptec is not suited to power the remote control, but I’m happy that the other converter works as expected (a Recom R78 3.3-1.0 also works).

Printing and assembly

The remote-control parts aren’t overly stressed and can, therefore, be printed with standard settings. Notably, the wires of the components that are mounted in the top part of the RC’s body should be prepared long enough to allow removal of the cover and safely unplugging the connectors without stretching any of the wires. You don’t need to pay attention to the wiring of the joysticks: Left-right and forward-reverse are assigned in the calibration menu so that it doesn’t matter if VCC and GND are interchanged. If you switch them for the max speed poti, you can change if a faster maximum should be on the left or on the right side. Note that the two displays require different I2C addresses to operate. The default I2C address of the OLED display is 0x3C, and by adding a solder bridge between the center and the right solder pads of the I2C address select area on the rear side of its PCB, the address can be changed to 0x3D. Make sure to change the address of one of the two displays (I placed the 0x3D display on the left side). The PCB has mounting points for two optional resistors (I2C pullup). They only need to be populated if the ADS1115 breakout board does not include pullup resistors (typically 4.7 or 10 kOhm) between SDA/SCL and VCC.

Required non-printed parts

  1. 1x custom PCB
  2. 1x ESP32 development board, 1x ADS1115 breakout board, with 2.54 mm pin sockets for mounting
  3. 1x Traco 1-2433E DC/DC converter
  4. 2x 22 µF, 1x 10 µF, 1x 100 nF ceramic capacitors
  5. 4x IRLML 6244 transistors
  6. 1x 47 kOhm, 1x 22 kOhm resistors
  7. 2x 2pin, 3x 3pin, 6x 4pin Molex SL sockets
  8. Screws and nuts: 14x M3x6 mm, 4x M3x10 mm, 4x M3x16 mm, 6x M3x20 mm, 12x M3x22 mm, 6x M3x25 mm, 28x M3 threaded insert, 18x M3 nut

Software

20250430_223646.jpg
20250430_223626.jpg
20250430_223631.jpg
20250430_223637.jpg
20250430_223641.jpg
20250430_224053.jpg
Input interpolation.png
Steering response.png

Lizard’s code is written in a way that allows its usage with minimal modifications when building this project. The remote control includes calibration steps that allow the trimming of the joysticks and the adjustment of the steering angles. Also, many customization options are available to adjust the car’s control to the operator’s preferences, such as switching the response curve of the steering joystick from linear to exponential, adjusting the throttle input interpolation, or implementing a current limit. This step provides a brief overview of the remote control’s options. However, before diving into the matter, I want to acknowledge Adafruit, Wolles Elektronikkiste, StudioPieters, and RandomNerdTutorials, as the information they published was incredibly helpful in designing the code for this project.

Mandatory first steps

When the assembly of the car and the remote control are completed, the first required step is uploading the sketches onto the ESP32 microcontrollers. It is important that the car’s steering servo and motor driver are not connected to the main PCB when powering it up initially to avoid possible damage and injuries: Unexpected behavior of these motors can occur when powering a not-yet-programmed microcontroller. Uploading the code is done using the Arduino IDE software after adding the ESP32 boards manager package and selecting “NodeMCU-32S” as your microcontroller. It is important that you select the latest version of the 2nd generation (version 2.017) of Espressif Systems’ esp32 boards manager for these sketches: There have been changes in the way ESP-NOW and the LEDC function are called between generation 2 and 3, and my sketches are written based on the 2nd generation syntax.

After uploading the sketches, you can power on the remote control and the car. Note that the car won’t move prior to running the calibration of the joysticks and the steering – a safety precaution that prevents unexpected starts when the system doesn’t know the neutral position of the joysticks and the limits of the steering system yet. Press the lower right button of the remote control to enter its menu. You can move the cursor in the menu up and down with the two buttons on the left, and you can select entries with the lower right button.

Calibration of joysticks and steering

Select the joystick calibration first and follow the instructions in the menu (remove the USB connection from the ESP32 before starting the calibration because this alters the logic level voltage and, therefore, the readout values of the joysticks). First, you move the joysticks to their limits and then you adjust the tolerance, which is the interval around the neutral position that is interpreted as neutral. Increasing this interval avoids involuntary movements but also increases the required joystick travel before the car starts following a command. The default tolerance is 7.8 % of the measurement range. You should try to reduce this value until you notice that the steering/throttle noise results in movements at neutral joystick positions – and then slightly increase the tolerance to avoid this phenomenon (I operate the RC with a tolerance of 4.7 % for both joysticks). If the car moves backward when pushing the throttle joystick forward, you must swap the motor wires to correct the drive direction.

The next step is the steering calibration. This step requires a connection to the car, as you need to check the steering angle while performing this calibration. It is important that the servo horn is connected to the servo and the steering assembly with the servo being roughly in its center position. Slight deviations don’t matter, but if the servo is at one of its limits, initializing the servo during the steering calibration may damage the steering assembly. If you don’t have a servo tester available, you can disconnect the servo horn from the steering assembly when starting this step, check the position of the servo horn after starting the calibration, and correct its mounting position if needed. Then, you adjust the left and right limits of the steering. Make sure not to push the assembly into hard limits. The maximum steering angle should be around 30 to 35°. In the last step of this calibration, you adjust the center position, which is internally done by changing the left and right limits (center = (upper limit – lower limit)/2). Let the car run straight on an even surface after this calibration and check if it pulls to one side. If it does, change the center position accordingly by an increment and repeat this procedure until the steering system is centered.

Standard screens of the displays

The car is ready to run after these calibration steps. In the default pages of the remote control, you can see the transmitter and receiver voltage on the left screen. The transmitter battery voltage should always be higher than 6.6 V (1.1 V per cell); it’s time to recharge them when the voltage drops below 6.8 V. The car runs on a 3S LiPo, which is at 12.6 V when it is fully charged and at 11.1 V when it is time to recharge it. The car’s battery is detected as low when it is below 11.1 V in idle (3.7 V per cell) or if it drops below 10.2 V (3.4 V per cell) under load. Note that there is no safety shutdown of the car implemented when reaching the lower limit to avoid a crash when suddenly losing control over steering and throttle. Thus, always make sure that you do not discharge the battery too much to avoid damaging the cells.

The lower lines of the left screen show the selected motor controller (open-loop PWM, closed-loop torque, or closed-loop speed), the interpolation setting for the throttle (overdrive, responsive, or stagnant), the selected current limit for the motor, and you can see whether the lights are on or off. The throttle interpolation setting can be adjusted on the fly by pressing the upper left button. The better the traction (warm weather, dry tarmac), the more responsive the throttle can be. Thus, you can use overdrive in dry summer weather, but when the street is wet or if it is cold, you may switch to stagnant to avoid involuntary drifts when braking. The lights are switched on and off with the upper right button. The lower left button switches pages of the right OLED screen.

The right screen can show the car’s power draw with some statistics (average, maximum, and total energy consumption since last startup) or the speed, again with some statistics (average, maximum, and travelled distance since last startup). You can reset the maximum values of speed and power without having to switch it off and on within the menu when selecting the “counter reset” entry. The other available pages for the right screen are real-time information of the car’s sensors, an overview page of the control settings, and information on the signal status. The latter indicates if there are connection issues (if the status is worse than “excellent”) and shows how long the car has been connected to the remote control since the last startup.

Additional settings

The entries in the remote control’s menu allow the user to adjust additional settings. The steering mode (response curve of the steering joystick) can be adjusted between linear and exponential. More exponential behavior reduces the steering response at small angles of the joystick, which can improve controllability at high speeds. The motor current limit adjusts the permitted current draw while driving forward (basically acting as a torque limit). The motor controller changes the conversion from throttle input to motor PWM duty cycle between open-loop PWM, closed-loop torque, and closed-loop speed control.

Further, the light intensity for the headlight and the taillight, the fan control (automatic according to the temperatures of motor and motor driver, or a manual setting), and WiFi settings (WiFi protocol and channel) can be adjusted. Note that the car must be connected to the remote control to successfully change WiFi settings. If the remote control’s and the car’s selected WiFi channels aren’t identical, the signal stability is poor or they can’t connect at all. In that case, you can switch channels in the remote control until the car is connected again, then select your desired channel, and restart both the car and the remote control to reset both to the same channel.

Motor control options

Lizard’s throttle input can be translated into different motor responses. The first option is the most common concept: The throttle input is directly converted into a PWM duty cycle output for the motor driver, which is an open-loop motor controller without feedback loop. Yet, you can combine this controller with a current limit. In that case, the throttle input is limited in case the car’s current draw exceeds the specified limit. If the current limit is exceeded, a PI controller is applied that tries to keep the motor’s current draw at the set limit. This PWM control with a 20 A current limit is recommended for controlling Lizard as it is the most intuitive controller (see also step 8).

The other two motor control options are closed-loop control, either of torque (PI controller for current) or of speed (PID controller for motor rpm). The former is well suited for drag racing since it does not apply the otherwise employed input interpolation, but since the car can only brake by reducing the PWM duty cycle, it is counter-intuitive that the throttle joystick basically controls the acceleration but not the speed (unless you return to neutral or reverse). The latter controller is more intuitive since the throttle input is converted into the desired speed that the car tries to reach. However, since RC cars are frequently cornering, accelerating, and braking, this controller has a hard time reaching maintaining its setpoint.

Telemetry Data

PWM with 20 A current limit.jpg
PWM with 10 A current limit.jpg
Torque controller.jpg
Speed controller.jpg
GPS speed measurement.jpg
Acceleration data.png

The code of Lizard includes a snippet that sends telemetry data from the car if another ESP32 is linked as a receiver. Thus, in addition to the limited data evaluation of the remote control, you can read all relevant sensor information at a very high refresh rate and feed this data to a computer. The concept relies on the serial communication between the telemetry-receiver ESP32 and a computer, using PuTTY to save the data as a log file. Note that the code for the telemetry-receiver ESP32 needs to be adjusted manually to set the correct WiFi channel. Then, I used MATLAB to evaluate the data and display them in plots (the codes used for graphically displaying the results are available on GitHub), but you can also use any other software to do so (after converting the analog sensor data into the correct units). This data evaluation helped a lot in debugging the code since it is impossible to check if the interpolation of joystick inputs is converted reliably into a PWM duty cycle for the motor without checking the data in real-time.

Checking the different control options

The telemetry data shows that the current limit function in PWM duty cycle control works decently. This current limit function relies on comparably fast readouts of the current sensor (fast for an ADS1115, which is a rather slow ADC) of around 200 Hz, and employing the torque controller whenever the drawn current exceeds the specified threshold. This way, the current does not exceed the set limit by around 1-2 A until the motor’s speed is so high that the current limit doesn’t apply any longer anyway. The images show exemplary runs with a 10 and 20 A current limit.

When switching to the closed-loop torque controller, the same PI controller is applied, but not only when exceeding a threshold but continuously. Controlling the car with this method requires some time to get used to, and I can’t recommend it for anything else but drag racing. However, since I wanted to explore this control option, I’m satisfied that it does work as expected – although it is only of academic interest.

The speed controller of Lizard can also be evaluated using the telemetry option. Here, it can be observed that this full PID controller does not work as precise as the current controller, which can be explained by the reduced controller frequency (new speed values are only obtained at around 35 Hz) and the fact that there is a much slower response between adjusting the motor’s PWM duty cycle and a change in speed compared with a change in current consumption. Therefore, tuning this controller requires a compromise between reaching the desired setpoint quickly at the cost of overshooting or avoiding overshooting at the cost of taking more time to reach the setpoint.

Accuracy of the speed sensor

The remote control of Lizard uses the speed data to calculate the distance driven by the car. Upon driving the car for around 30 min with a GPS tracker, the total distance according to GPS was 9.2 km, while the distance calculated from the speed sensor resulted in 9.3 km. Therefore, the deviation from the GPS data is only around 1 %, meaning that the tachometer of Lizard is very accurate when assuming that the GPS data is correct. However, single data points from the tachometer are somewhat noisy, which is why the maximum speed readout between GPS and internal measurements shows a deviation of around 6 % (GPS: 48 km/h; tachometer: 50.9 km/h). This sensor noise can also be observed in the speed plots in the pictures above.

Evaluation of acceleration times

Telemetry data can also be used to measure acceleration data of Lizard. Here, I used the open loop PWM control with throttle interpolation set to “responsive” and adjusted the current limit option between 5 A and 30 A. The data shows that the car profits significantly from increasing the current limit between 5 A and 20 A: At a limit of 5 A, the car can’t even reach 40 km/h since this speed requires more power than the allowed approx. 60 W. When increasing the current limit from 10 A to 20 A, the acceleration time from 0 to 40 km/h almost drops by 50 % (5.0 vs. 2.8 s). Increasing the current limit even further does not improve the result since the car’s motor isn’t limited by current draw at high rpms, but it simply doesn’t have the power to accelerate even faster.

The situation is a bit different when checking the acceleration data up to 20 km/h. In this range, the motor only reaches less than 50 % of its maximum rpm. Hence, increasing the PWM duty cycle has a more noticeable effect on the motor’s current draw: The acceleration time improves from a 20 A current limit to no current limit by 25 % (1.2 s vs. 0.9 s), although this difference is close to measurement tolerances. Notably, the acceleration time from 0 to 10 km/h can’t be evaluated for this case due to the throttle input interpolation. The interpolation that is applied when pushing the joystick instantly from 0 to 255 results in a delay to full throttle of around 1 s. Consequently, this acceleration time is equal for all settings that allow enough current draw to not further delay the interpolated rise in PWM duty cycle. Operating the car with the torque controller could improve this acceleration time since the torque controller does not apply an input interpolation.

Summary

After tweaking the different control options and considering how the controllability of Lizard changes with the different controllers, I believe that the best way to drive Lizard is using the open-loop PWM controller with the addition of setting the current limit at 20 A, effectively turning this into a semi-closed-loop controller that allows fast accelerations without putting excessive strain onto the powertrain.

WiFi Troubleshooting

20250430_225210.jpg
20250430_225055.jpg

The ESP32 microcontroller with its built-in WiFi capability is a great basis for remote-controlled vehicles. However, using ESP-NOW on cheap development boards is certainly not as reliable as a commercial 2.4 GHz transmitter-receiver combination. Yet, if you want to keep the freedom to adjust and tune your remote control without the boundaries of proprietary products, you may still prefer the custom solution. Notably, there are various other options for custom remote-control solutions available, such as the often-used NRF24L01 transceiver chip, or by reading data with the IBUS protocol of FlySky receivers with an Arduino.

I wanted to stick with ESP-NOW since I am used to programming the ESP32, and it is very convenient to have all required functions available in a single microcontroller. Thus, I tried to optimize the data transmission stability of ESP-NOW as much as possible. Here, I want to share the optimization options that I found, with no guarantee that they will significantly improve the signal quality. Some of them are generally applicable to remote controlled vehicles, but some are also specific to ESP-NOW.

  1. Keep the antenna as far away from the motor as possible. Motors of RC cars abruptly draw large currents, which in turn produce magnetic fields that influence surrounding electronic components. The situation is even worse for brushed motors that produce sparks when the brushes scrub over the commutator. Hence, moving the electronics, and particularly the antenna, further away from these sources of electromagnetic fields minimizes the risk of interference. That’s why the antenna of Lizard is in its front, as far from the 775 motor as possible.
  2. Insulate the antenna cable with an electrically conducting metal foil, which acts as an electromagnetic field shielding. I used a simple aluminum duct tape that I wrapped around the antenna cable. Further, I wrapped layer of packaging tape over the aluminum tape to prevent it from causing electrical shorts if it touches the PCB.
  3. Reduce the brush noise of the motor. This trick is helpful for brushed DC motors. The operation of a brushed motor causes electromagnetic interference, which can be reduced by adding a ceramic capacitor between the two motor leads, and additionally, by ceramic capacitors between each of the motor leads and the metal housing of the motor. These capacitors should be rated for a significantly higher voltage than the motor supply voltage (mine are rated for 50 V). I used a 47 nF capacitor between the motor leads and 10 nF capacitors between the poles and the metal housing.
  4. Use developer boards with a socket for an external antenna instead of a PCB antenna. The standard ESP32 developer boards have PCB antennas, which simply do not offer the required range for a reliable long-distance remote control. It is crucial to use an external antenna to improve the achievable transmission range, both for the transmitter and the receiver.
  5. Make use of Espressif’s long range WiFi mode. ESP-NOW can be used with a proprietary data protocol, which is called 802.11LR (“long range”). This protocol reduces the data throughput (but not to an extent that it restricts the operation of the remote control) while improving the maximum possible distance.
  6. Use the broadcast mode instead of unicast when sending data with ESP-NOW. Most online tutorials explain how to set up ESP-NOW between two boards by sending data packets to a specific receiver. In this setup, which is called unicast, the sender waits for feedback from the receiver, telling it that the data was received. If this feedback is not received, the sender automatically retries sending this data packet. Yet, in a remote control, we don’t need that. If a data packet is lost, it is more important that the next packet is sent without further delay instead of retrying to send an old data packet. Consequently, you should opt for broadcast mode, which means the transmitter sends a data packet without specifying a receiver board by setting 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF as a receiver MAC address. Thus, the sender does not care (or know) if the packet is received by a listening device or not. A “motor stop on connection loss” safety routine can still be added in broadcast mode, but you can’t make use of a feedback signal from the receiver since there is none. Instead, in the case of Lizard, the receiver counts the time between received data packets. If the time exceeds a threshold of 1 s, the motors automatically stop. Notably, also in broadcast mode, you can have several ESP-NOW connections at the same location without interference: Whenever an ESP32 sends a data packet, it sends its own MAC address with this data packet. Consequently, by letting the receiver check the sender’s MAC address, you can make sure that the car only reacts to signals from the right remote control.
  7. Choose an empty WiFi channel: The 2.4 GHz WiFi frequency range is split into 13 channels, and WiFi networks can use different channels to avoid interference. WiFi routers usually automatically check for other networks and try to find a channel with minimal population to prevent interference. ESP-NOW does not send an SSID, which means that it can’t be seen when using WiFi scan apps on smartphones, but you can still manually switch the used WiFi channel for each ESP-NOW connection and, thereby, avoid conflicts with other devices.


Conclusion

Lizard assembly front.png
Lizard assembly front-side-top.png
Lizard assembly rear-side.png
Lizard assembly side.png

Lizard was a very challenging project. The goal of a reliable, high performance RC car that is based on 3D-printed parts is contradictory since printed parts aren’t famous for their stability. Therefore, compromises were required throughout the development of this car: When a small, lightweight car is combined with a powerful motor, there will be significant problems with traction, and the powertrain can likely not cope with the torque generated by the motor. Thus, you need to increase the powertrain’s stability, which renders the car bigger and heavier, thereby reducing its performance when keeping the same motor. However, a bigger motor would start this circle all over again. Further, I wanted to keep some prerequisites, such as supporting all powertrain parts with ball bearings, and using an open differential, which requires a certain size.

Therefore, compromises between size, weight, and strength are needed that result in a trade-off between fast acceleration, high top speed, controllability, and component lifetime. Lizard most likely cannot compete with a commercial RC car, probably in none of these criteria, but I believe that such a goal would not be realistic for a printed DIY car. This is also why I do not attempt to upgrade the brushed DC motor with a brushless motor. While brushless motors are much more powerful and can spin faster, this higher performance is accompanied by more strain on the powertrain that already struggles to support the 775 brushed DC motor.

Also, I realized by first-hand proof during the development of this project that improving the durability of one component does not necessarily make the overall system more durable. Instead, it may happen that you simply shift the breaking point (more or less intendedly) to a different component. Therefore, it can save a lot of time and effort when certain limitations are acknowledged and accounted for by designing cheap and easily accessible parts as intended breaking points.

In summary, I believe that this project is a success, and I did learn a lot on the way to the completion of this car. Lizard is fun to drive, and after many design iterations, it even reached a level of reliability that allows it to run for multiple hours under load without parts failing constantly. There are still damages to be expected every now and then, but it does hold up quite a while. Also, I’m happy with the remote control and the code that lets Lizard run. It took a while to get rid of the bugs in the code, but now, it offers a reliable wireless connection and all the freedom to the user to adjust it as needed.

Thank you for reading this Instructable! I hope you enjoyed it, and if you decide to build your own Lizard, I’m here to support you if you encounter any issues. I look forward to your feedback and questions, and please let me know if you found any mistakes, missing information, or unclear paragraphs in this text so that I can improve it.