Posted on Leave a comment

Smart City Mini Project Using Arduino UNO

(Street Light Automation, Smart Gate & Air Quality Alert)

Smart cities are built by combining automation, sensing, and decision-making.
In this project, a Smart City model was developed using an Arduino UNO and commonly available sensors to demonstrate how embedded systems can be used for urban automation.

This project focuses on real-world logic, not just blinking LEDs.


📌 Project Demonstration Video

🎥 Watch the complete working demonstration here:

👉 The video shows the automatic street lights, smart gate operation, and air quality alert system working in real time.


Project Overview

The model demonstrates four smart city subsystems working together:

  1. Automatic Street Light System (LDR based)
  2. Smart Entry Gate / Traffic Control (Ultrasonic + Servo)
  3. Air Quality Monitoring System (MQ135 sensor)
  4. Alert System (Buzzer for pollution warning)

Each subsystem works independently but is controlled by a single Arduino UNO, simulating how real smart-city nodes operate.


Components Used

  • Arduino UNO
  • LDR (Light Dependent Resistor – discrete, not module)
  • Ultrasonic Sensor (HC-SR04)
  • Servo Motor (gate control)
  • MQ135 Air Quality Sensor
  • Buzzer
  • LEDs (street lights)
  • 330 Ω resistors (current limiting)
  • Breadboard, jumper wires, external LED wiring

🔌 Circuit Diagram

🧩 Complete circuit diagram for this project:

Open circuit Open circuit

The circuit includes:

  • LDR voltage divider connected to an analog input
  • Ultrasonic sensor connected to digital pins
  • Servo motor connected to a PWM pin
  • MQ135 sensor connected to an analog input
  • Buzzer connected to a digital output
  • LED street lights driven through resistors

Street Light Implementation (Important Design Detail)

To simulate street lights, multiple LEDs were used.

Instead of driving LEDs directly from the Arduino pin, the following safe current-limiting method was implemented:

  • Two GPIO pins were used for street lighting
  • Each GPIO pin was connected through a 330 Ω resistor
  • The other end of each resistor was connected to 4–5 LEDs in parallel

Why this method was used

  • The resistor limits the current drawn from the GPIO pin
  • Using two GPIOs distributes the load instead of stressing a single pin
  • Parallel LEDs represent multiple street lights connected to one junction

This approach keeps the circuit safe for the Arduino while still allowing multiple LEDs to turn ON together during night conditions.


Automatic Street Light Logic (LDR)

  • The LDR continuously measures ambient light
  • During daytime:
    • LDR value is high
    • Street LEDs remain OFF
  • During night:
    • LDR value drops below a threshold
    • Street LEDs turn ON automatically

This demonstrates energy conservation, a core principle of smart cities.


Smart Gate / Traffic Control System

An ultrasonic sensor is placed near the entry point of the model.

  • When a vehicle or object comes within a fixed distance:
    • The ultrasonic sensor detects it
    • The servo motor rotates
    • The gate opens automatically
  • When the object moves away:
    • The gate closes again

This simulates automatic toll gates, parking entry systems, and traffic barriers.


Air Quality Monitoring (MQ135 Sensor)

To introduce environmental monitoring, an MQ135 air quality sensor was added.

  • The sensor measures pollution levels in the surrounding air
  • When the pollution value crosses a preset threshold:
    • The buzzer turns ON
    • This acts as an air pollution warning

This models how pollution monitoring stations work in smart cities.


Alert System (Buzzer)

The buzzer is used as a city alert mechanism.

  • OFF during normal air conditions
  • ON when pollution exceeds safe limits

This introduces the concept of public warning systems and real-time environmental alerts.


💻 Firmware Upload (Arduino Code)

Flash the firmware directly to Arduino UNO:

👉 This allows users to upload the tested firmware without opening the Arduino IDE.

#include <Servo.h>

#define TRIG_PIN 9
#define ECHO_PIN 8
#define LED_PIN  3
#define LED_PIN1 5
#define LDR_PIN  A0
#define MQ135_PIN A1
#define BUZZER_PIN 7
#define SERVO_PIN 6

