Posted on Leave a comment

Make a Joystick-Controlled Car with ESP32 and NodeMCU

Are you interested in making a remote-controlled car using ESP32 and NodeMCU? In this project, we will use a joystick module to control a car wirelessly. This is a fun and simple way to explore ESP-NOW communication and motor control!


What You Need

To build this joystick-controlled car, you will need:

  • ESP32 (for the remote controller)
  • NodeMCU (ESP8266) (for the car)
  • Joystick module
  • L298N motor driver
  • Two DC motors (500 RPM Johnson motors)
  • Two wheels (70mm diameter, 20mm width)
  • LM2596 voltage regulator (to power NodeMCU)
  • Power source (3S battery or similar)
  • Jumper wires

How It Works

  • The ESP32 reads joystick movements and sends commands wirelessly using ESP-NOW.
  • The NodeMCU receives these commands and controls the motors accordingly.
  • The car moves forward, backward, left, and right based on joystick input.

Wiring Connections

Remote (ESP32 + Joystick Module)

  • Joystick VCCESP32 3.3V
  • Joystick GNDESP32 GND
  • Joystick X-axisESP32 GPIO 34
  • Joystick Y-axisESP32 GPIO 35

Car (NodeMCU + L298N Motor Driver + Motors)

  • Motor 1 IN1NodeMCU D1
  • Motor 1 IN2NodeMCU D2
  • Motor 2 IN1NodeMCU D3
  • Motor 2 IN2NodeMCU D4
  • Motor 1 ENANodeMCU D5
  • Motor 2 ENBNodeMCU D6
  • LM2596 Output (3.3V/5V)NodeMCU Vin
  • LM2596 GroundNodeMCU GND

ESP32 Code for the Remote

#include <esp_now.h>
#include <WiFi.h>

// Define DEBUG to enable or disable serial debug statements
//#define DEBUG  // Comment this line to disable debug statements

const int xPin = 34;  // X-axis connected to GPIO 34
const int yPin = 35;  // Y-axis connected to GPIO 35

// Define thresholds for dead zone (adjust as needed)
const float DEAD_ZONE_THRESHOLD = 0.2;  // Joystick values within ±0.2V are considered centered

uint8_t broadcastAddress[] = {0xCC, 0x50, 0xE3, 0x0D, 0x15, 0x09};

typedef struct struct_message {
  char command[32];
  int b;
} struct_message;

struct_message myData;
esp_now_peer_info_t peerInfo;

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  #ifdef DEBUG
  Serial.print("\r\nLast Packet Send Status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
  #endif
}

void setup() {
    #ifdef DEBUG
    Serial.begin(115200);
    #endif

    WiFi.mode(WIFI_STA);

    if (esp_now_init() != ESP_OK) {
        #ifdef DEBUG
        Serial.println("Error initializing ESP-NOW");
        #endif
        return;
    }

    esp_now_register_send_cb(OnDataSent);
    memcpy(peerInfo.peer_addr, broadcastAddress, 6);
    peerInfo.channel = 0;
    peerInfo.encrypt = false;
    if (esp_now_add_peer(&peerInfo) != ESP_OK) {
        #ifdef DEBUG
        Serial.println("Failed to add peer");
        #endif
        return;
    }
}

void loop() {
    int xValue = analogRead(xPin);  // Read X-axis value
    int yValue = analogRead(yPin);  // Read Y-axis value

    float xVoltage = (xValue * 3.3) / 4095;  // Convert to voltage (0-3.3V)
    float yVoltage = (yValue * 3.3) / 4095;  // Convert to voltage (0-3.3V)

    #ifdef DEBUG
    Serial.print("X Voltage: "); Serial.print(xVoltage);
    Serial.print(" | Y Voltage: "); Serial.println(yVoltage);
    #endif

    // Check if the joystick is within the dead zone (centered)
    if (abs(xVoltage - 1.65) < DEAD_ZONE_THRESHOLD && abs(yVoltage - 1.65) < DEAD_ZONE_THRESHOLD) {
        strcpy(myData.command, "S"); // Stop (centered)
        myData.b = 0;  // No movement
    }
    // Check for movement outside the dead zone
    else if (xVoltage > (1.65 + DEAD_ZONE_THRESHOLD)) {
        strcpy(myData.command, "F"); // Forward
        myData.b = xVoltage;
    } else if (xVoltage < (1.65 - DEAD_ZONE_THRESHOLD)) {
        strcpy(myData.command, "B"); // Backward
        myData.b = xVoltage;
    } else if (yVoltage > (1.65 + DEAD_ZONE_THRESHOLD)) {
        strcpy(myData.command, "L"); // Left
        myData.b = yVoltage;
    } else if (yVoltage < (1.65 - DEAD_ZONE_THRESHOLD)) {
        strcpy(myData.command, "R"); // Right
        myData.b = yVoltage;
    }

    // Send the command via ESP-NOW
    esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
    #ifdef DEBUG
    if (result == ESP_OK) {
        Serial.println("Sent with success");
    } else {
        Serial.println("Error sending the data");
    }
    #endif
    
    delay(20);  // Small delay to make output readable
}

