ADXL345 3-Axis Accelerometer Module Tutorial: Arduino I2C/SPI Wiring, Reading g-Forces, Tilt/Orientation, and Reliable Motion Detection

Beginner Tutorial Views: 1806

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 <strong>I2C</strong> and <strong>SPI</strong>, how to read raw acceleration data, convert it into <strong>g</strong> and <strong>m/s²</strong>, 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?”).

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?”).

Tutorial Beginner ? Intermediate Arduino Sensors Accelerometer ADXL345 I2C SPI Interrupts
What this module does: The ADXL345 measures acceleration on X, Y, and Z. That includes both motion acceleration and the constant acceleration due to gravity. When the sensor is not moving, it still measures ~1g in the direction of gravity, which you can use to estimate tilt/orientation.
Critical wiring rule: Don’t leave bus selection pins floating. For I2C, ensure CS is held HIGH. For SPI, ensure CS is controlled and SDO/SDI/SCK are wired correctly. A floating CS pin is a common cause of “no readings.”

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
Practical takeaway: Your project success is mostly about (1) wiring the bus correctly, (2) selecting a range that matches your motion, (3) filtering/thresholding correctly for stable detection.

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)
Bus mode rule of thumb: For I2C, keep CS HIGH. For SPI, control CS from the Arduino. If CS floats, the device may not respond reliably.

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
VCC5V (or 3.3V)Use 3.3V if you want maximum safety; many modules accept 5V.
GNDGNDCommon ground required.
SDAA4 (SDA)I2C data.
SCLA5 (SCL)I2C clock.
CS5VHold HIGH for I2C mode.
SDOGND or 5VSets 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)
Tip: If you have multiple I2C devices, confirm you do not have address collisions. The SDO pin lets you choose between two common ADXL345 I2C addresses.

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
VCC5V (or 3.3V)Prefer 3.3V if unsure about board tolerance.
GNDGNDCommon ground required.
SCL/SCKD13 (SCK)SPI clock.
SDA/SDI/MOSID11 (MOSI)Master ? sensor.
SDO/MISOD12 (MISO)Sensor ? master.
CSD10 (or any digital)Chip select (active LOW).
SPI gotcha: Make sure the library you use matches your wiring and expects the correct SPI mode. If you get all 0xFF or all 0x00 reads, suspect wiring or CS behavior first.

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
Best practice: Run an I2C scanner sketch first. It confirms wiring, power, and address in under 30 seconds.

7) Bring-up checklist (the fastest path to a first reading)

  1. Wire I2C (SDA/SCL/VCC/GND), set CS HIGH.
  2. Run an I2C scanner. Confirm you see 0x53 or 0x1D.
  3. Read the device ID register (DEVID). It should return a stable value (commonly 0xE5).
  4. Put the sensor into measurement mode (write the POWER_CTL register).
  5. Read X/Y/Z registers and print values.
Measurement mode: Many sensors power up in standby. If you never enable measurement mode, you may get zeros or stale data.

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.
Offsets are normal: Real accelerometers have zero-g bias and sensitivity tolerances. If you need accuracy, you calibrate.

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²))
Important limitation: During motion, acceleration includes gravity + movement acceleration. That will corrupt tilt angles unless you filter aggressively or fuse with a gyro.

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
Rule: Filter first, then apply thresholds/events. If you threshold raw jitter, you’ll get false triggers.

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

  1. Wire INT1 (or INT2) to an Arduino interrupt-capable pin (UNO: D2 or D3).
  2. Configure thresholds and durations in code/library.
  3. When interrupt triggers, read the interrupt source register to know what happened.
Interrupt gotcha: Always read/clear the interrupt source in firmware, otherwise the interrupt can stay latched and “never stop.”

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);
}
    
Sanity check: With the board still, the magnitude sqrt(gX²+gY²+gZ²) should be near 1.0. If it’s far off, suspect range/config or scaling/library mismatch.

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);
}
    
If angles jump during movement: that’s expected. For moving platforms, you need a gyro and sensor fusion.

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).");
  }
}
    
Why interrupts matter: Your Arduino can sleep or do other work and only wake/react when movement happens. This is how you build battery-friendly motion devices.

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
    

Products that this may apply to