Servo gateServo;

long duration;
int distance;
int ldrValue;
int airValue;

int LDR_THRESHOLD = 500;     // adjust after testing
int DIST_THRESHOLD = 15;     // cm
int AIR_THRESHOLD  = 400;    // MQ135 threshold (adjust)

void setup() {
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  pinMode(LED_PIN1, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);

  gateServo.attach(SERVO_PIN);
  gateServo.write(90);   // gate closed

  Serial.begin(9600);
  Serial.println("Smart City System Starting...");
}

void loop() {

  /* ---------- LDR STREET LIGHT ---------- */
  ldrValue = analogRead(LDR_PIN);

  if (ldrValue < LDR_THRESHOLD) {
    analogWrite(LED_PIN, 255);     // street light ON
    digitalWrite(LED_PIN1, HIGH);
  } else {
    digitalWrite(LED_PIN, LOW);    // street light OFF
    digitalWrite(LED_PIN1, LOW);
  }

  /* ---------- ULTRASONIC SENSOR ---------- */
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  duration = pulseIn(ECHO_PIN, HIGH, 30000);
  distance = duration * 0.034 / 2;

  /* ---------- SERVO GATE CONTROL ---------- */
  if (distance > 0 && distance < DIST_THRESHOLD) {
    gateServo.write(0);    // open gate
  } else {
    gateServo.write(90);   // close gate
  }

  /* ---------- MQ135 AIR QUALITY ---------- */
  airValue = analogRead(MQ135_PIN);

  if (airValue > AIR_THRESHOLD) {
    digitalWrite(BUZZER_PIN, HIGH);   // pollution alert
  } else {
    digitalWrite(BUZZER_PIN, LOW);
  }

  /* ---------- SERIAL MONITOR ---------- */
  Serial.print("LDR: ");
  Serial.print(ldrValue);
  Serial.print(" | Distance: ");
  Serial.print(distance);
  Serial.print(" cm | Air: ");
  Serial.println(airValue);

  delay(1000);
}

Software Logic Summary

The Arduino program continuously performs:

  • Ambient light measurement (LDR)
  • Distance measurement (Ultrasonic)
  • Air quality measurement (MQ135)
  • Decision-making based on thresholds
  • Actuator control (LEDs, servo, buzzer)

All logic runs in a loop, similar to how embedded systems operate in real infrastructure.


Educational Value

This project helps learners understand:

  • GPIO usage and current limiting
  • Sensor interfacing and calibration
  • Real-world automation logic
  • Embedded decision-making
  • Power and safety considerations
  • Smart city concepts beyond theory

Unlike ready-made kits, this setup uses discrete components, encouraging deeper understanding and troubleshooting skills.


Conclusion

This Smart City project demonstrates how simple electronics and embedded programming can be combined to address urban automation challenges such as energy efficiency, traffic control, and pollution monitoring.

It serves as a reference implementation for students and educators exploring smart-city concepts using Arduino.

Posted on Leave a comment

Arduino Uno Dot Matrix Display Using MAX7219 (Scrolling Text)

In this project, we interface an 8×8 Dot Matrix Display (MAX7219) with Arduino Uno to display scrolling text using the MD_Parola library.

This setup is ideal for learning:

  • SPI communication
  • External display control
  • Library-based programming
  • Real-time visual output from code

All components, wiring, code, and flashing are available on a single page for smooth lab execution.


Circuit Diagram (Arduino Uno + MAX7219)

The circuit below shows the exact wiring required between the Arduino Uno and the dot matrix module.

👉 Open this on the board or laptop and follow the connections directly.

Open circuit Open circuit

Pin Connections

MAX7219 ModuleArduino Uno
VCC5V
GNDGND
DIND11
CS / LOADD10
CLKD13

Output Preview

The following video shows the expected output after successful wiring and code upload.

📺 Demo Video
https://youtube.com/shorts/sZoBYKpf11I