NodeMCU Code for the Car

#include <ESP8266WiFi.h>
#include <espnow.h>

// Structure to receive data
typedef struct struct_message {
  char a[32];
  int b;
  float c;
  bool d;
} struct_message;

struct_message myData;

// Pin definitions
#define LED_PIN 2  // Onboard LED pin (D4 on ESP8266)
#define M1_ENA D5   // Motor 1 PWM (Speed)
#define M2_ENB D6   // Motor 1 PWM (Speed)
#define M1_IN1 D1  // Motor 1 input 1
#define M1_IN2 D2  // Motor 1 input 2
#define M2_IN1 D3  // Motor 2 input 1
#define M2_IN2 D4  // Motor 2 input 2

// Default speeds (adjust these values experimentally)
int speedLeft = 800;   // 0-1023 (80% speed)
int speedRight = 700;  // Right motor slightly slower

// Callback function when data is received
void OnDataRecv(uint8_t *mac, uint8_t *incomingData, uint8_t len) {
  memcpy(&myData, incomingData, sizeof(myData));
  Serial.print("Bytes received: ");
  Serial.println(len);
  Serial.print("Data received: ");
  Serial.println(myData.a);
  Serial.print("value received: ");
  Serial.println(myData.b);

  // Control motors based on received character
  if (strcmp(myData.a, "F") == 0) {
    // Move forward
    analogWrite(M1_ENA, speedLeft);
    analogWrite(M2_ENB, speedRight);
    digitalWrite(M1_IN1, HIGH); digitalWrite(M1_IN2, LOW);
    digitalWrite(M2_IN1, HIGH); digitalWrite(M2_IN2, LOW);
    digitalWrite(LED_PIN, LOW);  // Turn on LED (inverted logic on ESP8266)
  } else if (strcmp(myData.a, "B") == 0) {
    // Move backward
    analogWrite(M1_ENA, speedLeft);
    analogWrite(M2_ENB, speedRight);
    digitalWrite(M1_IN1, LOW); digitalWrite(M1_IN2, HIGH);
    digitalWrite(M2_IN1, LOW); digitalWrite(M2_IN2, HIGH);
    digitalWrite(LED_PIN, HIGH);  // Turn off LED (inverted logic on ESP8266)
  } else if (strcmp(myData.a, "L") == 0) {
    // Turn left
    analogWrite(M1_ENA, speedLeft);
    analogWrite(M2_ENB, speedRight);
    digitalWrite(M1_IN1, LOW); digitalWrite(M1_IN2, HIGH);
    digitalWrite(M2_IN1, HIGH); digitalWrite(M2_IN2, LOW);
  } else if (strcmp(myData.a, "R") == 0) {
    // Turn right
    analogWrite(M1_ENA, speedLeft);
    analogWrite(M2_ENB, speedRight);
    digitalWrite(M1_IN1, HIGH); digitalWrite(M1_IN2, LOW);
    digitalWrite(M2_IN1, LOW); digitalWrite(M2_IN2, HIGH);
  }
  else if (strcmp(myData.a, "S") == 0) {
    // Turn right
    analogWrite(M1_ENA, 0);
    analogWrite(M2_ENB, 0);
    digitalWrite(M1_IN1, HIGH); digitalWrite(M1_IN2, LOW);
    digitalWrite(M2_IN1, LOW); digitalWrite(M2_IN2, HIGH);
  } else {
    // Stop motors
    analogWrite(M1_ENA, 0);
    analogWrite(M2_ENB, 0);
    //   digitalWrite(M1_IN1, LOW); digitalWrite(M1_IN2, LOW);
    //   digitalWrite(M2_IN1, LOW); digitalWrite(M2_IN2, LOW);
  }
}

