Artificial Brain Controlled RC Truck

This is a draft so you have access to the code used on the two ESP32 microcontrollers. I wrote these instructions while building the project but have not proofread them really. I will finalize the article when I get some free time and add more content to it with better formatting.  These instructions go along with this video.

Here is a guide with code and setup instructions for two ESP32 boards to wirelessly transmit data of four proximity sensors. All analog input values will be converted to 12-bit (0-4095) by the ESP32 ADC and sent using ESP-NOW protocol as a 12 bit output. This is received as a 12-bit values and then converted to 8-bit(0-255) by dividing by 16. This 8-bit output is turned into a PWM signal.

The PWM Signal will be turned into an analog signal by using a 10kΩ Resistor and a100nF Capacitor as a RC filter. This will create the original analog signal that was send out. This will be amplified by an LM358 op-amp and a gain of 2 will be applied by using two 10K resistors. The output will power a display LED to show the signal was received. It will also go into a second op-amp which will act as buffer to endure the outputs signal is matched. This output will will feed into the artificial neuron. When activated the output will increase the firing rate of that neuron.

ESP32 Setup Instructions

Part 1: Software Setup
Steps to Upload Code for the Receiver First. (Cause you need the MAC Address for the Transmitter)

When I plugged in ESP 32 with USB data cable nothing would show up. I had to update the drivers.
Go to device manager.
Look for, CP2102 USB to UART bridge Controller
The drivers for this device are not installed. (Code 28)
There are no compatible drivers for this device.
To find a driver for this device, click Update Driver.

Download the correct driver:

  • Go to https://www.silabs.com/developer-tools/usb-to-uart-bridge-vcp-drivers?tab=downloads

  • Download ” CP210x Universal Windows Driver” as a zip

  • Extract the zip file
    Right click in device manager, update driver, Browse you computer and selected select the extracted folder. CP210x_Universal_Windows_Driver

  • restart your computer.

  • Now you can delete the zip and extracted folder.

    Under Ports I see Silicon Labs CP210x USB UART Bridge(com3)

  • Now we can upload files to the ESP 32 with port (COM3) in your code editor or Arduino IDE.

Step 1: Download and Install Arduino IDE

  1. Go to the official Arduino website: https://www.arduino.cc/en/software

  2. Download “Arduino IDE 2.3.2” for Windows

  3. Install Arduino IDE:

    • Run the downloaded installer

    • Accept the license agreement

    • Choose installation location (default is fine)

    • Allow the driver installation when prompted

    • Wait for installation to complete

Step 2: Configure Arduino IDE for ESP32

  1. Open Arduino IDE

  2. Go to File > Preferences

  3. In the “Additional Boards Manager URLs” field, paste:

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

  1. Click “OK”

  2. Go to Tools > Board > Boards Manager

  3. In the search box, type “ESP32”

  4. Look for “esp32 by Espressif Systems” and click “Install” Version 3.2.0

  5. Wait for installation to complete

Step 3: Select ESP32 Board and Port

  1. Go to Tools > Board

  2. Look for “ESP32 Arduino” section and select “ESP32 Dev Module”

  3. Go to Tools > Port

  4. Select “COM3 (Silicon Labs CP210x USB to UART Bridge)”

Step 4: Get Your Receiver’s MAC Address

You need to first upload a MAC address finder code to your receiver ESP32. Here’s how:

  1. Create a new sketch in Arduino IDE

  2. Copy and paste this code:

  3. #include <WiFi.h>
  4. void setup() {
  5.   Serial.begin(115200);
  6.   delay(1000);  // Wait for serial to initialize
  7.  
  8.   Serial.println(“Starting…”);
  9.  
  10.   // Initialize WiFi
  11.   WiFi.mode(WIFI_STA);
  12.   delay(500);  // Small delay for WiFi to initialize
  13.  
  14.   Serial.print(“ESP Board MAC Address: “);
  15.   Serial.println(WiFi.macAddress());
  16. }
  17. void loop() {
  18.   // Nothing here
  19. }