Required Libraries

This project uses the following Arduino libraries:

  • MD_MAX72XX
  • MD_Parola

How to Install Libraries

  1. Open Arduino IDE
  2. Go to Sketch → Include Library → Manage Libraries
  3. Search and install:
    • MD_MAX72XX
    • MD_Parola
  4. Restart Arduino IDE after installation

If libraries are missing, the code will not compile.


Arduino Uno Code (Scrolling Text)

Copy and paste the following code into Arduino IDE:

#include <MD_Parola.h>
#include <MD_MAX72XX.h>
#include <SPI.h>

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 1   // Number of dot matrix modules
#define CS_PIN 10       // CS (LOAD) pin

MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

void setup() {
  P.begin();
  P.displayText("HELLO!", PA_CENTER, 70, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}

void loop() {
  if (P.displayAnimate()) {
    P.displayReset();
  }
}

Uploading Code to Arduino Uno

  1. Connect Arduino Uno using USB cable
  2. Select:
    • Board: Arduino Uno
    • Port: Correct COM port
  3. Click Upload

One-Click Arduino Flash (Recommended)

To avoid IDE and driver issues, use the flash button below to upload firmware directly.


Common Issues and Fixes

  • No display output
    • Check VCC and GND
    • Verify CS pin is set to 10 in code
  • Compilation error
    • Confirm both libraries are installed
  • Display orientation incorrect
    • Ensure FC16_HW hardware type is selected

Next Steps

You can extend this project by:

  • Adding a joystick to control text movement
  • Changing text dynamically
  • Using multiple dot matrix modules
  • Creating interactive animations

This project forms a strong foundation for input + output based embedded systems.

Posted on Leave a comment

AI Robot Head Tracking Using Browser Vision (No Training, No Server)

Turn your head left, right, up, and down.
Open circuit Open circuit

Download FreeCAD project and STL files

Introduction

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.

Posted on Leave a comment

RGB LED with Arduino UNO/Nano

What is an RGB LED and What Are We Building Here?

An RGB LED is a special type of LED that contains three LEDs inside one packageRed, 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 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.

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 make an Obstacle Avoiding Vehicle using Arduino UNO

Arduino Sketch

#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:

#define m1p 7
#define m1n 6
#define m2p 5
#define m2n 4
#define echopin 9
#define trigpin 10

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).

void motor_forward();
void motor_stop();
void motor_left();
void motor_right();
void motor_back();

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.

void setup() {
  Serial.begin(9600);
  pinMode(m1p, OUTPUT);
  pinMode(m1n, OUTPUT);
  pinMode(m2p, OUTPUT);
  pinMode(m2n, OUTPUT);
  pinMode(echopin, INPUT);
  pinMode(trigpin, OUTPUT);
}

In the setup function, the serial communication is initialized, and the pin modes for motor control and the ultrasonic sensor are set.

long duration, distance;

Here, two variables duration and distance are declared to store the duration of the ultrasonic pulse and the calculated distance, respectively.

void loop() {
  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);

  if (distance > 25) {
    motor_forward();
    delay(20);
    motor_forward(); 
  } else {
    motor_stop();
    delay(50);
    motor_back();
    delay(100);
    motor_right();
    delay(100);
  }
}

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.

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);
}

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.

Posted on Leave a comment

How to use attachInterrupt() in Arduino IDE to toggle an LED

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.

For more documentation visit the lin below.

https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

Components Required

Before we delve into the code, let’s gather the necessary components for this project:

  1. Arduino board (e.g., Arduino Uno)
  2. LED
  3. Resistor (220-330 ohms)
  4. Push button
  5. 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.
Posted on Leave a comment

How to use Mini STM32 v3.0 USB Port as Virtual Com Port

To use USB port of the mini STM32 v3. We need to configure the USB port and for that, we have to look at the schematics.

You can view the Full schematic here https://www.exasub.com/development-kit/mini-stm32-v3-0/mini-stm32-v3-0/

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.

Posted on Leave a comment

