Hi there,
SO, it’s been asked many times if more than one BLE connection can be made at a time with Peripherals to a Central and is the Seeed Studio Round Display up to the effort. YES!
Albeit NOT a standard method but uses another BLE LIB available in the Arduino tool box(works in ESP-IDF) also This is the NimBLE LIB
it is somewhat better for multiple peripherals and with some effort and understanding , 3 concurrent BLE connections can be made with 6 Notifications possible. How , Watch the video and Examine the code.
It’s a two part Series , with this being the SensorNode (peripheral) portion first.
ask any question you might have here, I’ll do my best to answer them.
HTH
GL PJ
/* =====================================================================================
Project : SensorNode BLE Peripheral with OLED & RGB Status
Platform : Seeed Studio XIAO nRF52840 Sense with Xiao DEV/expansion Base.
BSP : Seeeduino mbed @ 2.9.3
Core : Arduino mbed (nRF52840)
MCU : Nordic nRF52840 (Cortex-M4F @ 64MHz, 1MB Flash, 256KB RAM)
Board FQBN : Seeeduino:mbed:xiaonRF52840Sense
IDE : Arduino IDE 2.x
Libraries :
- ArduinoBLE v1.4.0 (BLE Peripheral GATT Profile)
- Wire (I2C OLED via Dev Expansion Board)
- U8x8lib or U8g2lib (for SSD1306 OLED on Expansion Base)
Peripherals:
- OLED Display (via I2C)
- RGB LED (Builtin)
- USER Button (D1)
- Buzzer (A3)
Features :
- BLE advertising as SensorNodeX (custom 181A service) "B" and "M" OLED indicators for Notify states,
- OLED Display showing device name and BLE status
- RGB LED feedback (Yellow=Advertising, Green=Connected, Red=Disconnected)
- Animated "..." progress indicator
- Placeholder for future sensor expansion (Temp, Humidity, Motion, Battery)
Author : Pj Glasso, & Embedded AI Assistant (ESP32 Expert Mode) , wiki.seeedstudio.com/
Version : REV 1.0 // 2025-05-26 02:00 EDT
===================================================================================== */
// ===================================================================================
// Features : BLE 0x181A service, "B" and "M" OLED indicators for Notify states,
// RGB LED, buzzer feedback, dynamic re-advertising after disconnect
// Version : REV 1.6d // 2025-05-29 08:50 PM
// Added Vertual Battery Level Notify (changes the Level cuases the notify to fire)
// accepts Subscribes to both Notify's Beep on Connect/Disconnect.
// ===================================================================================
#include <ArduinoBLE.h>
#include <U8x8lib.h>
#define REVISION "REV 1.6d"
#define SENSOR_NODE_NUMBER 4 // each node number should be unique, It is posssible to use the MAC address to derive it also.
#define BUZZER_PIN A3 // Buzzer on Expansion Base
U8X8_SSD1306_128X64_NONAME_HW_I2C oled(U8X8_PIN_NONE); // OLED on DEV Base
BLEService envService("181A");
BLEUnsignedCharCharacteristic tempChar("2A6E", BLERead);
BLEUnsignedCharCharacteristic humidityChar("2A6F", BLERead);
BLEUnsignedCharCharacteristic batteryChar("2A19", BLERead | BLENotify);
BLEUnsignedCharCharacteristic motionChar("2A56", BLERead | BLENotify);
int batteryLevel = 99;
unsigned long lastBatteryUpdate = 0;
bool batteryNotifySubscribed = false;
bool motionNotifySubscribed = false;
int scrollOffset = 0;
unsigned long lastScrollUpdate = 0;
unsigned long lastFlashTime = 0;
bool flashState = false;
void initdisplay() {
pinMode(LEDR, OUTPUT);
pinMode(LEDG, OUTPUT);
pinMode(LEDB, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(LEDR, HIGH);
digitalWrite(LEDG, HIGH);
digitalWrite(LEDB, HIGH);
digitalWrite(BUZZER_PIN, LOW);
}
void setLedRGB(bool red, bool green, bool blue) {
digitalWrite(LEDR, red ? LOW : HIGH);
digitalWrite(LEDG, green ? LOW : HIGH);
digitalWrite(LEDB, blue ? LOW : HIGH);
}
void drawCenteredNodeNumber() {
oled.setFont(u8x8_font_inb33_3x6_r);
String numStr = String(SENSOR_NODE_NUMBER);
uint8_t col = (numStr.length() == 1) ? 5 : 4;
oled.drawString(col, 2, numStr.c_str());
oled.setFont(u8x8_font_7x14_1x2_r);
oled.drawString(9, 2, batteryNotifySubscribed ? "B" : " ");
oled.setFont(u8x8_font_7x14_1x2_r);
oled.drawString(9, 5, motionNotifySubscribed ? "M" : " ");
oled.setFont(u8x8_font_chroma48medium8_r);
oled.drawString(2, 7, "Connected ");
}
void setup() {
Serial.begin(115200);
delay(2000);
printBuildInfo();
Serial.println("\nPower ON\n");
startsound();
oled.begin();
oled.setFlipMode(1);
oled.clear();
oled.setFont(u8x8_font_chroma48medium8_r);
oled.drawString(0, 0, "Power ON");
initdisplay();
setLedRGB(false, true, false); // GREEN
delay(1500);
oled.clear();
oled.setFont(u8x8_font_7x14_1x2_r);
oled.drawString(2, 0, "SensorNode");
drawCenteredNodeNumber();
oled.setFont(u8x8_font_chroma48medium8_r);
oled.drawString(2, 7, "Advertising");
setLedRGB(false, false, true); // BLUE
if (!BLE.begin()) {
oled.drawString(0, 6, "BLE Failed");
while (1);
}
String nodeName = "SensorNode" + String(SENSOR_NODE_NUMBER);
BLE.setLocalName(nodeName.c_str());
BLE.setDeviceName(nodeName.c_str());
BLE.setAdvertisedService(envService);
envService.addCharacteristic(tempChar);
envService.addCharacteristic(humidityChar);
envService.addCharacteristic(batteryChar);
envService.addCharacteristic(motionChar);
BLE.addService(envService);
tempChar.writeValue(77);
humidityChar.writeValue(55);
batteryChar.writeValue(batteryLevel);
motionChar.writeValue(0);
BLE.advertise();
Serial.println(nodeName + " advertising...");
}
void loop() {
BLEDevice central = BLE.central();
if (central) {
oled.drawString(0, 7, " ");
oled.drawString(2, 7, "Connected ");
Serial.print("Connected to: ");
Serial.println(central.address());
beepConnected();
while (central.connected()) {
unsigned long now = millis();
// Battery Notify + Simulate Drop every 25 sec
if (batteryChar.subscribed() && now - lastBatteryUpdate > 25000) {
lastBatteryUpdate = now;
batteryLevel -= random(1, 3);
if (batteryLevel < 70) batteryLevel = 99;
batteryChar.writeValue(batteryLevel);
Serial.print("Battery Level: ");
Serial.println(batteryLevel);
}
// Detect notify subscription state changes
bool currentBatterySubscribed = batteryChar.subscribed();
bool currentMotionSubscribed = motionChar.subscribed();
if (currentBatterySubscribed != batteryNotifySubscribed ||
currentMotionSubscribed != motionNotifySubscribed) {
batteryNotifySubscribed = currentBatterySubscribed;
motionNotifySubscribed = currentMotionSubscribed;
drawCenteredNodeNumber();
}
if (now - lastFlashTime > 15000) {
lastFlashTime = now;
setLedRGB(false, true, true);
delay(300);
setLedRGB(false, false, false);
}
delay(100);
}
// --- DISCONNECT HANDLING + BLE RE-INIT ---
oled.setFont(u8x8_font_7x14_1x2_r);
oled.drawString(9, 2, " "); // Clear B
oled.setFont(u8x8_font_chroma48medium8_r);
oled.drawString(9, 5, " "); // Clear M
oled.drawString(0, 7, "Disconnected ");
setLedRGB(true, false, false);
beepDisconnected();
delay(10000);
batteryNotifySubscribed = false;
motionNotifySubscribed = false;
batteryChar.unsubscribe();
motionChar.unsubscribe();
BLE.stopAdvertise();
delay(200);
String nodeName = "SensorNode" + String(SENSOR_NODE_NUMBER);
BLE.setLocalName(nodeName.c_str());
BLE.setDeviceName(nodeName.c_str());
BLE.setAdvertisedService(envService);
BLE.advertise();
oled.drawString(0, 7, " ");
scrollOffset = 0;
lastScrollUpdate = millis();
Serial.println("Disconnected.");
Serial.println("Re-advertising as: " + nodeName);
}
unsigned long now = millis();
if (!BLE.connected() && (now - lastScrollUpdate > 200)) {
lastScrollUpdate = now;
oled.drawString(0, 7, " ");
String scrollText = " Advertising ";
int windowSize = 11;
if (scrollOffset >= scrollText.length()) scrollOffset = 0;
String view;
if (scrollOffset + windowSize <= scrollText.length()) {
view = scrollText.substring(scrollOffset, scrollOffset + windowSize);
} else {
int firstPart = scrollText.length() - scrollOffset;
view = scrollText.substring(scrollOffset);
view += scrollText.substring(0, windowSize - firstPart);
}
oled.drawString(0, 7, view.c_str());
scrollOffset++;
flashState = !flashState;
setLedRGB(false, false, flashState);
}
}
void startsound() {
tone(BUZZER_PIN, 890); delay(220); noTone(BUZZER_PIN); delay(20);
tone(BUZZER_PIN, 800); delay(220); noTone(BUZZER_PIN); delay(20);
tone(BUZZER_PIN, 800); delay(220); noTone(BUZZER_PIN); delay(20);
tone(BUZZER_PIN, 990); delay(420); noTone(BUZZER_PIN); delay(20);
}
void beepConnected() {
tone(BUZZER_PIN, 880); delay(250); noTone(BUZZER_PIN);
}
void beepDisconnected() {
delay(200);
tone(BUZZER_PIN, 480); delay(300); noTone(BUZZER_PIN);
}
void printBuildInfo() {
String filename = String(__FILE__);
int lastSlash = filename.lastIndexOf('/');
if (lastSlash == -1) lastSlash = filename.lastIndexOf('\\');
String sketchName = filename.substring(lastSlash + 1);
Serial.println("\n=== Build Info ===");
Serial.println("Sketch: " + sketchName);
Serial.println("Version: " + String(REVISION));
Serial.println("Compiled: " + String(__DATE__) + " at " + String(__TIME__));
Serial.println("==================\n");
}
Full Screen, you can see the serial port messages from the SensorNode_4 in the IDE.