11. Networking and Communications

This week I used the XIAO nrf52840 to send the accelerometer data via Bluetooth to the ESP32 PCB I made during week08. We also did a group assignment, which you can find here.

I first tried to use the Python script I made for week 4. Our advisor briefly explained how to connect boards to send data via Bluetooth, so I tried making my ESP32 board. First, I obtained the MAC address of my XIAO. The XIAO board would be the server, and the ESP32 the client, which would connect to the XIAO requesting the data.

Although I added the MAC address to the ESP32 code, when I ran both codes, the ESP32 couldn’t find the XIAO. I made sure it was the right one by making a code only to show the address from the XIAO in the serial monitor. After making sure it was indeed correct, I asked ChatGPT to compare both codes and help me figure out what was wrong. It told me that I had to make sure the XIAO advertised the service UUID that the ESP32 was looking for. Although everything seemed to be right, it wasn’t working either. Another thing ChatGPT gave me was a code for the ESP32 that scans all the nearby BLE devices and prints their MAC addresses in the serial monitor. Below is the scanning code:

Scanning Code

#include 
#include 
#include 
#include 

int scanTime = 5; // scan duration in seconds

void setup() {
  Serial.begin(115200);
  Serial.println("BLE Scanner starting...");
  BLEDevice::init("");
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setActiveScan(true);   // active scan to get scan responses (names, etc.)
  pBLEScan->setInterval(100);      // scan interval (in 0.625 ms units)
  pBLEScan->setWindow(50);         // scan window (must be <= interval)
}

void loop() {
  BLEScan* pBLEScan = BLEDevice::getScan();
  BLEScanResults* results = pBLEScan->start(scanTime, false);  // Fix: results should be a pointer
  Serial.print("Devices found: ");
  Serial.println(results->getCount());
  for (int i = 0; i < results->getCount(); ++i) {
    BLEAdvertisedDevice device = results->getDevice(i);
    String deviceName = device.getName().c_str();  // Directly use String constructor for conversion
    String deviceAddr = device.getAddress().toString().c_str();
    Serial.print("Device ");
    Serial.print(i);
    Serial.print(": ");
    Serial.print(deviceName);
    Serial.print(" [");
    Serial.print(deviceAddr);
    Serial.println("]");
  }
  Serial.println("Scan done!");
  Serial.println();
  pBLEScan->clearResults();   // free scan results to avoid memory leaks
  delay(2000);                // wait 2 seconds before next scan cycle
}
                    

After several failed attempts trying to detect the board, I decided to use the Arduino code I also made during week 4 for my XIAO instead of the Python one. It worked, so I knew I was on the right track. Since the ESP32 finally detected the XIAO.

For the final ESP32 code I included these files:

#include BLEDevice.h>

#include BLEUtils.h>

#include BLEScan.h>

#include BLEAdvertisedDevice.h>

The problem was that I constantly had this error: Multiple libraries were found for "BLEDevice.h" after asking Chat GPT why I was getting this error while compiling the code. It told me there was a confusion between the ESP32 BLE and the ArduinoBLE libraries, so I had to uninstall the ArduinoBLE library for the code to work. Here are the codes:

XIAO Code:

  #include bluefruit.h>             // Using Bluefruit library instead of ArduinoBLE
  #include "LSM6DS3.h"
  #include "Wire.h"
  
  // BLE Service Setup (using Bluefruit)
  BLEDfu bledfu;  // OTA DFU service
  BLEDis bledis;  // Device Information Service
  BLEUart bleuart; // UART over BLE for data transmission
  BLEBas blebas;   // Battery Service
  
  // Create an instance of the LSM6DS3 IMU
  LSM6DS3 myIMU(I2C_MODE, 0x6A);
  
  void setup() {
    Serial.begin(115200);
    
    // Debugging setup
    #if CFG_DEBUG
    while (!Serial) yield();
    #endif
  
    // Initialize the IMU sensor
    if (myIMU.begin() != 0) {
      Serial.println("IMU error!");
      while (1);
    }
  
    // Configure Bluefruit
    Bluefruit.autoConnLed(true);  // Enable LED connection indicator
    Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);  // Max bandwidth
    
    // Initialize Bluefruit
    Bluefruit.begin();
    Bluefruit.setTxPower(4);    // Set transmission power
    Bluefruit.Periph.setConnectCallback(connect_callback);
    Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
  
    // Setup services
    bledfu.begin();  // Device Firmware Update service
    
    // Device Information Service
    bledis.setManufacturer("Adafruit Industries");
    bledis.setModel("Xiao-Accel");
    bledis.begin();
    
    // UART service for data transmission
    bleuart.begin();
    
    // Battery Service
    blebas.begin();
    blebas.write(100);  // Set initial battery level
    
    // Start advertising
    startAdv();
    
    Serial.println("BLE Advertising...");
  }
  
  void startAdv(void) {
    // Advertising packet
    Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
    Bluefruit.Advertising.addTxPower();
    
    // Include UART service UUID
    Bluefruit.Advertising.addService(bleuart);
    
    // Secondary scan response packet (device name)
    Bluefruit.ScanResponse.addName();
    
    // Start advertising
    Bluefruit.Advertising.restartOnDisconnect(true);
    Bluefruit.Advertising.setInterval(160, 320);  // in units of 0.625 ms
    Bluefruit.Advertising.setFastTimeout(30);      // 30 seconds
    Bluefruit.Advertising.start(0);                // 0 = Don't stop advertising
  }
  
  void loop() {
    // Read accelerometer data
    float x = myIMU.readFloatAccelX();
    float y = myIMU.readFloatAccelY();
    float z = myIMU.readFloatAccelZ();
    
    // Format data string
    char buffer[50];
    snprintf(buffer, sizeof(buffer), "X:%.2f Y:%.2f Z:%.2f", x, y, z);
    
    // Send data if connected
    if (Bluefruit.connected()) {
      bleuart.println(buffer);  // Send via BLE UART
    }
    
    Serial.println(buffer);     // Print to serial
    delay(100);                // 100ms delay
  }
  
  // Connection callback
  void connect_callback(uint16_t conn_handle) {
    BLEConnection* connection = Bluefruit.Connection(conn_handle);
    
    char central_name[32] = { 0 };
    connection->getPeerName(central_name, sizeof(central_name));
    
    Serial.print("Connected to ");
    Serial.println(central_name);
  }
  
  // Disconnection callback
  void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
    (void) conn_handle;
    (void) reason;
    
    Serial.println();
    Serial.print("Disconnected, reason = 0x");
    Serial.println(reason, HEX);
  }
       