Raspberry Pi Pico W as Bluetooth Low Energy Central Device and Peripheral Device

This is a very simple demonstration of the Unsecured Bluetooth Low Energy technology.

I am using two Raspberry Pi Pico W for this.
One will be operated in Central Role and the other will be in the Peripheral Role.

The peripheral device will advertise the temperature data of the rp2040 chip.

The Central device will scan the surrounding and connect to the peripheral device to receive the temperature data.
The central device does not use any passcode or pairing methods to connect to the peripheral device.

Note: You will need ble_advertising.py
You can check the code How to use Bluetooth LE of Raspberry Pi Pico W using MicroPython

ble_Central_device.py
# This example finds and connects to a peripheral running the
# UART service (e.g. ble_simple_peripheral.py).

import bluetooth
import random
import struct
import time
import micropython

from ble_advertising import decode_services, decode_name

from micropython import const

_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)
_IRQ_GATTS_READ_REQUEST = const(4)
_IRQ_SCAN_RESULT = const(5)
_IRQ_SCAN_DONE = const(6)
_IRQ_PERIPHERAL_CONNECT = const(7)
_IRQ_PERIPHERAL_DISCONNECT = const(8)
_IRQ_GATTC_SERVICE_RESULT = const(9)
_IRQ_GATTC_SERVICE_DONE = const(10)
_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11)
_IRQ_GATTC_CHARACTERISTIC_DONE = const(12)
_IRQ_GATTC_DESCRIPTOR_RESULT = const(13)
_IRQ_GATTC_DESCRIPTOR_DONE = const(14)
_IRQ_GATTC_READ_RESULT = const(15)
_IRQ_GATTC_READ_DONE = const(16)
_IRQ_GATTC_WRITE_DONE = const(17)
_IRQ_GATTC_NOTIFY = const(18)
_IRQ_GATTC_INDICATE = const(19)

_ADV_IND = const(0x00)
_ADV_DIRECT_IND = const(0x01)
_ADV_SCAN_IND = const(0x02)
_ADV_NONCONN_IND = const(0x03)

_UART_SERVICE_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
_UART_RX_CHAR_UUID = bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E")
_UART_TX_CHAR_UUID = bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")


