Add the motion sensor. It learns which way is "upright", then the OLED shows how many degrees you have tilted it.
| Part | How many |
|---|---|
| Wiring from Lesson 1 | |
| GY-521 (MPU-6050) motion sensor | 1 |
| Male-to-male jumper wires | 4 |
Nothing extra - just the kit and your computer.
Sitting still, the accelerometer feels only gravity: 1 g pointing down. Which of its x, y and z axes feel it tells us which way the sensor is facing.
For 3 seconds the sketch averages 150 readings while you keep it still. That direction becomes "upright", however the sensor is mounted.
After that it compares each new reading with "upright" and works out the angle between them (with acos of the dot product). 0° = as at the start, 90° = lying on its side.
The OLED and the GY-521 share SDA and SCL. They do not get mixed up because each has its own address.
Every item is in the kit — nothing extra to buy.
All 6 parts in one box, with this project and 4 more to build from them.
One of each part. Packs (resistor assortment, jumper wires) already hold more than a single build needs. Backorder parts ship once they arrive. Delivery is AED 20, free on orders AED 100+.
Unplug USB before you change any wire. Pin names are the labels printed on the board.
| Part / pin | Connect to | Note |
|---|---|---|
| GY-521 VCC | + rail (3V3) | |
| GY-521 GND | − rail (GND) | |
| GY-521 SDA | GPIO21 | Same wire row as the OLED SDA |
| GY-521 SCL | GPIO22 | Same wire row as the OLED SCL |
| GY-521 XDA, XCL, AD0, INT | Nothing | Leave empty |
| OLED, button | As in Lesson 1 | GPIO21, GPIO22, GPIO26 |
One thing at a time. Tick nothing off until it matches the diagram.
Written for the board in the kit. Copy it into the Arduino IDE; change the pin numbers if you wire it differently.
/*
DIY KIT - ESP32 Fall-Detection Learning Prototype
Lesson 2 of 5: Tilt Angle from a Baseline
WIRING (unplug the USB cable before you wire)
Keep the OLED and button from Lesson 1.
GY-521 (MPU-6050) VCC -> 3V3 GND -> GND SDA -> GPIO21 SCL -> GPIO22
The OLED and the GY-521 share the same two I2C wires (SDA and SCL).
Leave XDA, XCL, AD0 and INT on the GY-521 unconnected.
WHAT SHOULD HAPPEN
Keep the sensor still for 3 seconds while it learns "upright". Then the
OLED shows the angle you have tilted it from that position. Past 55
degrees it says TILTED. Press the button to learn a new "upright".
Serial Monitor (115200) or Serial Plotter shows angle_deg.
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
const int SDA_PIN = 21;
const int SCL_PIN = 22;
const int BUTTON_PIN = 26;
const float TILT_DEG = 55.0; // the "lying down" angle used in Lesson 5
const int CALIBRATION_SAMPLES = 150; // 150 x 20 ms = 3 seconds
const unsigned long SAMPLE_MS = 20;
const unsigned long REPORT_MS = 250;
Adafruit_SSD1306 display(128, 64, &Wire, -1);
bool displayOk = false;
int mpuAddress = 0; // 0x68 or 0x69, found at start
float baseX = 0, baseY = 0, baseZ = 1; // the "upright" direction of gravity
unsigned long lastSample = 0, lastReport = 0;
bool lastButton = HIGH;
bool answers(int address) {
Wire.beginTransmission(address);
return Wire.endTransmission() == 0;
}
// Read the three accelerometer axes, in g. 1 g = the pull of gravity.
bool readAccel(float &x, float &y, float &z) {
Wire.beginTransmission(mpuAddress);
Wire.write(0x3B); // the first acceleration register
if (Wire.endTransmission(false) != 0) return false;
if (Wire.requestFrom(mpuAddress, 6) != 6) return false;
int16_t rx = (int16_t)((Wire.read() << 8) | Wire.read());
int16_t ry = (int16_t)((Wire.read() << 8) | Wire.read());
int16_t rz = (int16_t)((Wire.read() << 8) | Wire.read());
x = rx / 16384.0; // +-2 g range: 16384 counts per g
y = ry / 16384.0;
z = rz / 16384.0;
return true;
}
void show(const char *line1, int angle) {
if (!displayOk) return;
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("TILT ANGLE LAB");
display.println(line1);
if (angle >= 0) {
display.setTextSize(3);
display.setCursor(0, 22);
display.print(angle);
display.setTextSize(2);
display.print(" deg");
}
display.setTextSize(1);
display.setCursor(0, 56);
display.print("limit ");
display.print((int)TILT_DEG);
display.print(" btn=reset"); // "limit 55 btn=reset": 21 characters fit on a line
display.display();
}
// Average 150 readings while the sensor is still: that is "upright".
bool calibrate() {
show("KEEP STILL: 3 s", -1);
Serial.println("Calibrating - keep the sensor still");
float sx = 0, sy = 0, sz = 0;
int good = 0;
for (int i = 0; i < CALIBRATION_SAMPLES; i++) {
float x, y, z;
if (readAccel(x, y, z)) {
sx += x; sy += y; sz += z;
good++;
}
delay(SAMPLE_MS);
}
float length = sqrt(sx * sx + sy * sy + sz * sz);
if (good == 0 || length < 0.001) return false;
baseX = sx / length; // keep only the direction
baseY = sy / length;
baseZ = sz / length;
Serial.println("Calibrated");
return true;
}
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
Wire.begin(SDA_PIN, SCL_PIN);
displayOk = display.begin(SSD1306_SWITCHCAPVCC, 0x3C) || display.begin(SSD1306_SWITCHCAPVCC, 0x3D);
if (!displayOk) Serial.println("OLED not found - see Lesson 1");
if (answers(0x68)) mpuAddress = 0x68;
else if (answers(0x69)) mpuAddress = 0x69;
if (mpuAddress == 0) {
Serial.println("GY-521 not found - unplug USB and check VCC 3V3, GND, SDA GPIO21, SCL GPIO22");
show("GY-521 NOT FOUND", -1);
while (true) delay(1000); // stop here
}
Serial.print("GY-521 found at 0x");
Serial.println(mpuAddress, HEX);
Wire.beginTransmission(mpuAddress); // wake the sensor up (it starts asleep)
Wire.write(0x6B);
Wire.write(0x00);
Wire.endTransmission();
delay(50);
if (!calibrate()) Serial.println("Calibration failed - is the sensor wired?");
}
void loop() {
bool button = digitalRead(BUTTON_PIN);
if (button == LOW && lastButton == HIGH) calibrate(); // press: learn a new "upright"
lastButton = button;
if (millis() - lastSample < SAMPLE_MS) return;
lastSample = millis();
float x, y, z;
if (!readAccel(x, y, z)) return;
float length = sqrt(x * x + y * y + z * z);
if (length < 0.001) return;
// The angle between "upright" and gravity now: the dot product of the two directions.
float dot = (x * baseX + y * baseY + z * baseZ) / length;
float angle = acos(constrain(dot, -1.0, 1.0)) * 180.0 / PI;
if (millis() - lastReport >= REPORT_MS) {
lastReport = millis();
Serial.print("angle_deg:");
Serial.print(angle, 1);
Serial.print(",limit:");
Serial.println(TILT_DEG, 0);
show(angle >= TILT_DEG ? "TILTED past the limit" : "upright", (int)angle);
}
}
| In the code | What it does |
|---|---|
| readAccel(x, y, z) | Reads the 6 bytes of acceleration from the GY-521 over I2C and turns them into g. |
| calibrate() | Averages 150 readings and keeps only the direction: the "upright" baseline. |
| acos(dot) * 180 / PI | The angle between "upright" and gravity now, in degrees. |
| answers(0x68) | Asks "is anyone at address 0x68?" - that is how the sketch finds the sensor. |
| Problem | What to check |
|---|---|
| "GY-521 not found" | Unplug USB. Check VCC to 3V3, GND, SDA to GPIO21 and SCL to GPIO22. Are the GY-521 header pins soldered? Loose pins pushed through the holes do not make contact. |
| The angle never comes back to 0 | It moved during the 3 seconds. Press the button and keep still. |
| The angle jumps about | Tape the sensor down firmly - a loose sensor wobbles. |
| "OLED not found" or a black screen | Unplug USB. Check OLED VCC to 3V3 (not 5V), GND, SDA to GPIO21 and SCL to GPIO22 - by the labels printed on the OLED. |
| Nothing, or strange symbols, in the Serial Monitor | Set the speed in the corner of the Serial Monitor to 115200 baud. |
Calibrate, then turn the book upside down. What angle does it show? Why is 180° the biggest angle you can get?
Gravity: about 1 g.
So the angle is right however the sensor is mounted.
Each has its own I2C address.
Most problems are one wire in the wrong hole. Check these first:
Open the troubleshooting table for this step →
Hi DIY KIT, I'm stuck on the ESP32 Fall-Detection Learning Prototype. Lesson: __ Step number: __ What should happen: __ What happens instead: __ Error message (copied exactly): __ I'll attach a photo of my wiring.
Watch total g-force on a live graph and count every impact (above 2.5 g) and free fall (below 0.45 g). Choose the main build's limits from your own data.
A button starts a 20-second countdown on the OLED and a second press cancels it - the user-interface half of the fall detector, with no motion yet. Do Start here first.
Add the buzzer module (an optional add-on), powered from 3V3: an alert pattern the button acknowledges, with a 15-second time-out so it never runs for ever.
We use cookies that the shop needs to work — signing in, your cart, form security. We also measure how the shop is used so we can stock the right parts and fix what is broken. Choose “Only essential” and we will not load Google Analytics or the Meta pixel, and will not record your visit. Read more.