void setup() {
  // Initialize Serial Monitor
  Serial.begin(115200);

  // Initialize pins
  pinMode(LED_PIN, OUTPUT);
  pinMode(M1_IN1, OUTPUT);
  pinMode(M1_IN2, OUTPUT);
  pinMode(M2_IN1, OUTPUT);
  pinMode(M2_IN2, OUTPUT);

  // Ensure motors and LED are off initially
  digitalWrite(LED_PIN, HIGH);  // LED off (inverted logic on ESP8266)
  digitalWrite(M1_IN1, LOW); digitalWrite(M1_IN2, LOW);
  digitalWrite(M2_IN1, LOW); digitalWrite(M2_IN2, LOW);

  // Set device as a Wi-Fi Station
  WiFi.mode(WIFI_STA);

  // Initialize ESP-NOW
  if (esp_now_init() != 0) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  // Register the receive callback
  esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);
  esp_now_register_recv_cb(OnDataRecv);
}

void loop() {
  // Nothing to do here
}

How to Upload the Code

  1. Upload the remote code to your ESP32.
  2. Upload the car code to your NodeMCU.
  3. Power up both devices and check serial monitor outputs for debugging.

Testing the Car

  1. Move the joystick forward → The car moves forward.
  2. Move the joystick backward → The car moves backward.
  3. Move the joystick left → The car turns left.
  4. Move the joystick right → The car turns right.
  5. Release the joystick (center position) → The car stops.

3d Printed parts

Since when i made this car. I had Johnson motors of 500RPM. But i did not had any bracket so i designed the brackets in FreeCAD and printed on Creality ender 3 v3 KE printer.

Posted on Leave a comment

How to setup ESP32 on Arduino IDE