class BLESimpleCentral:
    def __init__(self, ble):
        self._ble = ble
        self._ble.active(True)
        self._ble.irq(self._irq)

        self._reset()

    def _reset(self):
        # Cached name and address from a successful scan.
        self._name = None
        self._addr_type = None
        self._addr = None

        # Callbacks for completion of various operations.
        # These reset back to None after being invoked.
        self._scan_callback = None
        self._conn_callback = None
        self._read_callback = None

        # Persistent callback for when new data is notified from the device.
        self._notify_callback = None

        # Connected device.
        self._conn_handle = None
        self._start_handle = None
        self._end_handle = None
        self._tx_handle = None
        self._rx_handle = None

    def _irq(self, event, data):
        if event == _IRQ_SCAN_RESULT:
            addr_type, addr, adv_type, rssi, adv_data = data
            if adv_type in (_ADV_IND, _ADV_DIRECT_IND) and _UART_SERVICE_UUID in decode_services(
                adv_data
            ):
                # Found a potential device, remember it and stop scanning.
                self._addr_type = addr_type
                self._addr = bytes(
                    addr
                )  # Note: addr buffer is owned by caller so need to copy it.
                self._name = decode_name(adv_data) or "?"
                self._ble.gap_scan(None)

        elif event == _IRQ_SCAN_DONE:
            if self._scan_callback:
                if self._addr:
                    # Found a device during the scan (and the scan was explicitly stopped).
                    self._scan_callback(self._addr_type, self._addr, self._name)
                    self._scan_callback = None
                else:
                    # Scan timed out.
                    self._scan_callback(None, None, None)

        elif event == _IRQ_PERIPHERAL_CONNECT:
            # Connect successful.
            conn_handle, addr_type, addr = data
            if addr_type == self._addr_type and addr == self._addr:
                self._conn_handle = conn_handle
                self._ble.gattc_discover_services(self._conn_handle)

        elif event == _IRQ_PERIPHERAL_DISCONNECT:
            # Disconnect (either initiated by us or the remote end).
            conn_handle, _, _ = data
            if conn_handle == self._conn_handle:
                # If it was initiated by us, it'll already be reset.
                self._reset()

        elif event == _IRQ_GATTC_SERVICE_RESULT:
            # Connected device returned a service.
            conn_handle, start_handle, end_handle, uuid = data
            print("service", data)
            if conn_handle == self._conn_handle and uuid == _UART_SERVICE_UUID:
                self._start_handle, self._end_handle = start_handle, end_handle

        elif event == _IRQ_GATTC_SERVICE_DONE:
            # Service query complete.
            if self._start_handle and self._end_handle:
                self._ble.gattc_discover_characteristics(
                    self._conn_handle, self._start_handle, self._end_handle
                )
            else:
                print("Failed to find uart service.")

        elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT:
            # Connected device returned a characteristic.
            conn_handle, def_handle, value_handle, properties, uuid = data
            if conn_handle == self._conn_handle and uuid == _UART_RX_CHAR_UUID:
                self._rx_handle = value_handle
            if conn_handle == self._conn_handle and uuid == _UART_TX_CHAR_UUID:
                self._tx_handle = value_handle

        elif event == _IRQ_GATTC_CHARACTERISTIC_DONE:
            # Characteristic query complete.
            if self._tx_handle is not None and self._rx_handle is not None:
                # We've finished connecting and discovering device, fire the connect callback.
                if self._conn_callback:
                    self._conn_callback()
            else:
                print("Failed to find uart rx characteristic.")

        elif event == _IRQ_GATTC_WRITE_DONE:
            conn_handle, value_handle, status = data
            print("TX complete")

        elif event == _IRQ_GATTC_NOTIFY:
            conn_handle, value_handle, notify_data = data
            if conn_handle == self._conn_handle and value_handle == self._tx_handle:
                if self._notify_callback:
                    self._notify_callback(notify_data)

    # Returns true if we've successfully connected and discovered characteristics.
    def is_connected(self):
        return (
            self._conn_handle is not None
            and self._tx_handle is not None
            and self._rx_handle is not None
        )

    # Find a device advertising the environmental sensor service.
    def scan(self, callback=None):
        self._addr_type = None
        self._addr = None
        self._scan_callback = callback
        self._ble.gap_scan(2000, 30000, 30000)

    # Connect to the specified device (otherwise use cached address from a scan).
    def connect(self, addr_type=None, addr=None, callback=None):
        self._addr_type = addr_type or self._addr_type
        self._addr = addr or self._addr
        self._conn_callback = callback
        if self._addr_type is None or self._addr is None:
            return False
        self._ble.gap_connect(self._addr_type, self._addr)
        return True

    # Disconnect from current device.
    def disconnect(self):
        if self._conn_handle is None:
            return
        self._ble.gap_disconnect(self._conn_handle)
        self._reset()

    # Send data over the UART
    def write(self, v, response=False):
        if not self.is_connected():
            return
        self._ble.gattc_write(self._conn_handle, self._rx_handle, v, 1 if response else 0)

    # Set handler for when data is received over the UART.
    def on_notify(self, callback):
        self._notify_callback = callback


def demo():
    

    not_found = False

    def on_scan(addr_type, addr, name):
        if addr_type is not None:
            print("Found peripheral:", addr_type, addr, name)
            central.connect()
        else:
            nonlocal not_found
            not_found = True
            print("No peripheral found.")

    central.scan(callback=on_scan)

    # Wait for connection...
    while not central.is_connected():
        time.sleep_ms(100)
        if not_found:
            return

    print("Connected")

    def on_rx(v):
        buf1 = bytearray(v)
        print("RX ")
        for _ in v:
            print(chr(_), end='')
        print("")

    central.on_notify(on_rx)

    #with_response = False
    with_response = True

    i = 0
    while central.is_connected():
        try:
            v = str(i) + "_"
            print("TX", v)
            central.write(v, with_response)
        except:
            print("TX failed")
        i += 1
        time.sleep_ms(2000 if with_response else 30)

    print("Disconnected")


