Interactive Notification Cube Project
This document describes the hardware components, wiring connections, face detection
logic, and example Arduino Nano and ESP32 code for an Interactive Notification Cube used
in medical and restaurant environments.
Components Used
Arduino Nano: Reads MPU-6050 sensor over I²C and calculates which face of the cube
is facing upwards.
ESP32 Dev Board (NodeMCU ESP32-S 30 pin): Receives face ID from Arduino via
serial and updates the OLED display / wireless output.
MPU-6050 (GY-521 6-DOF Accelerometer + Gyroscope): Provides acceleration and
rotation data to detect cube orientation.
0.96" OLED Display (SSD1306, I²C): Shows the active notification mode (medical and
restaurant labels).
18650 Li-ion Battery: Main power source for the portable cube.
TP4056 Li-ion Charging Module (with protection): Charges the 18650 cell and
provides regulated output to the system.
18650 Battery Holder: Holds the battery securely inside the cube.
Slide ON/OFF Switch: Used to disconnect battery output when the cube is off.
Mini Breadboard or PCB: For connecting and soldering components.
Jumper Wires: Male–male and male–female wires for all connections.
Optional Buzzer / LED + Resistor: For additional sound or light feedback when faces
change.
Wiring and Connections
All grounds (GND) of the Arduino Nano, ESP32, MPU-6050, OLED, and TP4056 output must
be connected together to form a common reference.
1. MPU-6050 (GY-521) to Arduino Nano (I²C):
1. VCC → 5V on Arduino Nano
GND → GND on Arduino Nano
SCL → A5 on Arduino Nano
SDA → A4 on Arduino Nano
INT → Not connected (optional)
2. Arduino Nano to ESP32 (Serial Communication):
2. TX (D1) on Arduino Nano → RX2 (GPIO 16) on ESP32
RX (D0) on Arduino Nano → TX2 (GPIO 17) on ESP32
GND on Arduino Nano → GND on ESP32
During code upload to either the Arduino Nano or the ESP32, disconnect the TX/RX lines to
avoid interference with the USB serial connection. Reconnect them after the upload is
complete.
3. OLED Display (SSD1306, I²C) to ESP32:
3. VCC → 3.3V on ESP32
GND → GND on ESP32
SCL → GPIO 22 on ESP32
SDA → GPIO 21 on ESP32
4. Power System (18650 Battery + TP4056 + Boards):
4. Battery positive (+) → B+ on TP4056
Battery negative (–) → B– on TP4056
OUT+ from TP4056 → one side of ON/OFF switch
Other side of ON/OFF switch → 5V (VIN) on ESP32 and 5V on Arduino Nano
OUT– from TP4056 → GND on ESP32 and GND on Arduino Nano
From the ESP32 and Nano 5V pins, you can also power the MPU-6050 module (VCC → 5V)
and keep all modules sharing the same ground.
Face Detection Logic (Which Side is Up)
The MPU-6050 provides raw acceleration values on the X, Y, and Z axes (ax, ay, az). When
the cube is resting on a face, gravity (~1g) will mostly act along one axis. The algorithm
finds which axis has the largest absolute acceleration and uses its sign to decide which face
is up.
Example logic:
1. Read ax, ay, az from the MPU-6050.
2. Convert them to g units (optional) or compare raw values.
3. Find the axis with the largest absolute value: max(|ax|, |ay|, |az|).
4. If |ax| is largest and ax > 0 → Face = +X (e.g., Face 1)
If |ax| is largest and ax < 0 → Face = −X (Face 2)
If |ay| is largest and ay > 0 → Face = +Y (Face 3)
If |ay| is largest and ay < 0 → Face = −Y (Face 4)
If |az| is largest and az > 0 → Face = +Z (Face 5)
If |az| is largest and az < 0 → Face = −Z (Face 6)
The Arduino Nano maps each face (1–6) to a numeric ID and sends it to the ESP32 as plain
text over serial in the format "FACE:x".
Arduino Nano Code (MPU-6050 + Face Detection)
#include <Wire.h>
const int MPU_ADDR = 0x68; // I2C address of the MPU-6050
int16_t ax, ay, az;
enum Face {
FACE_UNKNOWN = 0,
FACE_POS_X = 1,
FACE_NEG_X = 2,
FACE_POS_Y = 3,
FACE_NEG_Y = 4,
FACE_POS_Z = 5,
FACE_NEG_Z = 6
};
Face currentFace = FACE_UNKNOWN;
void setup() {
[Link]();
[Link](9600);
// Wake up MPU-6050 (clear sleep bit)
[Link](MPU_ADDR);
[Link](0x6B); // PWR_MGMT_1 register
[Link](0); // set to zero (wakes up the MPU-6050)
[Link](true);
}
void loop() {
readAccel();
Face newFace = detectFace();
if (newFace != currentFace && newFace != FACE_UNKNOWN) {
currentFace = newFace;
[Link]("FACE:");
[Link]((int)currentFace);
}
delay(200); // adjust as needed
}
void readAccel() {
[Link](MPU_ADDR);
[Link](0x3B); // starting register for accel data
[Link](false);
[Link](MPU_ADDR, 6, true); // read 6 bytes (ax, ay, az)
ax = [Link]() << 8 | [Link]();
ay = [Link]() << 8 | [Link]();
az = [Link]() << 8 | [Link]();
}
Face detectFace() {
long ax_abs = abs(ax);
long ay_abs = abs(ay);
long az_abs = abs(az);
long maxVal = ax_abs;
Face face = FACE_UNKNOWN;
if (ay_abs > maxVal) {
maxVal = ay_abs;
face = (ay > 0) ? FACE_POS_Y : FACE_NEG_Y;
} else {
face = (ax > 0) ? FACE_POS_X : FACE_NEG_X;
}
if (az_abs > maxVal) {
maxVal = az_abs;
face = (az > 0) ? FACE_POS_Z : FACE_NEG_Z;
}
// Optional threshold to avoid noise
if (maxVal < 5000) { // adjust threshold after testing
return FACE_UNKNOWN;
}
return face;
}
ESP32 Code (Receive Face ID and Update OLED)
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
// I2C pins for ESP32 (can be adjusted)
#define OLED_SDA 21
#define OLED_SCL 22
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// Use UART2 on ESP32
HardwareSerial CubeSerial(2);
String incomingLine = "";
// Example labels for each face (you can customize)
const char* medicalLabels[] = {
"Unknown",
"Patient Alerts",
"Medication",
"Emergency",
"Appointments",
"Equipment",
"Lab Updates"
};
const char* restaurantLabels[] = {
"Unknown",
"New Orders",
"Prep Status",
"Reservations",
"Staff Alerts",
"Inventory",
"System Msgs"
};
void setup() {
[Link](115200);
[Link](OLED_SDA, OLED_SCL);
if () {
[Link]("SSD1306 allocation failed");
for (;;);
}
[Link]();
[Link](1);
[Link](SSD1306_WHITE);
[Link](0, 0);
[Link]("Interactive");
[Link]("Notification Cube");
[Link]();
// RX2 = GPIO16, TX2 = GPIO17
[Link](9600, SERIAL_8N1, 16, 17);
}
void loop() {
readFromNano();
}
void readFromNano() {
while ([Link]() > 0) {
char c = (char)[Link]();
if (c == '\n') {
processLine(incomingLine);
incomingLine = "";
} else if (c != '\r') {
incomingLine += c;
}
}
}
void processLine(String line) {
[Link]("Received: ");
[Link](line);
if ([Link]("FACE:")) {
int faceId = [Link](5).toInt();
updateDisplay(faceId);
}
}
void updateDisplay(int faceId) {
if (faceId < 0 || faceId > 6) {
faceId = 0;
}
[Link]();
[Link](1);
[Link](0, 0);
[Link]("Interactive Cube");
[Link](0, 16);
[Link]("Medical Mode:");
[Link](1);
[Link](0, 26);
[Link](medicalLabels[faceId]);
[Link](0, 42);
[Link](1);
[Link]("Restaurant Mode:");
[Link](0, 52);
[Link](restaurantLabels[faceId]);
[Link]();
}
Circuit Diagram (Block-Level)
The following block diagram shows the main connections between the battery, TP4056
charging module, ESP32, Arduino Nano, MPU-6050 sensor, and OLED display. Use this along
with the detailed wiring list above when building the actual hardware.