Skip to content

p r o g r a m m i n g

i n i t i a l . s e t u p | n R F . c o n n e c t

Before integrating the stretch sensor, I initially tried to control the servo motor directly through Bluetooth using the nRF Connect app. The first steps involved setting up the Arduino IDE and ensuring that the ESP32-C3 boards were correctly configured for Bluetooth communication.

s e t t i n g . u p . a r d u i n o . I D E

The Arduino IDE had to be configured to support the ESP32-C3 microcontrollers, which involved installing the appropriate board package:

  • Install the ESP32 Board Package:

Go to File > Preferences in Arduino IDE. Add the URL for the ESP32 board manager: https://dl.espressif.com/dl/package_esp32_index.json.

Go to Tools > Board > Boards Manager, search for "ESP32", and install it.

  • Select the Correct Board:

In Tools > Board, I selected either Xiao ESP32-C3 or ESP32C3 Dev Module, depending on which microcontroller I was programming.

t h e . c o d e

At first, I set up a basic communication script where I used nRF Connect to send simple commands (such as numbers) to control the servo motor connected to the Super Mini ESP32-C3. Below is the basic setup of the servo code and Bluetooth communication.

#include <BluetoothSerial.h>
#include <Servo.h>

BluetoothSerial SerialBT;
Servo myServo;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ServoControl"); // Bluetooth name
  myServo.attach(9); // Servo pin
}

void loop() {
  if (SerialBT.available()) {
    int angle = SerialBT.parseInt(); // Parse incoming integer (angle)
    if (angle >= 0 && angle <= 180) {
      myServo.write(angle); // Set the servo to the received angle
    }
  }
}

I used nRF Connect on the phone to send data like 90 or 0, which represented angles to move the servo motor.

Here is the final code:

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <ESP32Servo.h>

// Define the servo pin
const int servoPin = 8;

// Create a servo object
Servo myServo;

// BLE UUIDs
#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

// BLE objects
BLEServer* pServer = nullptr;
BLECharacteristic* pCharacteristic = nullptr;

// Flag to indicate if a device is connected
bool deviceConnected = false;

// BLE Server Callbacks
class MyServerCallbacks : public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
        deviceConnected = true;
        Serial.println("Device connected");
    }

    void onDisconnect(BLEServer* pServer) {
        deviceConnected = false;
        Serial.println("Device disconnected");
        // Restart advertising to allow reconnection
        pServer->startAdvertising();
    }
};

// BLE Characteristic Callbacks
class MyCallbacks : public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic* pCharacteristic) {
        String value = pCharacteristic->getValue().c_str(); // Get the value as a String
        if (value.length() > 0) {
            // Convert the received value to an integer (angle)
            int angle = value.toInt();
            angle = constrain(angle, 0, 180); // Constrain to valid servo angles

            // Move the servo to the specified angle
            myServo.write(angle);
            Serial.print("Servo moved to: ");
            Serial.println(angle);
        }
    }
};

void setup() {
    // Start serial communication
    Serial.begin(115200);

    // Attach the servo to the pin
    myServo.attach(servoPin);

    // Initialize BLE
    BLEDevice::init("ESP32C3_Servo");
    pServer = BLEDevice::createServer();
    pServer->setCallbacks(new MyServerCallbacks());

    // Create a BLE service
    BLEService* pService = pServer->createService(SERVICE_UUID);

    // Create a BLE characteristic
    pCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_UUID,
        BLECharacteristic::PROPERTY_WRITE
    );
    pCharacteristic->setCallbacks(new MyCallbacks());

    // Start the service
    pService->start();

    // Start advertising
    pServer->getAdvertising()->start();
    Serial.println("BLE Server started. Waiting for connections...");
}

void loop() {
    // Nothing to do here; BLE callbacks handle everything
}

d e b u g g i n g

s e r i a l . m o n i t o r . i s s u e

At the beginning, I was unable to read any output from the serial monitor in Arduino IDE. Despite the fact that I had written code to print serial data, there was no output.

p r o b l e m

The serial monitor was blank.

c a u s e

I discovered that the USB CDC On Boot option under Tools > USB CDC On Boot in the IDE settings needed to be enabled for serial output to function.

s o l u t i o n

After enabling the USB CDC On Boot setting, I was able to see the serial output in the monitor.

s e r v o . n o t . r e s p o n d i n g

After successfully setting up the serial monitor, I tested the communication via nRF Connect. I used the app to send commands to control the servo, but the servo did not respond to the commands as expected.

p r o b l e m

The servo motor did not respond to the commands sent from the nRF Connect app.

c a u s e

The main issue was that the data sent via nRF Connect was not in the correct format for the ESP32-C3 to interpret. The app was sending the data in a numeric format or potentially binary format, and the ESP32-C3 microcontroller could not parse it correctly. The values were not being interpreted as text, which caused the servo to remain stationary despite receiving commands.

