Loading 3D model… large boards can take a moment.
This project is a custom-built remote-controlled (RC) car, designed from the ground up with custom electronics, PCB design, and embedded firmware for both the car and its remote.
This board is the car's real-time control core. It receives wireless throttle/steering commands from the handheld transmitter, converts steering targets into stable hardware PWM for the MG90S servo, and drives the rear motor through a protected H-bridge path with direction dead-time and command deadbands for smoother behavior. The result is a compact control system designed for responsive handling, predictable failsafe behavior, and reliable power delivery under changing load.
The car uses an nRF24L01+ module with the RF24 library over SPI to receive drive and steering commands from the handheld controller. In firmware, the radio object is created as RF24 radio(CE_PIN, CSN_PIN) with CE on PB0 and CSN on PB1.
The RF stack is configured in configureRFRadio() with:
radio.begin() hardware check (halts if radio is not detected)radio.setPALevel(RF24_PA_LOW) to reduce TX power/currentradio.enableDynamicPayloads()radio.setAutoAck(false)"jag-1" and "jag-2"radio.startListening() so this node stays in RX modeAn important part of the RF path is Timer0 timing configuration. Because this firmware includes #include <RF24.h>, the RF24 stack depends on millis()/micros() timing for operations such as CE pulse timing, transmit timing windows, retry timing, and transaction spacing.
The RF24 library itself does not directly configure Timer0. Instead, those timing primitives are provided by the Arduino core (millis()/micros()), and the project sets Timer0 for a 50 us timing base:
TCCR0A = (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);
TCCR0B = (1 << WGM02) | (1 << CS01); // prescaler = 8
OCR0A = 49;
At 8 MHz CPU clock with prescaler 8:
So the Timer0 event period is 50 us (20 kHz). Timer0 is intentionally kept running at this rate so the millis()/micros() time base used by RF24 remains available and stable.
The receiver expects a MotorControlPayload packet with two fields:
int16_t ocrMotor for signed throttle/drive commanduint16_t ocrSteering for servo pulse width command in microsecondsThis payload is 4 bytes total (2 + 2), and after reception the car firmware applies it directly:
OCR1B = payload.ocrSteering for steering positionpayload.ocrMotor drives H-bridge direction/speed logic with a deadband of -15 to +15Reliability/safety behavior tied to RF reception:
WDTO_2S)Steering is driven by an MG90S servo using Timer1 hardware PWM on OC1B (PD4). The car firmware configures Timer1 in Fast PWM mode 14 with ICR1 as TOP, prescaler = 8, and ICR1 = 20000. With an 8 MHz clock, that gives a 1 us timer tick and about a 50 Hz control period (20 ms), which matches standard hobby servo timing.
The commanded steering pulse is written directly to OCR1B. Steering values are transmitted as absolute pulse widths in microseconds. Instead of assuming a fixed joystick center, the controller now supports calibration by entering measured joystick potentiometer voltages for minimum, maximum, and center. Those measured values are used to map ADC input to the steering pulse range and produce a more accurate neutral point (typically near 1900 us). On the car side, OCR1B is updated from the payload each loop:
This keeps steering response deterministic because the servo waveform is generated in hardware (Timer1), not by software toggling.
The drive motor uses a discrete H-bridge driven by a hybrid approach: latch lines select direction, while PWM is generated in software via Timer2 interrupts.
H-bridge signal assignment:
Timer2 is configured for Fast PWM with OCR2A as TOP. With OCR2A = 199 and prescaler = 8 at 8 MHz, PWM frequency is about 5 kHz. Instead of using OC2A/OC2B output pins directly, compare interrupts are used:
This effectively creates direction-dependent PWM on one of two output pins while the opposite side remains off.
Direction changes are intentionally staged to protect the bridge and drivetrain:
That delay prevents hard instant reversals and helps ensure MOSFETs on the same side of the H-bridge are not on at the same time during direction transitions, reducing shoot-through/braking stress.
Additional control logic from the car firmware:
Using Timer2 compare interrupts for every PWM cycle means the MCU is servicing frequent interrupts continuously while also polling and reading nRF24 payloads over SPI. That interrupt load can steal time from RF handling and make robust payload reception harder than a pure hardware-PWM motor path.
In practice, the current tuning (about 5 kHz PWM, deadband around zero, staged direction changes) was a workable sweet spot between motor smoothness and reliable radio updates. A strictly hardware PWM implementation for both drive channels would reduce ISR overhead and leave more CPU time for RF processing.
This power system was sized around worst-case drive events (motor stall plus active steering), not just average cruising current.
Estimated load envelope:
Worst-case instantaneous estimate:
Battery capability:
Power-stage device headroom:
Conclusion: the battery and MOSFET current limits provide practical headroom over the expected peak load, so the system can supply drive, steering, and control electronics simultaneously under normal and transient operation.
For safe charging practice on the car side, do not charge the car while it is powered on. Turn the car off first, then connect the charger.
The handheld controller samples two joystick axes, converts them into throttle and steering commands, and transmits those values over 2.4 GHz to the car at a steady update rate. The design goal is low-latency control with stable center behavior so the car tracks driver input without twitching or drift.
The remote uses an nRF24L01+ module with RF24 over SPI to transmit MotorControlPayload packets containing ocrMotor and ocrSteering. During initialization, firmware performs a radio hardware check, configures dynamic payload mode, and sets TX/RX addresses for the paired node. Timer0 is also configured with a 50 us base so the millis()/micros() timing used by RF24 remains stable while packets are sent continuously.
Joystick values are read through the ADC and mapped into the exact command domains expected by the vehicle firmware. This preprocessing keeps control semantics consistent across the RF link: signed throttle around zero and steering in absolute pulse-width units centered at a calibrated neutral.
Throttle is sampled on ADC channel 0, inverted to match intuitive stick direction, scaled into a signed drive command, and centered around zero. Positive values represent forward demand, negative values represent reverse demand, and near-zero commands cooperate with the car-side deadband to prevent idle creep.
Steering is sampled on ADC channel 1 and converted using a calibration profile where measured joystick potentiometer minimum, maximum, and center voltages are entered in firmware. The mapping uses those measured endpoints and center value to generate steering pulses, then clamps to the valid steering window before transmission. This makes neutral alignment and left/right travel more accurate than a fixed center assumption. A center deadzone is applied so small stick jitter and ADC noise snap back to neutral, reducing servo chatter and improving straight-line stability.
To measure and enter steering calibration voltages:
The remote has a built-in LiPo charging circuit. When a charger is connected, the controller may automatically power on while charging. You can place the power switch in the OFF position before charging if you prefer it to stay off.
|
|
ATmega164A/P pin information is from the Microchip datasheet: https://ww1.microchip.com/downloads/en/devicedoc/atmel-8272-8-bit-avr-microcontroller-atmega164a_pa-324a_pa-644a_pa-1284_p_datasheet.pdf
| Signal | MCU Pin | Notes |
|---|---|---|
| nRF24 CE | PB0 | Defined as CE_PIN 0 in car RF firmware |
| nRF24 CSN | PB1 | Defined as CSN_PIN 1 in car RF firmware |
| nRF24 SPI SCK | PB7 | Hardware SPI clock |
| nRF24 SPI MISO | PB6 | Hardware SPI MISO |
| nRF24 SPI MOSI | PB5 | Hardware SPI MOSI |
| nRF24 SPI SS | PB4 | Hardware SPI SS |
| Steering PWM (OC1B) | PD4 | Servo output (Timer1 OC1B) |
| Motor Forward Latch | PC0 | H-bridge control |
| Motor Forward PWM | PC1 | H-bridge control |
| Motor Reverse Latch | PC2 | H-bridge control |
| Motor Reverse PWM | PC3 | H-bridge control |
| Status LED | PD5 | Set during startup/status |
| Signal | MCU Pin | Notes |
|---|---|---|
| nRF24 CE | PB0 | Defined as CE_PIN 0 in remote RF firmware |
| nRF24 CSN | PB1 | Defined as CSN_PIN 1 in remote RF firmware |
| nRF24 SPI SCK | PB7 | Hardware SPI clock |
| nRF24 SPI MISO | PB6 | Hardware SPI MISO |
| nRF24 SPI MOSI | PB5 | Hardware SPI MOSI |
| nRF24 SPI SS | PB4 | Hardware SPI SS |
| Throttle Joystick (Y) ADC | PA0 / ADC0 | Read with readADC(0) |
| Steering Joystick (X) ADC | PA1 / ADC1 | Read with readADC(1) |
See LICENSE for details.
| x1 |
| NRF24L01+ Module | x1 |
| 7.4V battery | x1 |
| Gears | x1 |
| M2 Inserts | x3 |
| M2 screws | x3 |
| x1 |
| M2 Inserts | x10 |
| M2 Screws | x10 |
No comments yet. Be the first to ask about this board.