I am coding an RC car in the Arduino IDE.
Here are the specs of what I use:
- Arduino Nano esp-32
- Wifi protocol: esp-now
- Motor: AL-2835-10
The receiver and the sender are both the same Arduino and are correctly communicating. Steering and forward is working fine, it is reversing that doesn't work at all.
What do I have to do or am I not seeing?
The code looks as follows:
#include <esp_now.h>
#include <WiFi.h>
#include <esp_wifi.h>
#include <ESP32Servo.h>
typedef struct struct_message {
int steering_stick;
int accelerating_stick;
} struct_message;
struct_message myReceivedData;
Servo steeringServo;
Servo ESC;
// Keep original pin definitions
const int steeringPin = 4;
const int escPin = 6;
// Maintain original mapping constants for accelerating stick
const int deadzoneLow = 1920;
const int deadzoneHigh = 1940;
const int idlePulse = 850; // Confirmed neutral pulse is 1000μs
const int forwardMinPulse = 1100; // Forward begins at this value
const int forwardMaxPulse = 2000; // Full forward
const int reverseMinPulse = 500; // Modified: Less aggressive reverse
const int reverseMaxPulse = 950; // Approach neutral from below
// Keep original steering constants
const int steerMin = 60;
const int steerMax = 130;
// Variables for state tracking
int lastAccel = -1;
int lastSteer = -1;
bool motorBraked = false; // Track if motor is in braked state
unsigned long brakeStartTime = 0; // When braking began
const unsigned long brakeHoldTime = 2000; // Hold time in brake/neutral before reverse
// ESC initialization flags
bool escInitialized = false;
bool reverseModeEnabled = false;
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
memcpy(&myReceivedData, incomingData, sizeof(myReceivedData));
// Keep the original logging logic
if (myReceivedData.accelerating_stick != lastAccel || myReceivedData.steering_stick != lastSteer) {
Serial.println("Received data:");
Serial.print("Accelerating Stick: ");
Serial.println(myReceivedData.accelerating_stick);
Serial.print("Steering Stick: ");
Serial.println(myReceivedData.steering_stick);
lastAccel = myReceivedData.accelerating_stick;
lastSteer = myReceivedData.steering_stick;
}
// Make sure ESC is initialized
if (!escInitialized) {
return;
}
int escPulse;
// Deadzone handling - stick in neutral position
if (myReceivedData.accelerating_stick >= deadzoneLow && myReceivedData.accelerating_stick <= deadzoneHigh) {
escPulse = idlePulse;
motorBraked = true;
brakeStartTime = millis();
// In deadzone, reset brake timer only if we weren't already braked
if (!motorBraked) {
brakeStartTime = millis();
motorBraked = true;
}
}
// Stick in reverse position
else if (myReceivedData.accelerating_stick < deadzoneLow) {
// Check if we're currently going forward (need to brake first)
if (!motorBraked) {
// Need to brake first before reversing
escPulse = idlePulse;
motorBraked = true;
brakeStartTime = millis();
Serial.println("Braking before reverse...");
}
// If we've been in brake mode long enough, allow reverse
else if (millis() - brakeStartTime >= brakeHoldTime) {
// Use double-tap reverse technique (common in brushless ESCs)
// First tap already happened with the braking
// Now apply reverse signal
escPulse = map(myReceivedData.accelerating_stick, 0, deadzoneLow, reverseMinPulse, reverseMaxPulse);
Serial.println("Applying reverse signal after brake period");
}
// Still in braking period
else {
escPulse = idlePulse;
Serial.println("Still in braking period before reverse");
}
}
// Stick in forward position
else {
// Mapping for forward motion - keep your original mapping logic
escPulse = map(myReceivedData.accelerating_stick, deadzoneHigh, 4095, forwardMinPulse, forwardMaxPulse);
motorBraked = false; // No longer in brake state
}
// Keep your original steering mapping
int steerAngle = map(myReceivedData.steering_stick, 0, 4095, steerMin, steerMax);
// Update outputs
ESC.writeMicroseconds(escPulse);
steeringServo.write(steerAngle);
// Keep your original debug output
Serial.print("Mapped ESC Pulse: ");
Serial.println(escPulse);
Serial.print("Mapped Steering Angle: ");
Serial.println(steerAngle);
// Additional debug information about motor state
if (motorBraked) {
Serial.print("Motor braked: ");
Serial.print((millis() - brakeStartTime) / 1000.0);
Serial.println(" seconds");
}
Serial.println();
}
void setup() {
Serial.begin(115200);
delay(1000);
// Keep your original WiFi and ESP-NOW initialization
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
esp_now_register_recv_cb(OnDataRecv);
Serial.println("Receiver initialized and waiting for data...");
// Keep original servo setup
steeringServo.setPeriodHertz(50);
ESC.setPeriodHertz(50);
// Attach servos with full pulse range
steeringServo.attach(steeringPin);
ESC.attach(escPin, 800, 2000); // Adjusted range for bidirectional operation
// Enhanced ESC initialization for bidirectional mode
initializeESC();
// Set default positions
steeringServo.write((steerMin + steerMax) / 2);
ESC.writeMicroseconds(idlePulse);
Serial.println("Default positions set: steering centered, ESC idle.");
}
void initializeESC() {
Serial.println("Initializing ESC for bidirectional operation...");
Serial.println("Step 1: Hold in neutral position");
// First, ensure the ESC is in neutral for a few seconds
ESC.writeMicroseconds(idlePulse);
delay(1000);
// Step 2: Programming sequence for bidirectional mode
// Many brushless ESCs use a specific pattern to enter programming mode
Serial.println("Step 2: Sending bidirectional mode programming sequence");
// Sequence: neutral → full forward → neutral → full reverse → neutral
// This is a common pattern to enable bidirectional operation
// Full forward
ESC.writeMicroseconds(forwardMinPulse);
delay(1000);
// Back to neutral
ESC.writeMicroseconds(idlePulse);
delay(1000);
// Full reverse (as a programming signal)
ESC.writeMicroseconds(reverseMinPulse);
delay(1000);
// Back to neutral
ESC.writeMicroseconds(idlePulse);
delay(1000);
// Short pulse to forward
ESC.writeMicroseconds(forwardMinPulse);
delay(500);
// Back to neutral
ESC.writeMicroseconds(idlePulse);
delay(1000);
// Short pulse to reverse
ESC.writeMicroseconds(reverseMaxPulse);
delay(500);
// Final neutral position
ESC.writeMicroseconds(idlePulse);
delay(1000);
Serial.println("Bidirectional mode initialization complete");
Serial.println("Note: Most ESCs require double-tap for reverse (brake first, then reverse)");
escInitialized = true;
reverseModeEnabled = true;
}
void loop() {
// All updates are handled in the ESP-NOW callback
// Safety feature: if no signal for a while, go to neutral
static unsigned long lastActivityTime = millis();
if (millis() - lastActivityTime > 5000) {
// No signal for 5 seconds - safety neutral
ESC.writeMicroseconds(idlePulse);
Serial.println("Safety neutral engaged - no activity for 5+ seconds");
lastActivityTime = millis();
}
}