s o l u t i o n

To resolve the issue, I reconfigured the nRF Connect app to send the values as text rather than as raw numeric data. This adjustment allowed the ESP32-C3 to correctly parse the command and move the servo accordingly.

// Updated code to read string data sent via nRF Connect
void loop() {
  if (SerialBT.available()) {
    String command = SerialBT.readString(); // Read string input
    int angle = command.toInt(); // Convert string to integer
    if (angle >= 0 && angle <= 180) {
      myServo.write(angle); // Move the servo to the given angle
    }
  }
}

This adjustment worked, and the servo started responding correctly to commands sent through nRF Connect.

w i r e l e s s . c o n t r o l

i n t r o d u c t i o n

In this project, the goal was to establish a wireless communication system between two ESP32-C3 microcontrollers to control a servo motor using a stretch sensor. The ESP32-C3 boards, chosen for their Bluetooth Low Energy (BLE) capabilities, are ideal for this application, as they provide efficient wireless communication and control over a distance.

s e t u p

The hardware setup consisted of:

  • Xiao ESP32-C3: Connected to a DIY stretch sensor made with conductive threads.

  • Super Mini ESP32-C3: Connected to a servo motor for physical movement.

  • Servo Motor: Controlled by the Super Mini ESP32-C3, with its position adjusted based on the stretch sensor's input.

  • nRF Connect App: To check the connection of microcontrollers.

For software:

  • Arduino IDE: Used for programming the ESP32 boards.

  • ESP32 Servo Library: Used for controlling the servo motor on the Super Mini ESP32-C3.

  • BLE Libraries: These libraries are essential for handling BLE communication on both ESP32 devices.

c o n n e c t i n g . t h e . s e n s o r

The next step was to integrate the stretch sensor with the Xiao ESP32-C3. This sensor was used to detect physical stretching and send the corresponding angle to the Super Mini ESP32-C3 to adjust the servo motor's position accordingly.

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <ESP32Servo.h>  // ESP32 Servo library for controlling the servo

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

// BLE objects
BLEServer* pServer = nullptr;
BLECharacteristic* pCharacteristic = nullptr;

// Stretch sensor pin
const int stretchSensorPin = A0;  // Pin where the stretch sensor is connected

// Discrete servo angles
const int angles[] = {30, 45, 75, 90, 150}; // Define the desired angles
const int numAngles = sizeof(angles) / sizeof(angles[0]);

// Servo control
Servo myServo;
#define SERVO_PIN 9  // Pin where the servo is connected

void setup() {
    Serial.begin(115200);  // Start serial communication
    BLEDevice::init("Stretch_Sensor");  // Initialize BLE

    // Create BLE server and service
    pServer = BLEDevice::createServer();
    BLEService* pService = pServer->createService(SERVICE_UUID);

    // Create a BLE characteristic to send the angle to the client
    pCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_UUID,
        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
    );
    pCharacteristic->addDescriptor(new BLE2902());

    // Start the service and start advertising
    pService->start();
    pServer->getAdvertising()->start();

    // Set up the servo
    myServo.attach(SERVO_PIN);
    myServo.write(90);  // Set initial position of servo

    Serial.println("BLE Server started. Waiting for connections...");
}

void loop() {
    // Read the analog value from the stretch sensor
    int sensorValue = analogRead(stretchSensorPin);

    // Map the sensor value to a discrete angle (based on the range of your sensor)
    int angle = mapToDiscreteAngles(sensorValue);

    // Send the angle via BLE to the client
    uint8_t value = angle;  // Convert to byte
    pCharacteristic->setValue(&value, 1);
    pCharacteristic->notify();

    // Print the value to the Serial Monitor
    Serial.print("Sensor Value: ");
    Serial.print(sensorValue);
    Serial.print(" | Sent Angle: ");
    Serial.println(angle);

    delay(100);  // Adjust delay as needed
}

// Function to map sensor value to discrete angles
int mapToDiscreteAngles(int sensorValue) {
    int index = map(sensorValue, 10, 210, 0, numAngles - 1);
    index = constrain(index, 0, numAngles - 1);  // Ensure the index is within bounds
    return angles[index];
}

In this code, the stretch sensor's analog input is read and mapped to a discrete set of angles. These angles are then sent over BLE to the connected device, which is expected to process and control the servo motor based on the received data.

s e r v o . c o n t r o l

The next part involved receiving the angle data on the Super Mini ESP32-C3, which controlled the servo. The servo angle was updated whenever new data was received from the Xiao ESP32-C3.

