The ADXL345 is a 3-axis accelerometer with tap, double-tap and free-fall detection built in. The GY-291 board is a common, cheap way to use it.
The board has a 3.3 V regulator, so it is powered from the Uno’s 5V, but the chip’s I2C pins stay at 3.3 V — so SDA and SCL go through a BSS138 level shifter. Two more wires are often skipped and cause most problems: CS to 3.3V for I2C mode and SDO to GND for address 0x53. The sketch prints acceleration in m/s² and the tilt angle, and lights the on-board LED when the module tilts past 20°.
Wiring
| Arduino Uno R3 | ADXL345 3-axis accelerometer (GY-291) | Wire | Notes |
|---|---|---|---|
| 5V | VCC | red | Into the GY-291’s 3.3 V regulator. |
| GND | GND | black | |
| SDA | SDA | blue | Via level shifter — Shifter LV = 3.3V, HV = 5V, GND shared. |
| SCL | SCL | yellow | Via level shifter |
| 3.3V | CS | red | CS HIGH locks the chip in I2C mode. |
| GND | SDO | black | SDO LOW = address 0x53 (HIGH = 0x1D). |
3.3–5 V on the GY-291 (chip itself 2.0–3.6 V) part, 5 V board. The GY-291 has a 3.3 V regulator and pulls SDA/SCL up to 3.3 V, so ESP32 and Pico connect directly. The chip’s pins are not 5 V tolerant: on an Uno it usually works because I2C lines are only pulled low, but a level shifter is the correct fix.
Board quirks
- The ADXL345 chip is rated to 3.6 V and its pins are not 5 V tolerant. The Wire library turns on the Uno’s internal pull-ups to 5 V, which would push the bus above the chip’s limit, and the board’s 3.3 V pull-ups only just reach the Uno’s HIGH threshold (0.7 × 5 V = 3.5 V). A BSS138 shifter solves both.
- CS and SDO are tied on purpose. CS to 3.3V keeps the chip in I2C mode, SDO to GND fixes the address at 0x53. Left floating, both can change from board to board.
- Use the Uno’s 3.3V pin for CS, not 5V — CS is a direct chip input with no level shifting on the GY-291.
- The tilt angle is measured between the sensor’s Z axis and gravity, so it is 0° flat and 90° on edge, whatever the direction. That is all a “has it fallen over?” alarm needs, for a ladder, a cupboard or an LPG cylinder trolley.
- Accelerometer tilt is only valid while the sensor is still: any shake adds to gravity. For a moving vehicle combine it with a gyro (MPU6050).
Code
// ADXL345 (GY-291) on an Arduino Uno R3 — tilt alarm with level-shifted I2C.
// VCC → 5V (board regulator), GND → GND, SDA/SCL → level shifter → SDA/SCL (A4/A5),
// CS → 3.3V (I2C mode), SDO → GND (address 0x53). Shifter: LV = 3.3V, HV = 5V, GND shared.
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_ADXL345_U.h>
Adafruit_ADXL345_Unified accel(12345);
const float TILT_ALARM_DEG = 20; // e.g. a cupboard, ladder or gas cylinder that should stay upright
void setup() {
Serial.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);
if (!accel.begin(0x53)) {
Serial.println(F("No ADXL345 at 0x53 - check the level shifter, CS on 3.3V and SDO on GND"));
while (true) delay(1000);
}
accel.setRange(ADXL345_RANGE_2_G); // finest steps for slow tilt
Serial.println(F("ADXL345 ready (+-2 g)"));
}
void loop() {
sensors_event_t e;
accel.getEvent(&e);
float x = e.acceleration.x, y = e.acceleration.y, z = e.acceleration.z;
// Angle between the sensor's Z axis and straight down: 0° when flat.
float tilt = degrees(acos(constrain(z / sqrt(x * x + y * y + z * z), -1.0, 1.0)));
bool alarm = tilt > TILT_ALARM_DEG;
digitalWrite(LED_BUILTIN, alarm);
Serial.print(F("X "));
Serial.print(x, 2);
Serial.print(F(" Y "));
Serial.print(y, 2);
Serial.print(F(" Z "));
Serial.print(z, 2);
Serial.print(F(" m/s2 tilt "));
Serial.print(tilt, 1);
Serial.println(alarm ? F(" deg TILTED") : F(" deg"));
delay(250);
}// ADXL345 (GY-291) on an Arduino Uno R3 — tilt alarm with level-shifted I2C.
// VCC → 5V (board regulator), GND → GND, SDA/SCL → level shifter → SDA/SCL (A4/A5),
// CS → 3.3V (I2C mode), SDO → GND (address 0x53). Shifter: LV = 3.3V, HV = 5V, GND shared.
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_ADXL345_U.h>
Adafruit_ADXL345_Unified accel(12345);
const float TILT_ALARM_DEG = 20; // e.g. a cupboard, ladder or gas cylinder that should stay upright
void setup() {
Serial.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);
if (!accel.begin(0x53)) {
Serial.println(F("No ADXL345 at 0x53 - check the level shifter, CS on 3.3V and SDO on GND"));
while (true) delay(1000);
}
accel.setRange(ADXL345_RANGE_2_G); // finest steps for slow tilt
Serial.println(F("ADXL345 ready (+-2 g)"));
}
void loop() {
sensors_event_t e;
accel.getEvent(&e);
float x = e.acceleration.x, y = e.acceleration.y, z = e.acceleration.z;
// Angle between the sensor's Z axis and straight down: 0° when flat.
float tilt = degrees(acos(constrain(z / sqrt(x * x + y * y + z * z), -1.0, 1.0)));
bool alarm = tilt > TILT_ALARM_DEG;
digitalWrite(LED_BUILTIN, alarm);
Serial.print(F("X "));
Serial.print(x, 2);
Serial.print(F(" Y "));
Serial.print(y, 2);
Serial.print(F(" Z "));
Serial.print(z, 2);
Serial.print(F(" m/s2 tilt "));
Serial.print(tilt, 1);
Serial.println(alarm ? F(" deg TILTED") : F(" deg"));
delay(250);
}Library: Adafruit ADXL345 (Adafruit (also install Adafruit Unified Sensor)) — compiled with Adafruit ADXL345 1.3.4.
Expected serial output
ADXL345 ready (+-2 g)
X 0.12 Y -0.08 Z 9.81 m/s2 tilt 0.8 deg
X 4.93 Y -0.10 Z 8.47 m/s2 tilt 30.2 deg TILTEDADXL345 ready (+-2 g) X 0.12 Y -0.08 Z 9.81 m/s2 tilt 0.8 deg X 4.93 Y -0.10 Z 8.47 m/s2 tilt 30.2 deg TILTED
Common errors
“No ADXL345 at 0x53”
Check the shifter’s LV (3.3V), HV (5V) and GND, then SDO on GND (or try 0x1D) and CS on 3.3V. Run the I2C scanner.
All axes read 0
CS floating — the chip dropped into SPI mode. Tie CS to 3.3V and reset.
It worked without a shifter, why add one?
It often does work, out of spec. The shifter keeps 5 V off a 3.6 V chip and gives clean HIGH levels, so it keeps working in a hot enclosure or on a long cable.