if __name__ == "__main__":
    ble = bluetooth.BLE()
    central = BLESimpleCentral(ble)
    while(1):
        demo()
ble_Peripheral_device.py
# This example demonstrates a UART periperhal.

import bluetooth
import random
import struct
import time
from ble_advertising import advertising_payload

from micropython import const

def Temp_sensor():
    # Configure the internal temperature sensor
    sensor_temp = machine.ADC(machine.ADC.CORE_TEMP)
    conversion_factor = 3.3 / 65535
    raw_temp = sensor_temp.read_u16() * conversion_factor
    temperature = (27 - (raw_temp - 0.706) / 0.001721)
    return temperature

_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)

_FLAG_READ = const(0x0002)
_FLAG_WRITE_NO_RESPONSE = const(0x0004)
_FLAG_WRITE = const(0x0008)
_FLAG_NOTIFY = const(0x0010)

_UART_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
_UART_TX = (
    bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"),
    _FLAG_READ | _FLAG_NOTIFY,
)
_UART_RX = (
    bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"),
    _FLAG_WRITE | _FLAG_WRITE_NO_RESPONSE,
)
_UART_SERVICE = (
    _UART_UUID,
    (_UART_TX, _UART_RX),
)


class BLESimplePeripheral:
    def __init__(self, ble, name="mpy-uart"):
        self._ble = ble
        self._ble.active(True)
        self._ble.irq(self._irq)
        ((self._handle_tx, self._handle_rx),) = self._ble.gatts_register_services((_UART_SERVICE,))
        self._connections = set()
        self._write_callback = None
        self._payload = advertising_payload(name=name, services=[_UART_UUID])
        self._advertise()

    def _irq(self, event, data):
        # Track connections so we can send notifications.
        if event == _IRQ_CENTRAL_CONNECT:
            conn_handle, _, _ = data
            print("New connection", conn_handle)
            self._connections.add(conn_handle)
        elif event == _IRQ_CENTRAL_DISCONNECT:
            conn_handle, _, _ = data
            print("Disconnected", conn_handle)
            self._connections.remove(conn_handle)
            # Start advertising again to allow a new connection.
            self._advertise()
        elif event == _IRQ_GATTS_WRITE:
            conn_handle, value_handle = data
            value = self._ble.gatts_read(value_handle)
            if value_handle == self._handle_rx and self._write_callback:
                self._write_callback(value)

    def send(self, data):
        for conn_handle in self._connections:
            self._ble.gatts_notify(conn_handle, self._handle_tx, data)

    def is_connected(self):
        return len(self._connections) > 0

    def _advertise(self, interval_us=500000):
        print("Starting advertising")
        self._ble.gap_advertise(interval_us, adv_data=self._payload)

    def on_write(self, callback):
        self._write_callback = callback


def demo():
    ble = bluetooth.BLE()
    p = BLESimplePeripheral(ble)

    def on_rx(v):
        print("RX", v)

    p.on_write(on_rx)

    i = 0
    while True:
        if p.is_connected():
            # Short burst of queued notifications.
            transmit_msg = "Temperature: "+str(Temp_sensor())
            p.send(str(transmit_msg))
            '''
            for _ in range(3):
                data = "oz"+ str(i) + "_"
                print("TX", data)
                p.send(data)
                i += 1
            '''
            i = i + 1
        time.sleep_ms(1000)


if __name__ == "__main__":
    demo()
Posted on Leave a comment

How to use Bluetooth LE of Raspberry Pi Pico W using MicroPython

Step 1: Download the MicroPython UF2 file from the link below

https://www.raspberrypi.com/documentation/microcontrollers/micropython.html

Download the UF2 file which has Wi-Fi and Bluetooth LE support.