Save file as MAC_Finder
Upload this code to the ESP32 that will be your
receiver.

void loop() {}

  1. Upload this code to the ESP32 that will be your receiver by clicking upload.

  2. Open Serial Monitor (Tools > Serial Monitor), set baud rate to 115200

  3. You might need to push EN (rest) button on the ESP board.

  4. You should see output like: ESP Board MAC Address: 24:6F:28:AE:C5:9C

My Actual MAC Address was.
ESP Board MAC Address: F4:65:0B:56:66:6C

Code should be updated.
uint8_t receiverAddress[] = {0xF4, 0x65, 0x0B, 0x56, 0x66, 0x6C};

Step 5: Upload the Receiver Code

// Complete Receiver Code

// Receiver Code – PWM Output Pins

#include <esp_now.h>

#include <WiFi.h>

// Structure to match transmitter’s data

typedef struct struct_message {

int sensor1;

int sensor2;

int sensor3;

int sensor4;

} struct_message;

struct_message sensorData;

// Optimal PWM output pin definitions

const int OUTPUT1_PIN = 13; // PWM Channel 0

const int OUTPUT2_PIN = 14; // PWM Channel 1

const int OUTPUT3_PIN = 26; // PWM Channel 3

const int OUTPUT4_PIN = 27; // PWM Channel 2

// PWM settings

const int PWM_FREQ = 16000; // 16 kHz frequency

const int PWM_RESOLUTION = 8; // 8-bit resolution (0-255)

// Updated callback function signature for ESP32 Core 3.x

void OnDataRecv(const esp_now_recv_info *info, const uint8_t *incomingData, int len) {

memcpy(&sensorData, incomingData, sizeof(sensorData));

// Debug output

Serial.print(“Sensor 1: “); Serial.println(sensorData.sensor1);

Serial.print(“Sensor 2: “); Serial.println(sensorData.sensor2);

Serial.print(“Sensor 3: “); Serial.println(sensorData.sensor3);

Serial.print(“Sensor 4: “); Serial.println(sensorData.sensor4);

Serial.println(“——————–“);

// Convert 12-bit ADC values (0-4095) to 8-bit PWM (0-255)

ledcWrite(OUTPUT1_PIN, sensorData.sensor1 / 16);

ledcWrite(OUTPUT2_PIN, sensorData.sensor2 / 16);

ledcWrite(OUTPUT3_PIN, sensorData.sensor3 / 16);

ledcWrite(OUTPUT4_PIN, sensorData.sensor4 / 16);

}

void setup() {

Serial.begin(115200);

delay(1000); // Give Serial time to initialize

// Set device as WiFi station and initialize

WiFi.mode(WIFI_STA);

WiFi.begin(); // Initialize WiFi without connecting to a network

delay(100); // Give WiFi time to initialize

// Display MAC address

Serial.print(“ESP Board MAC Address: “);

Serial.println(WiFi.macAddress());

// Initialize ESP-NOW

if (esp_now_init() != ESP_OK) {

Serial.println(“Error initializing ESP-NOW”);

return;

}

esp_now_register_recv_cb(OnDataRecv);

// Setup PWM using new API for ESP32 Core 3.x

ledcAttach(OUTPUT1_PIN, PWM_FREQ, PWM_RESOLUTION);

ledcAttach(OUTPUT2_PIN, PWM_FREQ, PWM_RESOLUTION);

ledcAttach(OUTPUT3_PIN, PWM_FREQ, PWM_RESOLUTION);

ledcAttach(OUTPUT4_PIN, PWM_FREQ, PWM_RESOLUTION);

Serial.println(“Receiver ready!”);

}

void loop() {

// Main loop does nothing – all handled in callback

}

  1. Create a new sketch in Arduino IDE (File > New Sketch)

  2. Copy and paste the complete receiver code from the document above.

  3. Save the file as “ESP32_Receiver”

  4. Upload it to the ESP32 that will be your receiver

  5. Press the EN to start running the code.

  6. Open Serial Monitor to verify it’s working. It should show MAC Address.

Now we need to initialize the receiver ESP32 and upload this Transmitter code.