Code for Servo Control (Super Mini ESP32-C3)

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <ESP32Servo.h>  // Use the ESP32-specific Servo library for controlling the servo

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"  // Service UUID for BLE communication
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"  // Characteristic UUID for BLE communication

// BLE objects
BLEServer* pServer = nullptr;
BLECharacteristic* pCharacteristic = nullptr;

// Servo pin and setup
#define SERVO_PIN 9  // Pin where the servo is connected
Servo myServo;  // Create a servo object

// Discrete servo angles (adjust these values as per your requirement)
const int angles[] = {30, 45, 75, 90, 150};  
const int numAngles = sizeof(angles) / sizeof(angles[0]);

// Stretch sensor pin
const int stretchSensorPin = A0;  // Analog pin for the stretch sensor

void setup() {
    Serial.begin(115200);  // Start serial communication
    BLEDevice::init("Stretch_Sensor");  // Initialize BLE with a name for the device

    // Set up BLE service and characteristic
    pServer = BLEDevice::createServer();
    BLEService* pService = pServer->createService(SERVICE_UUID);
    pCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_UUID,
        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
    );
    pCharacteristic->addDescriptor(new BLE2902());

    // Start BLE service and begin advertising
    pService->start();
    pServer->getAdvertising()->start();

    // Set up the servo motor
    myServo.attach(SERVO_PIN);  // Attach servo to the specified pin
    myServo.write(90);  // Initialize servo at a neutral position (90 degrees)

    Serial.println("BLE Server started. Waiting for connections...");
}

void loop() {
    // Read the analog value from the stretch sensor (connected to A0)
    int sensorValue = analogRead(stretchSensorPin);

    // Map the sensor value to a corresponding discrete angle
    int angle = mapToDiscreteAngles(sensorValue);

    // Send the mapped angle to the BLE client
    uint8_t value = angle;  // Convert the angle to byte
    pCharacteristic->setValue(&value, 1);  // Set the value of the characteristic
    pCharacteristic->notify();  // Notify the client about the change

    // Move the servo to the new angle
    myServo.write(angle);

    // Print the sensor value and the sent angle to the Serial Monitor
    Serial.print("Sensor Value: ");
    Serial.print(sensorValue);
    Serial.print(" | Sent Angle: ");
    Serial.println(angle);

    delay(100);  // Delay to avoid excessive readings and BLE notifications
}

// Function to map the sensor value to a discrete angle
int mapToDiscreteAngles(int sensorValue) {
    // Map the sensor value range (for example, 10-210) to index in the angle array
    int index = map(sensorValue, 10, 210, 0, numAngles - 1);
    index = constrain(index, 0, numAngles - 1);  // Ensure the index is within bounds
    return angles[index];
}

In this code, the Super Mini ESP32-C3 listens for BLE notifications, receives the angle data, and moves the servo accordingly. The BLE communication protocol ensures the servo position is updated based on the stretch sensor input.

d e b u g g i n g

Several issues arose during the debugging process:

  • Instability of Bluetooth and Lost Connections: Initially, Bluetooth communication between the two ESP32-C3 boards was unstable, leading to frequent disconnections. This issue was traced to interference or timing issues in the Bluetooth communication. Adjustments in the timing and configuration settings helped stabilize the connection.

  • Redundant Libraries: Sometimes, using multiple libraries for similar functionality led to conflicts, causing instability. Removing redundant or conflicting libraries helped resolve the issue and ensure smoother operation of the Bluetooth communication.

  • Angle Mismatch Between Sensor Code and Servo Code: There was an issue with the corresponding angles between the stretch sensor and the servo motor. The sensor code outputted values that didn’t align with the servo’s expected range. This was resolved by using the map() function to ensure that the sensor’s values corresponded correctly to the servo motor’s angle range, providing accurate and responsive control.

  • UUID Mismatch: A key issue was ensuring that both the service and characteristic UUIDs matched between the two boards. Initially, different UUIDs caused the boards to fail to communicate. After synchronizing the UUIDs for both boards, the communication issue was resolved.

n e x t . s t e p s

The next phase involves testing the system with Wi-Fi communication instead of Bluetooth. This will enable a more robust and higher-range communication between the two boards, avoiding Bluetooth-related limitations.

  • Switching to Wi-Fi Communication: Replacing the Bluetooth setup with a Wi-Fi-based solution, using protocols such as HTTP or MQTT for communication, will allow the system to handle longer-range communication and more stable connections.

  • Testing Reliability and Range: The goal is to test how well the servo responds to stretch sensor inputs over Wi-Fi, ensuring the system operates reliably over a larger area and with fewer disconnections.

  • Ensuring Smooth Communication: Additional steps will be taken to ensure smooth communication between the two ESP32-C3 boards when using Wi-Fi, including error handling for network instability and ensuring real-time communication is maintained without significant delays.