Step 2: Put the UF2 file in your raspberry pi pico w

  1. Push and hold the BOOTSEL button and plug your Pico into the USB port of your Raspberry Pi or other computer. Release the BOOTSEL button after your Pico is connected.
  2. It will mount as a Mass Storage Device called RPI-RP2.
  3. Drag and drop the MicroPython UF2 file onto the RPI-RP2 volume. Your Pico will reboot. You are now running MicroPython.
  4. You can access the REPL via USB Serial.

Step 3: Save the following files in your raspberry pi pico w

As you can see in the image, these files should be saved on the raspberry pi pico w. As they will be imported as modules when you write your application code.

[ Click on the file names to see the complete code ]

ble_advertising.py
# Helpers for generating BLE advertising payloads.

from micropython import const
import struct
import bluetooth

# Advertising payloads are repeated packets of the following form:
#   1 byte data length (N + 1)
#   1 byte type (see constants below)
#   N bytes type-specific data

_ADV_TYPE_FLAGS = const(0x01)
_ADV_TYPE_NAME = const(0x09)
_ADV_TYPE_UUID16_COMPLETE = const(0x3)
_ADV_TYPE_UUID32_COMPLETE = const(0x5)
_ADV_TYPE_UUID128_COMPLETE = const(0x7)
_ADV_TYPE_UUID16_MORE = const(0x2)
_ADV_TYPE_UUID32_MORE = const(0x4)
_ADV_TYPE_UUID128_MORE = const(0x6)
_ADV_TYPE_APPEARANCE = const(0x19)


# Generate a payload to be passed to gap_advertise(adv_data=...).
def advertising_payload(limited_disc=False, br_edr=False, name=None, services=None, appearance=0):
    payload = bytearray()

    def _append(adv_type, value):
        nonlocal payload
        payload += struct.pack("BB", len(value) + 1, adv_type) + value

    _append(
        _ADV_TYPE_FLAGS,
        struct.pack("B", (0x01 if limited_disc else 0x02) + (0x18 if br_edr else 0x04)),
    )

    if name:
        _append(_ADV_TYPE_NAME, name)

    if services:
        for uuid in services:
            b = bytes(uuid)
            if len(b) == 2:
                _append(_ADV_TYPE_UUID16_COMPLETE, b)
            elif len(b) == 4:
                _append(_ADV_TYPE_UUID32_COMPLETE, b)
            elif len(b) == 16:
                _append(_ADV_TYPE_UUID128_COMPLETE, b)

    # See org.bluetooth.characteristic.gap.appearance.xml
    if appearance:
        _append(_ADV_TYPE_APPEARANCE, struct.pack("<h", appearance))

    return payload


def decode_field(payload, adv_type):
    i = 0
    result = []
    while i + 1 < len(payload):
        if payload[i + 1] == adv_type:
            result.append(payload[i + 2 : i + payload[i] + 1])
        i += 1 + payload[i]
    return result


def decode_name(payload):
    n = decode_field(payload, _ADV_TYPE_NAME)
    return str(n[0], "utf-8") if n else ""


def decode_services(payload):
    services = []
    for u in decode_field(payload, _ADV_TYPE_UUID16_COMPLETE):
        services.append(bluetooth.UUID(struct.unpack("<h", u)[0]))
    for u in decode_field(payload, _ADV_TYPE_UUID32_COMPLETE):
        services.append(bluetooth.UUID(struct.unpack("<d", u)[0]))
    for u in decode_field(payload, _ADV_TYPE_UUID128_COMPLETE):
        services.append(bluetooth.UUID(u))
    return services


def demo():
    payload = advertising_payload(
        name="micropython",
        services=[bluetooth.UUID(0x181A), bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")],
    )
    print(payload)
    print(decode_name(payload))
    print(decode_services(payload))


if __name__ == "__main__":
    demo()
ble_simple_peripheral.py
# This example demonstrates a UART periperhal.

import bluetooth
import random
import struct
import time
from ble_advertising import advertising_payload

from micropython import const

_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)

