Hallo,
Ich heiße Abhay Kant. Ich lebe in Delhi, India. Meine mutter ist Lehrerin.
Ich habe ein website. Mein website heiße exasub.com. Ich habe degree in Electroniks und kommunikation Ingenieurwesen. Dort schreibe ich über Halbleiterbauelemente und Mikrocontroller. Ich schreibe kleinen Code in C-Sprache
Ich kennen drei sprachen Englisch, Hindi und deutsch.
Most “AI robot face” demos rely on heavy machine-learning models, cloud APIs, or large datasets. This project explores a different approach:
Using simple geometry and browser-based vision to create an expressive robot head that reacts to human movement in real time.
The robot head tracks left, right, up, and down head motion and mirrors it using animated eyes displayed on a small OLED screen — all controlled directly from a web browser.
No dataset. No model training. No server.
What This Project Does
Detects a human face using the browser camera
Estimates head orientation (yaw & pitch)
Sends motion data to an Arduino using Web Serial
Animates eye movement on a 0.96″ OLED display
Allows direction inversion (mirror correction) at runtime
The result is a small robot head that feels responsive and alive.
System Architecture
Browser Camera
↓
Face Geometry (MediaPipe)
↓
Yaw & Pitch Calculation
↓
Web Serial (USB)
↓
Arduino UNO
↓
OLED Eye Animation
All computation happens locally in the browser.
Hardware Used
Arduino UNO
0.96″ OLED (SSD1306, I2C)
USB cable
3D-printed enclosure
Software Stack
HTML + JavaScript
MediaPipe Face Mesh
Web Serial API
Arduino (C++)
Key Design Decisions
1. Pupils Move, Not Eyeballs
Moving only the pupils inside fixed eyeballs makes the face feel more natural and expressive.
2. Face-Relative Geometry
Head motion is measured relative to facial landmarks, not camera pixels. This makes movement symmetric and stable.
3. Runtime Direction Flip
A toggle button allows instant correction for mirrored cameras without changing code.
Educational Value
This project can be used to teach:
Coordinate systems
Geometry-based tracking
Browser ↔ hardware communication
Human-centered design
It is suitable for classrooms, labs, and exhibitions.
Conclusion
This robot head demonstrates that intelligence is not just about models, but about understanding interaction.
By combining browser vision, simple math, and embedded hardware, we can build systems that feel responsive, expressive, and intuitive — without complexity.
Try It Live
Allow camera access, connect the Arduino, and move your head left, right, up, and down.
An RGB LED is a special type of LED that contains three LEDs inside one package — Red, Green, and Blue. Using these three colours, the Arduino can blend them at different brightness levels to produce hundreds of colours like yellow, cyan, purple, pink, white, sky-blue, etc.
In this example, we are using a Common-Cathode RGB LED, which means:
All three LEDs share one common negative (–) pin
The individual colour pins (R, G, B) must be connected through 220Ω resistors
The Arduino controls brightness using PWM pins
Red → D3
Green → D5
Blue → D6
This forms the basis for colour-mixing projects used in IoT, indicators, robots, and UI feedback systems.
Below, you can see the circuit wiring diagram, the mBlock representation, and the equivalent Arduino IDE code. Finally, you can flash the firmware directly from this page using ExaSub’s online uploader — no Arduino IDE installation required.
Open circuit
Arduino IDE CODE
// RGB LED with Arduino Nano
// Common Cathode RGB LED
// R -> D3, G -> D5, B -> D6
int redPin = 3;
int greenPin = 5;
int bluePin = 6;
void setup() {
pinMode(redPin, OUTPUT);
pinMode(greenPin, OUTPUT);
pinMode(bluePin, OUTPUT);
}
void loop() {
setColor(255, 0, 0); // Red
delay(800);
setColor(0, 255, 0); // Green
delay(800);
setColor(0, 0, 255); // Blue
delay(800);
setColor(255, 255, 0); // Yellow (Red + Green)
delay(800);
setColor(80, 0, 255); // Purple
delay(800);
setColor(0, 255, 255); // Cyan (Green + Blue)
delay(800);
setColor(255, 255, 255); // White
delay(800);
setColor(0, 0, 0); // Off
delay(800);
}
// Function to mix RGB
void setColor(int r, int g, int b) {
analogWrite(redPin, 255 - r); // common cathode → invert logic
analogWrite(greenPin, 255 - g);
analogWrite(bluePin, 255 - b);
}
Mblock Code
Flash Arduino UNO/Nano Directly From Browser Click on Button
Note: Nano with old bootloader is not supported. Use the code with the arduino IDE for the Nano with old bootloader.
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 VCC → ESP32 3.3V
Joystick GND → ESP32 GND
Joystick X-axis → ESP32 GPIO 34
Joystick Y-axis → ESP32 GPIO 35
Car (NodeMCU + L298N Motor Driver + Motors)
Motor 1 IN1 → NodeMCU D1
Motor 1 IN2 → NodeMCU D2
Motor 2 IN1 → NodeMCU D3
Motor 2 IN2 → NodeMCU D4
Motor 1 ENA → NodeMCU D5
Motor 2 ENB → NodeMCU D6
LM2596 Output (3.3V/5V) → NodeMCU Vin
LM2596 Ground → NodeMCU 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
Upload the remote code to your ESP32.
Upload the car code to your NodeMCU.
Power up both devices and check serial monitor outputs for debugging.
Testing the Car
Move the joystick forward → The car moves forward.
Move the joystick backward → The car moves backward.
Move the joystick left → The car turns left.
Move the joystick right → The car turns right.
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.
I bought the printer from wol3d’s New Delhi office. I am a electronics guy but i have inclination towards the 3d printing world. The feeling that you can design and make something in front of you. It’s a slow process but this printer comes with a slicer software which is fast.
Build Volume : 220 x 220 x 240 mm
The best thing is the auto bed leveling on this printer. I do not have to adjust the four corner bolts and rub a paper under the nozzle. It is done automatically done.
Till now the results are amazing.
I have noticed that the prints depends upon a lot of things:
Filament Quality : I have used a few filaments samples i got my hand on they were old and new. The old one which are above one year old open packet are just brittle they clog the printer every now and then. and one time it was so break while in the extruder which set my heart racing as i don’t want new printer to clog and change its nozzle. Thankfully the nozzle clogged removed when i inserted the fresh PLA and forced it into the extruder. So yes always use new filament. And also some filament are bad weather they are new or old.
#define led 2
void setup() {
// put your setup code here, to run once:
pinMode(2, OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
digitalWrite(led, 1);
delay(1000);
digitalWrite(led, 0);
delay(200);
}
OpenCV, or Open Source Computer Vision Library, is a powerful and widely-used open-source computer vision and machine learning software library. It provides a plethora of tools and functions for image and video processing, making it an essential resource for developers, researchers, and hobbyists. Installing OpenCV on a Windows system can be a straightforward process when using the Python package manager, pip. In this article, we’ll walk you through the steps to install OpenCV on your Windows machine using pip.
Prerequisites:
Before diving into the installation process, ensure that you have the following prerequisites:
A Windows operating system.
Python installed on your machine. You can download the latest version of Python from the official website: https://www.python.org/downloads/.
Installation Steps:
Follow these step-by-step instructions to install OpenCV using pip in Windows:
Step 1: Open a Command Prompt
Press Win + R to open the Run dialog, type cmd, and press Enter. This will open the Command Prompt.
Step 2: Upgrade pip
Ensure that your pip is up-to-date by running the following command:
pip install --upgrade pip
This ensures that you have the latest version of pip installed.
Step 3: Install NumPy
NumPy is a prerequisite for OpenCV, as it is used for numerical operations. Install it by running:
pip install numpy
Step 4: Install OpenCV
Now, you can install the OpenCV package using the following command:
pip install opencv-python
This command will download and install the latest stable version of OpenCV along with its dependencies.
Step 5: Verify the Installation
To ensure that OpenCV has been successfully installed, open a Python interpreter or create a simple Python script and import the cv2 module:
import cv2
print(cv2.__version__)
This should print the installed OpenCV version, confirming that the installation was successful.
#define m1p 7
#define m1n 6
#define m2p 5
#define m2n 4
#define echopin 9
#define trigpin 10
void motor_forward();
void motor_stop();
void motor_left();
void motor_right();
void motor_back();
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(m1p, OUTPUT);
pinMode(m1n, OUTPUT);
pinMode(m2p, OUTPUT);
pinMode(m2n, OUTPUT);
pinMode(echopin, INPUT);
pinMode(trigpin, OUTPUT);
}
long duration, distance;
void loop() {
// put your main code here, to run repeatedly:
digitalWrite(trigpin, LOW);
delayMicroseconds(2);
digitalWrite(trigpin, HIGH);
delayMicroseconds(10);
digitalWrite(trigpin, LOW);
duration = pulseIn(echopin, HIGH);
distance = (duration *0.0343 / 2);
Serial.println(distance);
// Serial.println(" cm");
//delay(1000);
//Serial.println(distance); //print data serially
if (distance > 25) { // if distance is more than 25 move bot forward
motor_forward();
delay(20);
motor_forward();
}
else {
// motor_stop();
//delay(500);
// motor_back();
// delay(3000);
motor_stop(); // if distance is less than 25 move bot right
delay(50);
motor_back();
delay(100);
motor_right();
delay(100);
}
}
void motor_back(){
digitalWrite(m1p, LOW);
digitalWrite(m1n, HIGH);
digitalWrite(m2p, HIGH);
digitalWrite(m2n, LOW);
}
void motor_forward(){
digitalWrite(m1p, HIGH);
digitalWrite(m1n, LOW);
digitalWrite(m2p, LOW);
digitalWrite(m2n, HIGH);
}
void motor_stop(){
digitalWrite(m1p, LOW);
digitalWrite(m1n, LOW);
digitalWrite(m2p, LOW);
digitalWrite(m2n, LOW);
}
void motor_left(){
digitalWrite(m1p, HIGH);
digitalWrite(m1n, LOW);
digitalWrite(m2p, LOW);
digitalWrite(m2n, LOW);
}
void motor_right(){
digitalWrite(m1p, LOW);
digitalWrite(m1n, LOW);
digitalWrite(m2p, HIGH);
digitalWrite(m2n, LOW);
}
Code Explanation
The code is a simple obstacle avoidance program for a robot using an ultrasonic sensor and two motors. Let’s break down the code and explain each section:
In this section, the code defines constants for motor pins (m1p, m1n, m2p, m2n) and pins for the ultrasonic sensor (echopin for echo and trigpin for trigger).
Here, the code declares five functions (motor_forward, motor_stop, motor_left, motor_right, motor_back) that will be used to control the movement of the robot.
In the loop function, the ultrasonic sensor is triggered to measure the distance. If the measured distance is greater than 25, the robot moves forward. Otherwise, it stops, moves back a bit, and then turns right.
These functions define the motor control logic for moving the robot in different directions. For example, motor_forward makes the robot move forward by setting the appropriate motor pins.
In summary, this code implements a basic obstacle avoidance mechanism for a robot using an ultrasonic sensor. If the robot detects an obstacle within 25 cm, it stops, moves back a bit, and then turns right. Otherwise, it continues moving forward. The motor control is achieved through the defined functions that set the appropriate pin states for the motor driver.
Arduino, with its user-friendly environment and a vast array of libraries, opens up a world of possibilities for electronic enthusiasts and hobbyists. One of the key features that makes Arduino a versatile platform is the ability to use interrupts. In this blog post, we will explore the use of attachInterrupt() in the Arduino IDE to toggle an LED with the press of a button.
Understanding attachInterrupt()
The attachInterrupt() function in Arduino is a powerful tool that allows you to execute a specified function (interrupt service routine or ISR) when a certain condition occurs on a digital pin. This capability is particularly useful for handling external events without constantly polling the pin in the main loop, thus improving efficiency and responsiveness.
Before we delve into the code, let’s gather the necessary components for this project:
Arduino board (e.g., Arduino Uno)
LED
Resistor (220-330 ohms)
Push button
Jumper wires
Wiring the Circuit
Connect the components as follows:
Connect the longer leg of the LED (anode) to a current-limiting resistor (220-330 ohms) then the other end of the resistor to pin A2 on the Arduino.
Connect the shorter leg of the LED (cathode) GND on the Arduino.
Connect one side of the push button to pin 2 on the Arduino.
Connect the other side of the push button to the GND on the Arduino.
The Arduino Sketch
Now, let’s dive into the Arduino sketch that utilizes attachInterrupt() to toggle the LED state when the button is pressed.
int led=A2;
int button=2;
volatile byte state = LOW;
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
pinMode(led, OUTPUT);
pinMode(button, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(2),blink,CHANGE);
}
void blink()
{
state = !state;
}
void loop()
{
digitalWrite(led, !state);
}
Breaking Down the Code
We define led and button as the respective pin numbers for the LED and the push button.
volatile byte state is a variable that holds the state of the LED, and it’s marked as volatile to indicate that it can be modified in an ISR.
In the setup() function, we set the LED pin and button pin modes. Additionally, we attach an interrupt to the button using attachInterrupt(), specifying the function blink to be executed on a state change (CHANGE) of the button pin.
The blink() function toggles the state variable when the interrupt is triggered.
In the loop() function, we continuously update the LED state based on the value of the state variable.
From the schematics, we see that there are three pins associated with the USB port. 1. PA11 2. PA12 3. PD2
The Pins PA11 and PA12 are PA11 – USB D- PA12 – USB D+
These two pins will be configured by the stm32cube ide when you enable the USB device.
PD2 should be configured as GPIO pin. Because the USB FS implementation says to pull up the D+ line to 3.3V. The pull up is performed by the S8550 PNP transistor. So by making the PD2 pin LOW, we enable the USB FS, since it makes the D+ pull up.
We also need to select the Communication Device Class as Class For FS IP.
After configuring the cube mx project. we can proceed to generate code.
The code needs to add the following header file
/* USER CODE BEGIN Includes */
#include "usbd_cdc_if.h"
#include "string.h"
/* USER CODE END Includes */
and then in the main() function. you can write this code in while loop
/* USER CODE BEGIN 2 */
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
uint8_t *data = "Hello World from USB CDC\r\n";
while (1)
{
CDC_Transmit_FS(data, strlen(data));
HAL_Delay (1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
After uploading the code to your microcontroller. It will be displayed in your windows device manager as “USB Serial Device”.
You can Connect to the Com port using the serial terminal software such as YAT, Putty or CoolTerm etc. Note: Since it is a virtual com port, you dont have to set a specific baud rate.