The present document provides all the information needed to rebuild an RC CAR from scratch using ESP32-based microcontroller units (MCUs), programmed in Arduino environment. The current project applies to the 1:16 Xinlehong (XLH)9136 model, but can be easily adapted to any RC Car model that incorporates:a) DC motor for the forward/backward (FW/BW) movement of the car,b) 3-wireServo Motor (operating at 5V) for the control of the steering wheel,c) 2S LiPo battery (of 7.4 nominal voltage or perhaps of higher voltage if taking into consideration the specifications and limitations of the boards held by the proposed system).
To complile this project with Arduino IDE you need to install:
1) ESP32 package (run Boards Manager in Arduino IDE)
2) Servo ESP32 library, available at: https://github.com/RoboticsBrno/ServoESP32
3) Adafruit_INA219, available at: https://github.com/adafruit/Adafruit_INA219
4) ThingPulse OLED SSD1306, available at: https://github.com/ThingPulse/esp8266-oled-ssd1306
Download a detailed report of the present project here
Demonstrationvideo:
Step-by-step guide to help you rebuild anRC Car (in the following 5videos)
#include <Wire.h>
#include <Adafruit_INA219.h>
#include <esp_now.h>
#include <WiFi.h>
#include <Servo.h>
Servo myservo;
Adafruit_INA219 ina219;
uint8_t broadcastAddress[] = {0x60, 0x55, 0xF9, 0x21, 0xCC, 0xD4}; //C3 MINI MAC Address on Remote Control
int LiPo=A7, Voltage, Voltage_Mapped; // Read the (divided by 2) Battery voltage from Analog Pin 7
unsigned char Battery;
float current_mA;
unsigned int I_mA;
int Receive_Timeout;
// Steering Wheel initally alignment
const int SERVO_QUIESCENT = 90;
// 4WD Movement
const int pwmMOTOR = 13; // Forward/Backward PWM pin
const int dirMOTOR = 12; // Forward/Backward DIR pin
const int pwmChannel_MOTOR = 2; //!!//SERVO is attached to pwm channel 2
// PWM settings
const int pwmFreq = 50;
const int pwmResolution = 8; // 8bits (0-255)
// Servo is initially set at 90 degrees
unsigned char servoValue=SERVO_QUIESCENT;
// Variables to store incoming readings
int FW_BW, L_R, L_R_Adj;
// Variables to drive the RC Car
int CAR_FW, CAR_BW, CAR_LEFT, CAR_RIGHT;
// Structure holding values to be sent by the REMOTE CONTROL
typedef struct RemoteControl_Outgoing {
signed char ForwardBackward;
signed char LeftRight;
signed char LeftRight_Adj;
} RemoteControl_Outgoing;
RemoteControl_Outgoing CAR;
// Create a struct_message to hold incoming readings sent by the REMOTE CONTROL
RemoteControl_Outgoing incomingReadings;
// Structure holding values to be sent by the RC CAR
typedef struct CAR_Outgoing {
unsigned char BatVoltage;
unsigned int Current;
} CAR_Outgoing;
CAR_Outgoing REMOTE_CONTROL;
esp_now_peer_info_t peerInfo;
// Callback when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
Receive_Timeout=0;
memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
FW_BW = incomingReadings.ForwardBackward;
L_R = incomingReadings.LeftRight;
L_R_Adj = incomingReadings.LeftRight_Adj;
//!!//Serial.println ( (String) "FW_BW=" + FW_BW + '\t' + '\t' + "L_R=" + L_R + '\t' + '\t' + "L_R_Adj=" + L_R_Adj );
// Prepare PWM configuration for the RC Car (Forward: 1 to 33 , Backward: -1 to -23 , Left: 1 to 25 , Right: -1 to -31)
if (FW_BW>0) CAR_FW = map(FW_BW,1,33,1,255);
if (FW_BW<0) CAR_BW = map(FW_BW,-1,-23,1,255);
if (FW_BW==0) CAR_BW = CAR_FW = 0;
if (L_R>0) CAR_LEFT = map(L_R,1,25,1,35); //1,255); // Don't let servo to turn from 0-180 (instead from 1-255 map to 1-35);
if (L_R<0) CAR_RIGHT = map(L_R,-1,-31,1,35); //1,255); // thus the SERVO will turn from 90-35 to 90+35
if (L_R==0) CAR_RIGHT = CAR_LEFT = 0;
Serial.println ( (String) '\t' + "CAR_FW=" + CAR_FW + '\t' + "CAR_BW=" + CAR_BW + '\t' + "CAR_LEFT=" + CAR_LEFT + '\t' + "CAR_RIGHT=" + CAR_RIGHT );
// 4WD Movement: generate PWM signal
if (CAR_FW>0 && CAR_BW==0) {
digitalWrite(dirMOTOR,LOW); //Forward
ledcWrite(pwmChannel_MOTOR, (CAR_FW+CAR_BW) );
}
else if (CAR_BW>0 && CAR_FW==0) {
digitalWrite(dirMOTOR,HIGH); //Backward
ledcWrite(pwmChannel_MOTOR, (CAR_FW+CAR_BW) );
}
else ledcWrite(pwmChannel_MOTOR, 0 );
// Steering Wheel: Prepare & Write Servo value
if (CAR_LEFT>0 && CAR_RIGHT==0) {
servoValue = (unsigned char) (SERVO_QUIESCENT-CAR_LEFT+L_R_Adj) ;
}
else if (CAR_RIGHT>0 && CAR_LEFT==0) {
servoValue = (unsigned char) (SERVO_QUIESCENT+CAR_RIGHT+L_R_Adj);
}
else servoValue = (unsigned char)(SERVO_QUIESCENT+L_R_Adj);
myservo.write(servoValue); // Write Servo value
//!!//Serial.println (servoValue);
// Prepare Voltage and Current Measurements
Voltage = analogRead(LiPo);
Voltage_Mapped = map(Voltage,1,4095,1,63);
Battery = (unsigned char)Voltage_Mapped;
//read Current
float current_mA = ina219.getCurrent_mA();
I_mA = (unsigned int)current_mA;
// Transmit Voltage and Current data to the Remote Control device
REMOTE_CONTROL.BatVoltage = Battery;
REMOTE_CONTROL.Current = I_mA;
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &REMOTE_CONTROL, sizeof(REMOTE_CONTROL));
}
// Callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
}
void setup() {
ina219.begin();
// 4WD Movement
pinMode(dirMOTOR, OUTPUT); // Forward/Backward DIR pin
ledcSetup(pwmChannel_MOTOR, pwmFreq, pwmResolution); // configure PWM
ledcAttachPin(pwmMOTOR, pwmChannel_MOTOR); // attach PWM channel to the GPIO pin to be controlled
// Steering Wheel
myservo.attach(27); // attach Servo pin
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
esp_now_init();
WiFi.setTxPower(WIFI_POWER_8_5dBm);
memcpy(peerInfo.peer_addr, broadcastAddress, 6); // Register peer
peerInfo.channel = 0;
peerInfo.encrypt = false;
esp_now_add_peer(&peerInfo); // Add peer
// Register for a callback function that will be called when data is received
esp_now_register_recv_cb(OnDataRecv);
}
void loop() {
Receive_Timeout++;
if (Receive_Timeout > 200000) {
ledcWrite(pwmChannel_MOTOR, 0 );
Receive_Timeout=200000; // stall counter (@ value higher than 200000) until RF communication is restored (about 2sec delay)
}
}
#include <esp_now.h>
#include <WiFi.h>
#include <Wire.h>
#include "SSD1306Wire.h"
SSD1306Wire display(0x3d, SDA, SCL); // I2C Addr = 0x3D
typedef void (*Demo)(void);
int demoMode = 0;
uint8_t broadcastAddress[] = {0x78, 0xE3, 0x6D, 0x1B, 0x1B, 0xC8}; //D1R32 MAC Address on RC Car
float Battery;
int Battery_Percentage;
unsigned int I_mA;
int Wheels=A2, Steering=A1, ServoAdj=A0; // Define Analog Channels
int ADC_Resolution = 4095; //ADC InputRange: 0-3V3; thus lsb=InputRange/2^12=0.8mV
int ADC_Wheels, ADC_Steering, ADC_ServoAdj, Wheels_Mapped, Steering_Mapped, ServoAdj_Mapped;
int FW_BW, L_R;
const int SteeringQ=35, WheelsQ=27; // quiescent point steering & wheels
const int Hysteresis = 3; // add hysteresis from quiescent point
// Structure holding values to be sent by the REMOTE CONTROL
typedef struct RemoteControl_Outgoing {
signed char ForwardBackward;
signed char LeftRight;
signed char LeftRight_Adj;
} RemoteControl_Outgoing;
RemoteControl_Outgoing CAR;
// Structure holding values to be sent by the RC CAR
typedef struct CAR_Outgoing {
unsigned char BatVoltage;
unsigned int Current;
} CAR_Outgoing;
CAR_Outgoing REMOTE_CONTROL;
// Create a struct_message to hold incoming readings sent by the RC CAR
CAR_Outgoing incomingReadings;
esp_now_peer_info_t peerInfo;
// Callback when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
Battery = (float) incomingReadings.BatVoltage;
Battery = (Battery * 3.3/63.0) * 4.0; // ADC for battery is mapped in the RC Car system from 1-4095 to 1-31(0.1V resolution)
Battery_Percentage = (int)(Battery*100.0);
Battery_Percentage = map(Battery_Percentage,640,840,0,100);
//Serial.println(incomingReadings.Current);
//!!//Serial.println((String) "Battery=" + Battery + '\t' + "Battery_Percentage=" + Battery_Percentage + '%' + '\t' + "Current=" + incomingReadings.Current);
}
// Callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
}
void setup() {
display.init();
display.flipScreenVertically();
display.setFont(ArialMT_Plain_10);
//!!//Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
esp_now_init();
WiFi.setTxPower(WIFI_POWER_8_5dBm);
esp_now_register_send_cb(OnDataSent); // Get the status of Trasnmitted packet
memcpy(peerInfo.peer_addr, broadcastAddress, 6); // Register peer
peerInfo.channel = 0;
peerInfo.encrypt = false;
esp_now_add_peer(&peerInfo); // Add peer
esp_now_register_recv_cb(OnDataRecv); // Register for a callback function that will be called when data is received
}
void drawProgressBarDemo() {
// draw the progress bar
display.drawProgressBar(0, 0/*32*/, 120, 10, Battery_Percentage);
// draw the percentage as String
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(64, 15, "BATTERY= " + String(Battery_Percentage) + "%");
}
Demo demos[] = {drawProgressBarDemo}; ///Demo demos[] = {drawFontFaceDemo, drawTextFlowDemo, drawTextAlignmentDemo, drawRectDemo, drawCircleDemo, drawProgressBarDemo, drawImageDemo};
int demoLength = (sizeof(demos) / sizeof(Demo));
void loop() {
if (incomingReadings.Current == 0) Battery=0;
// based on a revision of ..Arduino\libraries\esp8266-oled-ssd1306-master\examples\SSD1306SimpleDemo
display.clear();
// draw the current demo method
demos[demoMode]();
display.setFont(ArialMT_Plain_16);
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(128, 48, String(Battery) + "V"); // Battery Voltage
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(1, 48, String(incomingReadings.Current) + "mA" );///display.drawString(128, 54, String(millis()));
// write the buffer to the display
display.display();
//Steering Wheel
ADC_Steering = analogRead(Steering);
Steering_Mapped = map(ADC_Steering,160,4095,1,63); //Decrease Resolution of Steering Wheels
//Serial.println((String) "Steering_Mapped=" + Steering_Mapped);
if (Steering_Mapped > SteeringQ+Hysteresis) L_R = Steering_Mapped - (SteeringQ+Hysteresis); // LEFT (positive value)
else if (Steering_Mapped < SteeringQ-Hysteresis) L_R = (Steering_Mapped-SteeringQ+Hysteresis); //RIGHT (negative value)
else L_R=0;
//4WD Movement
ADC_Wheels = analogRead(Wheels);
Wheels_Mapped = map(ADC_Wheels,1270,4095,1,63); //Decrease Resolution of Wheels
if (Wheels_Mapped > WheelsQ+Hysteresis) FW_BW = Wheels_Mapped - (WheelsQ+Hysteresis); //FORWARD (positive value)
else if (Wheels_Mapped < WheelsQ-Hysteresis) FW_BW = (Wheels_Mapped-WheelsQ+Hysteresis); //BACKWARD (negative value)
else FW_BW=0;
//Servo adjustment (Steering Wheel alignment)
ADC_ServoAdj = analogRead(ServoAdj);
ServoAdj_Mapped = map(ADC_ServoAdj,0,4095,0,32); //Decrease Resolution of Servo trimming value
ServoAdj_Mapped -= 16;
//Serial.println((String) "Forward/Backward=" + FW_BW + '\t' + "Left/Right=" + L_R + '\t' + "Steering Alignment=" + ServoAdj_Mapped);
// Prepare & Transmit data to the RC Car
CAR.ForwardBackward = (signed char)FW_BW;
CAR.LeftRight = (signed char)L_R;
CAR.LeftRight_Adj = (signed char)(ServoAdj_Mapped);
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &CAR, sizeof(CAR));
}
Comments