Prerequisites: Before we dive into the setup process, there are a few things you’ll need to gather:

  1. An ESP32 development board: You can find numerous options available online, including the ESP32 DevKitC, NodeMCU-32S, and Wemos D1 R32, among others.
  2. A USB cable: Ensure you have a suitable USB cable to connect your ESP32 board to your computer.
  3. Arduino IDE: Download and install the latest version of the Arduino IDE from the official Arduino website (https://www.arduino.cc/en/software). The IDE provides an easy-to-use interface for writing and uploading code to your ESP32.

Now that you have everything you need, let’s get started with the setup process.

Step 1: Install the ESP32 Board in Arduino IDE:

  1. Open the Arduino IDE.
  2. Go to “File” > “Preferences” to open the Preferences window.
  3. In the “Additional Boards Manager URLs” field, add the following URL:
https://dl.espressif.com/dl/package_esp32_index.json
  1. Click “OK” to save the preferences.
  2. Navigate to “Tools” > “Board” > “Boards Manager.”
  3. In the Boards Manager window, search for “ESP32” and select the “esp32” package by Espressif Systems.
  4. Click the “Install” button to begin the installation process.
  5. Once the installation is complete, close the Boards Manager window.

Step 2: Select the ESP32 Board:

  1. Connect your ESP32 board to your computer using the USB cable.
  2. In the Arduino IDE, go to “Tools” > “Board” and select your ESP32 board from the list of available options. Choose the appropriate board variant based on the specific ESP32 module you are using.

Step 3: Choose the Correct Port:

  1. Navigate to “Tools” > “Port” and select the port to which your ESP32 board is connected. The port name will vary depending on your operating system.
    • On Windows, it will typically be in the format “COMX” (e.g., COM3, COM4).
    • On macOS, it will usually appear as “/dev/cu.SLAB_USBtoUART” or “/dev/cu.usbserial-XXXX” (X represents some alphanumeric characters).
    • On Linux, it may appear as “/dev/ttyUSBX” or “/dev/ttyACMX” (X represents a number).

Step 4: Upload a Test Sketch:

  1. Open the “Blink” example sketch by going to “File” > “Examples” > “01.Basics” > “Blink.”
  2. Make any necessary modifications to the sketch if required.
  3. Click on the “Upload” button (the rightward-pointing arrow) to compile and upload the sketch to your ESP32 board.
  4. Wait for the upload process to complete.
  5. Once the upload is successful, you should see the onboard LED blinking at a regular interval.

Congratulations! You have successfully set up your ESP32 board on the Arduino IDE. You are now ready to unleash the power of the ESP32 and start

Troubleshoot

ImportError: No module named serial in Linux

Traceback (most recent call last):
  File "/home/abhay/.arduino15/packages/esp32/tools/esptool_py/3.0.0/esptool.py", line 38, in <module>
    import serial
ImportError: No module named serial

exit status 1

Compilation error: exit status 1

here’s an alternative solution you can try:

  1. Install pySerial via apt-get:
  • Open a terminal.
  • Run the following command to install pySerial using apt-get:
sudo apt-get install python-serial
  1. Restart Arduino IDE: Close the Arduino IDE completely and then open it again.
  2. Upload the code to the ESP32 board: Try uploading your code to the ESP32 board once again.

By installing python-serial using apt-get, you should be able to resolve the missing serial module error. If you still encounter any issues, please provide any error messages or specific problems you’re facing, along with any additional details about your setup.

Posted on Leave a comment

Battery Monitoring with Led Light Control Using ESP32 Bluetooth for my Solar System

I need to monitor battery voltage to check weather my charging system is working correctly or not. But to do that i have get up and walk with my multimeter towards the battery and i have to take these reading in night.

I placed my battery in a corner where there is very little light. So I added a transistor switch which can be controlled using Bluetooth to turn on the led light. Which provides enough light to act as a night light.

The ESP32 also has an ADC built into it. Which is very poor in terms of accuracy. It gets you the idea that there is something to work with but it does not give very precise reading like a multimeter.

Also, the ESP32 ADC is non-linear. The ADC also has an attenuation feature. Which by default is set to 11db. which gives us a workable range of up to 3.3V. But there are flat-out regions which need to be taken into account if you want to measure anything from this ADC. There is a 0 – 0.2V region in which the value read is constant, and there is a 2.9V to 3.3V region which also gives you constant reading values.

Resolution is 12-bit by default.

To measure a large voltage using this device. I made a voltage divider.

Battery +ve———/\/\/ R1 \/\/\/ ———— Vout —————— /\/\/\/\/ R2 \/\/\/\/ ———-GND

R1 = 10 kilo Ohm

R2 = 1 kilo Ohm

Which gives me a dividing factor of 11.

So if 11 V is available at the battery anode. Then the V out is 1V.

Normally the lead acid battery voltage goes from 10V(fully discharged) to 14.6V(Maximum charge) to 15V(Over Charged)

The ADC values are converted to battery voltage using the following equation

( (analogValue * (3.3/4096 ) ) * ((9820+985)/985) ) + 3.3

#include "BluetoothSerial.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

BluetoothSerial SerialBT;


char var1;
int analogValue;
uint8_t i;
int avg_adc;

void setup()
{
  Serial.begin(115200);
  SerialBT.begin("ESP32test"); //Bluetooth device name
  Serial.println("pair it with bluetooth!");
pinMode(2, OUTPUT);
 
}

void loop()
{
  /*
   * UART 
   */
  if (Serial.available()) {
  
    SerialBT.write(Serial.read());
  }


/*
 * Bluetooth serial
 */
  if (SerialBT.available()) {
      var1 = SerialBT.read();
    Serial.write(var1);
  }
    if (var1 == '9')
{
  digitalWrite(2,HIGH);
}
else if(var1 == '1')
{
  digitalWrite(2,LOW);
}


avg_adc = 0;
for(i =0 ; i< 100; i++)
{
  avg_adc += analogRead(34);
}
analogValue = avg_adc / 100;

SerialBT.printf("ADC = %d\n",analogValue);
  SerialBT.printf("volt = %f\n",   ( (analogValue * (3.3/4096 ) ) * ((9820+985)/985) ) + 3.3 ) ;
    delay(1000);
}

Since the ADC is not accurate it goes all over the place. To dampen its effect on the reading I am averaging 100 readings of ADC.

avg_adc = 0;
for(i =0 ; i< 100; i++)
{
  avg_adc += analogRead(34);
}
analogValue = avg_adc / 100;

TIP3055 BJT is used as a low-side switch. R1 gives a base current of 330uA(=3.3/10000) which gets multiplied by the beta or hFE 70 of transistor to get a collector current of 0.023A or 23mA.

Posted on Leave a comment

How to interface potentiometer to ESP32 to read ADC Values

Here is the simple code to read the ADC value and show it in the serial monitor of Arduino ide.

const int Analog_channel_pin= 34;
int ADC_VALUE = 0;
float voltage_value = 0; 
void setup() 
{
Serial.begin(115200);
}
void loop() 
{
ADC_VALUE = analogRead(Analog_channel_pin);
Serial.print("ADC VALUE = ");
Serial.println(ADC_VALUE);
delay(1000);
voltage_value = (ADC_VALUE * 3.3 ) / (4095);
Serial.print("    Voltage = ");
Serial.print(voltage_value);
Serial.println("volts");
delay(1000);
}
The output of serial monitor