Configure Arduino IDE for on the Receiver ESP32

All the Drivers and board information was previously setup. Nothing should need to be done.

Step 1: Upload the Transmitter Code.

  1. Copy and past code and the Save the file as “ESP32_Transmitter”
  2. Upload it to the ESP32 that will be your transmitter.

  3. Press the EN to start running the code.

  4. Open Serial Monitor to verify it’s working. It should show MAC Address.


// Complete Transmitter Code – Analog Sensors – Fixed for ESP32 Core v5.x

#include <esp_now.h>

#include <WiFi.h>

#include <esp_task_wdt.h>

// Replace with your receiver’s MAC address

uint8_t receiverAddress[] = {0xF4, 0x65, 0x0B, 0x56, 0x66, 0x6C};

// Structure for sensor data

typedef struct struct_message {

  int sensor1;

  int sensor2;

  int sensor3;

  int sensor4;

} struct_message;

struct_message sensorData;

esp_now_peer_info_t peerInfo;

// Analog sensor pin definitions (reordered to match physical layout)

const int SENSOR1_PIN = 33;  // ADC1_CH5 (bidirectional)

const int SENSOR2_PIN = 32;  // ADC1_CH4 (bidirectional)

const int SENSOR3_PIN = 35;  // ADC1_CH7 (input-only)

const int SENSOR4_PIN = 34;  // ADC1_CH6 (input-only)

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {

  Serial.print(“Last Packet Send Status: “);

  Serial.println(status == ESP_NOW_SEND_SUCCESS ? “Delivery Success” : “Delivery Fail”);

}

void setup() {

  Serial.begin(115200);

  // No pinMode needed for analog input

  // Set device as WiFi station

  WiFi.mode(WIFI_STA);

  // Initialize ESP-NOW

  if (esp_now_init() != ESP_OK) {

    Serial.println(“Error initializing ESP-NOW”);

    return;

  }

  // Register callback function

  esp_now_register_send_cb(OnDataSent);

  // Register peer

  memcpy(peerInfo.peer_addr, receiverAddress, 6);

  peerInfo.channel = 0;  

  peerInfo.encrypt = false;

  if (esp_now_add_peer(&peerInfo) != ESP_OK){

    Serial.println(“Failed to add peer”);

    return;

  }

  // Initialize watchdog timer using newer format

  esp_task_wdt_config_t twdt_config = {

    .timeout_ms = 5000,

    .idle_core_mask = 0,

    .trigger_panic = true

  };

  esp_task_wdt_init(&twdt_config);   // Initialize with config structure

  esp_task_wdt_add(NULL);            // Add current task to WDT

  Serial.println(“Transmitter ready!”);

  Serial.println(“Receiver MAC address: F4:65:0B:56:66:6C”);

}

void loop() {

  // Reset watchdog timer

  esp_task_wdt_reset();

  // Read analog values (0-4095)

  sensorData.sensor1 = analogRead(SENSOR1_PIN);

  sensorData.sensor2 = analogRead(SENSOR2_PIN);

  sensorData.sensor3 = analogRead(SENSOR3_PIN);

  sensorData.sensor4 = analogRead(SENSOR4_PIN);

  // Debug output

  Serial.printf(“Analog Values: %d, %d, %d, %d\n”,

                sensorData.sensor1,

                sensorData.sensor2,

                sensorData.sensor3,

                sensorData.sensor4);

  // Send data via ESP-NOW

  esp_err_t result = esp_now_send(receiverAddress, (uint8_t *) &sensorData, sizeof(sensorData));

  // Debug output for transmission status

  if (result == ESP_OK) {

    Serial.println(“Sent successfully”);

  } else {

    Serial.println(“Error sending data”);

  }

  delay(10);  // Send every 10ms (I should made it 1 for 1ms that will make the response time faster with less delay)

}

After Upload

  1. Open Serial Monitor (Tools > Serial Monitor), set baud rate to 115200

  2. You may need to push the EN (rest) button on the ESP board.

  3. You should see
    Sent successfully
    Analog Values, 111, 416, 0, 0