_FLAG_READ = const(0x0002)
_FLAG_WRITE_NO_RESPONSE = const(0x0004)
_FLAG_WRITE = const(0x0008)
_FLAG_NOTIFY = const(0x0010)

_UART_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
_UART_TX = (
    bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"),
    _FLAG_READ | _FLAG_NOTIFY,
)
_UART_RX = (
    bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"),
    _FLAG_WRITE | _FLAG_WRITE_NO_RESPONSE,
)
_UART_SERVICE = (
    _UART_UUID,
    (_UART_TX, _UART_RX),
)


class BLESimplePeripheral:
    def __init__(self, ble, name="mpy-uart"):
        self._ble = ble
        self._ble.active(True)
        self._ble.irq(self._irq)
        ((self._handle_tx, self._handle_rx),) = self._ble.gatts_register_services((_UART_SERVICE,))
        self._connections = set()
        self._write_callback = None
        self._payload = advertising_payload(name=name, services=[_UART_UUID])
        self._advertise()

    def _irq(self, event, data):
        # Track connections so we can send notifications.
        if event == _IRQ_CENTRAL_CONNECT:
            conn_handle, _, _ = data
            print("New connection", conn_handle)
            self._connections.add(conn_handle)
        elif event == _IRQ_CENTRAL_DISCONNECT:
            conn_handle, _, _ = data
            print("Disconnected", conn_handle)
            self._connections.remove(conn_handle)
            # Start advertising again to allow a new connection.
            self._advertise()
        elif event == _IRQ_GATTS_WRITE:
            conn_handle, value_handle = data
            value = self._ble.gatts_read(value_handle)
            if value_handle == self._handle_rx and self._write_callback:
                self._write_callback(value)

    def send(self, data):
        for conn_handle in self._connections:
            self._ble.gatts_notify(conn_handle, self._handle_tx, data)

    def is_connected(self):
        return len(self._connections) > 0

    def _advertise(self, interval_us=500000):
        print("Starting advertising")
        self._ble.gap_advertise(interval_us, adv_data=self._payload)

    def on_write(self, callback):
        self._write_callback = callback


def demo():
    ble = bluetooth.BLE()
    p = BLESimplePeripheral(ble)

    def on_rx(v):
        print("RX", v)

    p.on_write(on_rx)

    i = 0
    while True:
        if p.is_connected():
            # Short burst of queued notifications.
            for _ in range(3):
                data = str(i) + "_"
                print("TX", data)
                p.send(data)
                i += 1
        time.sleep_ms(100)


if __name__ == "__main__":
    demo()

Step 4: Create a simple Bluetooth UART Code

which receives the message “Toggle\r\n” and Toggle the onboard LED and sends the state of the led back to the user.

# Import necessary modules
from machine import Pin 
import bluetooth
from ble_simple_peripheral import BLESimplePeripheral

# Create a Bluetooth Low Energy (BLE) object
ble = bluetooth.BLE()

# Create an instance of the BLESimplePeripheral class with the BLE object
sp = BLESimplePeripheral(ble)

# Create a Pin object for the onboard LED, configure it as an output
led = Pin("LED", Pin.OUT)

# Initialize the LED state to 0 (off)
led_state = 0
xWR_flag = 0
# Define a callback function to handle received data
def on_rx(data):
    print("Data received: ", data)  # Print the received data
    global led_state,xWR_flag  # Access the global variable led_state
    if data == b'toggle\r\n':  # Check if the received data is "toggle"
        led.value(not led_state)  # Toggle the LED state (on/off)
        led_state = 1 - led_state  # Update the LED state
        xWR_flag = 1

# Start an infinite loop
while True:
    if sp.is_connected():  # Check if a BLE connection is established
        sp.on_write(on_rx)  # Set the callback function for data reception
        if xWR_flag == 1:            
            # Create a message string                
            msg="LED STATE: "
            # Send the message via BLE
            sp.send(msg)
            sp.send(str(led_state))
            sp.send(str("\r\n"))
            xWR_flag = 0