ADXL345 3-Axis Accelerometer Module Tutorial: Arduino I2C/SPI Wiring, Reading g-Forces, Tilt/Orientation, and Reliable Motion Detection
This tutorial is a detailed, practical guide to using the ADXL345 3-axis accelerometer module (Leobot Product #519) with Arduino. You will learn correct wiring for I2C and SPI, how to read raw acceleration data, convert it into g and m/s², compute basic tilt angles, and build robust motion features like activity detection, free-fall detection, and interrupt-driven events. You’ll also learn the common pitfalls (wrong I2C address, floating CS pin, noisy readings, and “why do my values jump?”).
1) What the ADXL345 is (and when to use it)
The ADXL345 is a 3-axis MEMS accelerometer. It’s a good choice when you want: motion detection, tilt/orientation estimation, vibration/impact detection, step-like motion features, or “which way is up?” sensing. It can communicate over I2C or SPI.
Use ADXL345 when
- You need 3-axis acceleration (X/Y/Z) in a compact, low-power module
- You want tilt/orientation (static gravity vector) without needing a gyroscope
- You want interrupt features like activity/inactivity, tap/double-tap, or free-fall events
- You’re okay with “tilt angles” being approximate (accelerometers alone cannot distinguish tilt from acceleration during motion)
Don’t use ADXL345 when
- You need full 3D orientation during movement (you’ll need a gyro + sensor fusion, e.g., IMU like MPU6050/MPU9250)
- You need ultra-low noise precision acceleration measurements (you’d choose a higher performance sensor)
- You need absolute position (accelerometers drift badly when double-integrated)
2) Key specs and what matters in real projects
- Axes: 3 (X, Y, Z)
- Interfaces: I2C or SPI
- Measurement: total acceleration (includes gravity)
- Ranges (typical ADXL345 capability): ±2g / ±4g / ±8g / ±16g (selectable)
- Output: signed raw values you scale into g
- Extras: interrupt engine for events (tap/activity/free-fall), FIFO buffering, selectable bandwidth
3) Pinout (module-level) and bus selection
Most ADXL345 breakout modules expose these pins (names vary slightly by board):
- VCC — power input (many modules accept 3.3V or 5V; always treat 3.3V as safest if unsure)
- GND — ground
- SDA — I2C data (or SPI SDI/MOSI on some boards)
- SCL — I2C clock (or SPI SCK)
- CS — chip select (LOW enables SPI mode; HIGH typically selects I2C mode)
- SDO — I2C address select / SPI MISO (this pin matters for the I2C address)
- INT1 / INT2 — interrupt outputs (optional but very useful)
4) Arduino wiring: I2C (recommended for most builds)
I2C uses only two signal wires (SDA/SCL), so it’s usually the simplest option.
4.1 Wiring table (Arduino UNO/Nano)
| ADXL345 | Arduino UNO/Nano | Notes |
|---|---|---|
| VCC | 5V (or 3.3V) | Use 3.3V if you want maximum safety; many modules accept 5V. |
| GND | GND | Common ground required. |
| SDA | A4 (SDA) | I2C data. |
| SCL | A5 (SCL) | I2C clock. |
| CS | 5V | Hold HIGH for I2C mode. |
| SDO | GND or 5V | Sets I2C address (see next section). |
4.2 Wiring table (Arduino Mega 2560)
- SDA = 20, SCL = 21 (or the dedicated SDA/SCL pins near AREF depending on board revision)
5) Arduino wiring: SPI (when you should use it)
SPI is more wires but can be more robust at high speed or in bus-heavy systems. Use SPI when you already have SPI infrastructure or need deterministic timing.
5.1 Wiring table (UNO/Nano hardware SPI)
| ADXL345 | Arduino UNO/Nano | Notes |
|---|---|---|
| VCC | 5V (or 3.3V) | Prefer 3.3V if unsure about board tolerance. |
| GND | GND | Common ground required. |
| SCL/SCK | D13 (SCK) | SPI clock. |
| SDA/SDI/MOSI | D11 (MOSI) | Master ? sensor. |
| SDO/MISO | D12 (MISO) | Sensor ? master. |
| CS | D10 (or any digital) | Chip select (active LOW). |
6) I2C address selection (0x53 vs 0x1D)
The ADXL345 commonly uses one of two 7-bit I2C addresses depending on the SDO/ALT-ADDRESS pin:
- SDO = GND ? address 0x53
- SDO = VCC ? address 0x1D
7) Bring-up checklist (the fastest path to a first reading)
- Wire I2C (SDA/SCL/VCC/GND), set CS HIGH.
- Run an I2C scanner. Confirm you see 0x53 or 0x1D.
- Read the device ID register (DEVID). It should return a stable value (commonly 0xE5).
- Put the sensor into measurement mode (write the POWER_CTL register).
- Read X/Y/Z registers and print values.
8) Understanding raw readings, ranges, and scaling to g
The sensor outputs signed values per axis. To convert to g, you apply a scale factor that depends on your configured range and data format. Many Arduino libraries do this for you.
8.1 “Sanity check” expectations
- When the board is flat on a table, one axis should read close to +1g or -1g (depending on orientation).
- The other two axes should read near 0g (but not exactly 0 due to offsets/noise).
- When you rotate the board 90°, the gravity component moves to a different axis.
9) Tilt / orientation estimation (basic angles)
When the sensor is mostly stationary, the gravity vector can be used to estimate pitch and roll. A common approach:
- Roll ˜ atan2(Y, Z)
- Pitch ˜ atan2(-X, sqrt(Y² + Z²))
10) Filtering and stable readings (moving average + LPF mindset)
Your raw readings will jitter. Filtering makes your project feel “professional.”
10.1 Practical filtering options
- Moving average: average the last N samples (good for smoothing, adds lag)
- Exponential low-pass: new = old*(1-a) + sample*a (simple, adjustable response)
- Thresholding with hysteresis: for motion detection, avoid rapid on/off around the threshold
11) Interrupt features (activity, tap, free-fall)
The ADXL345 includes an event engine that can trigger interrupts. This is valuable because it lets you:
- Wake the Arduino from sleep when motion occurs
- Detect taps/knocks without constantly polling
- Detect inactivity to save power
- Detect free-fall (rapid reduction towards 0g)
11.1 Practical approach
- Wire INT1 (or INT2) to an Arduino interrupt-capable pin (UNO: D2 or D3).
- Configure thresholds and durations in code/library.
- When interrupt triggers, read the interrupt source register to know what happened.
12) Arduino Example 1: I2C read and print X/Y/Z in g
This example uses a common ADXL345 Arduino library pattern. Install an ADXL345 library in the Arduino Library Manager (search for “ADXL345”), then adjust includes/class name if your chosen library differs.
/*
ADXL345 (#519) - Basic I2C Read (g units)
Wiring (UNO/Nano):
- VCC -> 5V (or 3.3V)
- GND -> GND
- SDA -> A4
- SCL -> A5
- CS -> HIGH (tie to VCC for I2C)
- SDO -> GND (0x53) or VCC (0x1D)
*/
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_ADXL345_U.h>
Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345);
void setup() {
Serial.begin(115200);
while (!Serial) {}
if (!accel.begin()) {
Serial.println("ADXL345 not detected. Check wiring/address.");
while (1) { delay(100); }
}
// Optional: set range (choose based on your motion)
// accel.setRange(ADXL345_RANGE_2_G);
// accel.setRange(ADXL345_RANGE_4_G);
// accel.setRange(ADXL345_RANGE_8_G);
// accel.setRange(ADXL345_RANGE_16_G);
Serial.println("ADXL345 OK");
}
void loop() {
sensors_event_t event;
accel.getEvent(&event);
// event.acceleration is in m/s^2 in Adafruit's unified sensor model
// Convert to g for intuitive printing (1g ˜ 9.80665 m/s^2)
float gx = event.acceleration.x / 9.80665;
float gy = event.acceleration.y / 9.80665;
float gz = event.acceleration.z / 9.80665;
Serial.print("gX: "); Serial.print(gx, 3);
Serial.print(" gY: "); Serial.print(gy, 3);
Serial.print(" gZ: "); Serial.println(gz, 3);
delay(50);
}
13) Arduino Example 2: Tilt angles (pitch/roll) from accelerometer
This example assumes you’re mostly stationary (tilt, not high motion). It uses accelerometer readings to estimate pitch and roll.
/*
ADXL345 (#519) - Basic Tilt Angles (Pitch/Roll)
Uses Adafruit_ADXL345_Unified for convenience.
*/
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_ADXL345_U.h>
Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345);
static float toDeg(float rad) { return rad * 57.2957795; } // 180/pi
void setup() {
Serial.begin(115200);
while (!Serial) {}
if (!accel.begin()) {
Serial.println("ADXL345 not detected.");
while (1) { delay(100); }
}
Serial.println("Tilt demo started.");
}
void loop() {
sensors_event_t e;
accel.getEvent(&e);
float ax = e.acceleration.x;
float ay = e.acceleration.y;
float az = e.acceleration.z;
// Compute roll and pitch (in radians)
float roll = atan2(ay, az);
float pitch = atan2(-ax, sqrt(ay*ay + az*az));
Serial.print("Roll: "); Serial.print(toDeg(roll), 1);
Serial.print(" deg Pitch: "); Serial.print(toDeg(pitch), 1);
Serial.println(" deg");
delay(100);
}
14) Arduino Example 3: Interrupt-driven motion event (activity)
This is a template approach: configure the sensor for activity detection and route it to INT1. Different libraries expose different activity APIs. The key concept is wiring INT to an Arduino interrupt pin and reacting quickly.
14.1 Wiring
- ADXL345 INT1 ? Arduino D2 (UNO/Nano interrupt)
- Keep the rest as per I2C wiring above
/*
ADXL345 (#519) - Interrupt Template (Activity)
NOTE: This is a concept template. You must adapt it to your chosen ADXL345 library's interrupt configuration calls.
*/
const int PIN_INT = 2;
volatile bool motionEvent = false;
void isrMotion() {
motionEvent = true;
}
void setup() {
Serial.begin(115200);
pinMode(PIN_INT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PIN_INT), isrMotion, FALLING);
// PSEUDO STEPS (library-specific):
// 1) Initialize ADXL345 over I2C
// 2) Put device in measurement mode
// 3) Configure ACTIVITY threshold/duration
// 4) Map ACTIVITY interrupt to INT1 pin
// 5) Enable ACTIVITY interrupt
//
// Then in loop() you read/clear the interrupt source register when motionEvent triggers.
}
void loop() {
if (motionEvent) {
motionEvent = false;
// PSEUDO:
// byte src = readInterruptSource();
// if (src & ACTIVITY_BIT) { ... }
Serial.println("Motion interrupt detected (activity).");
}
}
15) Troubleshooting (no device, weird values, noisy data)
I2C scanner finds nothing
- Check SDA/SCL pins (UNO: A4/A5) and confirm common ground.
- Ensure CS is held HIGH for I2C mode.
- Try SDO to GND (0x53) and then to VCC (0x1D).
- Verify power: try 3.3V if 5V seems suspicious (or vice versa).
I2C scanner shows an address, but readings are always zero
- Ensure the sensor is set to measurement mode (POWER_CTL configuration).
- Confirm your library is using the correct address.
Readings are extremely noisy / jumpy
- Add filtering (moving average or exponential smoothing).
- Physically mount the sensor firmly (loose modules vibrate).
- Improve power stability (short wires, solid ground, avoid motor noise on same rail).
Values are “wrong direction”
- Your axis orientation depends on how the board is mounted.
- Fix in software by remapping axes and/or flipping signs.
SPI gives 0xFF or 0x00 reads
- CS wiring/logic wrong (must go LOW during transfers).
- MISO/MOSI swapped or wrong pins used.
- Library SPI mode mismatch (try a known-good library example first).
16) Quick checklist
ADXL345 Module (#519) Checklist
-------------------------------
? Decide bus: I2C (simpler) or SPI (more control)
? For I2C: CS held HIGH; SDA/SCL wired correctly; common ground confirmed
? Confirm I2C address with scanner (0x53 or 0x1D)
? Enable measurement mode (device not left in standby)
? Validate sanity: magnitude near 1g at rest
? Apply filtering before thresholding/events
? For tilt: only trust angles when mostly stationary
? If using interrupts: wire INT1/INT2 to Arduino interrupt pin and clear interrupt source
? Mount sensor firmly to reduce vibration artifacts