Hardware Wiring Instructions

Part 1: Hardware Wiring

Transmitter Board:

  1. Power connections:

    • 5V battery positive → ESP32 VIN pin

    • 5V battery ground → ESP32 GND pin

    • 5V battery positive → Breadboard positive power rail (+)

    • 5V battery ground → Breadboard ground rail (-)

  2. Proximity sensor connections:

    • All 4 sensors:

      • VCC → Breadboard positive rail (+)

      • GND → Breadboard ground rail (-)

    • Sensor 1 OUT → ESP32 pin D33

Right Proximity sensor

    • Sensor 2 OUT → ESP32 pin D32
      Front Proximity sensor

    • Sensor 3 OUT → ESP32 pin D35

Back Proximity sensor

    • Sensor 4 OUT → ESP32 pin D34

Left Proximity sensor


Wring Transmitter and Proximity Sensors.

Configuration:

Proximity Sensor Out → ESP32 GPIO pin (direct)

Proximity Sensor Out → LM358 Pin 3

LM358 Pin 2 → LM358 Pin 1 (feedback)

LM358 Pin 1 → Current limiting resistor → LED → GND

Connections

Op-amp 1 (Front)

Pin 3: Non-inverting input (+) – Connect your input signal from the proximity sensor here

Pin 2: Inverting input (-) – Connect this directly to

Pin 1 (output) connects to Pin 2 (inverting input) for feedback and also goes through a resistor to the LED.

Pin 4: Connect to GND
Pin 8: to positive supply

Op-amp 2 (Right)
Pin 5: Non-inverting input (+) – Connect your input signal from the proximity sensor here

Pin 6: Inverting input (-) – Connect this directly to

Pin 7 (output) connects to Pin 6 (inverting input) for feedback and also goes through a resistor to the LED.

Op-amp 1 (Left)

Pin 3: Non-inverting input (+) – Connect your input signal from the proximity sensor here

Pin 2: Inverting input (-) – Connect this directly to

Pin 1 (output) connects to Pin 2 (inverting input) for feedback and also goes through a resistor to the LED.

Pin 4: Connect to GND
Pin 8: to positive supply


Op-amp 2 (Back)
Pin 5: Non-inverting input (+) – Connect your input signal from the proximity sensor here

Pin 6: Inverting input (-) – Connect this directly to

Pin 7 (output) connects to Pin 6 (inverting input) for feedback and also goes through a resistor to the LED.


Wiring the Receiver Board:

  1. Power connections:

    • 5V power supply positive → ESP32 VIN pin

    • 5V power supply ground → ESP32 GND pin

  2. Output connections (optional – for testing):

    • D13, Right Proximity sensor PWM Output

    • D14, Front Proximity sensor PWM Output

    • D26, Back Proximity sensor PWM Output

    • D27, Left Proximity sensor PWM Output

How to Build Artificial Neurons

The video below shows how to build artificial neurons on breadboards using individual transistors.

The capacitor of the artificial neurons works like the dendrites and body of biological neurons which receive charge and integrate the charge until a voltage threshold is reached. Once the threshold is reached the capacitor discharges allowing the synapses of that neuron to transfer charge to all the postsynaptic neurons that connect to the firing neuron.

The goal of this video is to teach the basic of how these circuits work and to inspire others to work on hardware based neural networks. Hopefully we can work together to build awesome future projects!

How to build an Artificial Synapse

The video below shows how to build artificial synapses on breadboards using LEDs as an optocoupler.

Most biological neurons have thousands of connections to other synapses and each connection forms a synapse. When building artificial neurons artificial synapses are likely needed to connect to other neurons.

Each synapse consists of an inverter, an optocoupler made with two LEDs, an output buffer, a diode, and a variable resistor. If the synapse is inhibitory, a discharge transistor also needs to be added.

Each synapse either adds or removes charge from the postsynaptic neuron. For these synapses to be functionally equivalent to biological sells a proportional number of states needs to be transferred compared to the same biological network. The video above describes this in much more detail.

Leave a Comment