ESP32 Code:

  #include 
  #include 
  #include 
  #include 
  
  // BLE UART service UUIDs
  #define SERVICE_UUID        "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
  #define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
  
  // Global variables
  bool deviceFound = false;
  BLEAdvertisedDevice* myDevice = nullptr;
  BLEClient* pClient;
  BLERemoteCharacteristic* pRemoteCharacteristic;
  
  // Data processing variables
  String packetBuffer = "";
  bool expectingNewPacket = true;
  const unsigned long PACKET_TIMEOUT = 100; // ms
  
  class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
    void onResult(BLEAdvertisedDevice advertisedDevice) {
      Serial.print("Found device: ");
      Serial.println(advertisedDevice.getName().c_str());
      
      if ((advertisedDevice.getName() == "Xiao-Accel" || 
            advertisedDevice.getName() == "XIAO nRF52840 Sense") && 
          !deviceFound) {
        advertisedDevice.getScan()->stop();
        myDevice = new BLEAdvertisedDevice(advertisedDevice);
        deviceFound = true;
        Serial.println("Device found, stopping scan.");
      }
    }
  };
  
  void processCompletePacket(String packet) {
    if (packet.length() > 0) {
      Serial.print("Accel Data: ");
      Serial.println(packet);
    }
  }
  
  void setup() {
    Serial.begin(115200);
    Serial.println("Starting BLE Client for Xiao nRF52840 Sense...");
    
    BLEDevice::init("ESP32-BLE-Client");
  
    BLEScan* pBLEScan = BLEDevice::getScan();
    pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
    pBLEScan->setActiveScan(true);
    pBLEScan->setInterval(100);
    pBLEScan->setWindow(99);
    pBLEScan->start(5, false);
  }
  
  void loop() {
    static unsigned long lastPacketTime = 0;
    
    if (deviceFound) {
      if (!pClient || !pClient->isConnected()) {
        Serial.println("Attempting to connect to Xiao device...");
        
        pClient = BLEDevice::createClient();
        if (pClient->connect(myDevice)) {
          Serial.println("Connected to Xiao device!");
  
          BLERemoteService* pRemoteService = pClient->getService(SERVICE_UUID);
          if (pRemoteService != nullptr) {
            pRemoteCharacteristic = pRemoteService->getCharacteristic(CHARACTERISTIC_UUID_TX);
            if (pRemoteCharacteristic != nullptr && pRemoteCharacteristic->canNotify()) {
              pRemoteCharacteristic->registerForNotify([](BLERemoteCharacteristic* pBLERemoteCharacteristic, 
                                                        uint8_t* pData, 
                                                        size_t length, 
                                                        bool isNotify) {
                // Process received data
                for (int i = 0; i < length; i++) {
                  char c = (char)pData[i];
                  packetBuffer += c;
                }
                lastPacketTime = millis();
              });
  
              // Enable notifications
              uint8_t notificationOn[2] = {0x01, 0x00};
              pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue(notificationOn, 2, true);
              Serial.println("Enabled notifications, waiting for data...");
            }
          }
        }
      }
  
      // Check for complete packets
      if (packetBuffer.length() > 0) {
        // Look for complete lines
        int newlinePos = packetBuffer.indexOf('\n');  // Use '\n' as delimiter
        if (newlinePos >= 0) {
          String completePacket = packetBuffer.substring(0, newlinePos);  // Extract complete packet
          completePacket.trim();  // Remove any extra spaces/newlines
          processCompletePacket(completePacket);  // Process the full packet
          packetBuffer = packetBuffer.substring(newlinePos + 1);  // Remove the processed part of the buffer
        }
        // Timeout for incomplete data
        else if (millis() - lastPacketTime > PACKET_TIMEOUT && packetBuffer.length() > 0) {
          packetBuffer.trim();
          processCompletePacket(packetBuffer);  // Process remaining data
          packetBuffer = "";  // Reset buffer after processing
        }
      }
    } else {
      // Device not found, keep scanning
      if (millis() - lastPacketTime > 5000) {  // Rescan every 5 seconds if not connected
        BLEDevice::getScan()->start(5, false);
      }
    }
  
    delay(10);  // Small delay to prevent watchdog triggers
  }
       

After uploading the final code, the ESP32 detected the XIAO, and the communication was successfully established. Below is the video showing the boards communicating:

By the way, I made the PCB for the XIAO for my final project, you can find it here

Here´s the hero shot:

Hero Shot

Summary

This week I worked on establishing Bluetooth communication between the XIAO nRF52840 and the ESP32. I used two approaches; one with Python and another with Arduino IDE, both using BLE for communication. After obtaining the MAC address and making sure I had worked with the right UUID, I needed to take extra steps before success. The process involved troubleshooting some library conflicts, but ultimately both boards were able to communicate seamlessly over Bluetooth, and I was able to display the data in real-time.