When a fixture shifts, a cabinet door drifts out of alignment, or a machine frame slowly settles, you usually find out after the defect appears unless you’re measuring tilt continuously.
A tilt sensor (inclinometer) doesn’t have to be a complex embedded project. In this guide you’ll build a practical, production-style tilt monitor using Electromake’s Three Axes USB Accelerometer, a plug-and-play device that streams X/Y/Z acceleration as CSV over a USB port, so you can read it from almost any software stack.
By the end, you’ll have:
- live tilt angle (pitch/roll) in degrees
- a stable “tilt alarm” with hysteresis + time qualification
- clean logs you can feed into dashboards, QA reports, or IIoT pipelines
Product used: Three axes USB accelerometer sensor (ADXL345-based)
Why a USB accelerometer is the fastest path to a tilt sensor
Industrial teams and makers run into the same bottlenecks:
- “We need a tilt alarm… but we don’t want to spin a PCB.“
- “We need something we can mount today and validate in software.“
- “We need data now, and we’ll productize later.“
Electromake’s USB Accelerometer is designed for exactly that workflow:
- Plug & play on PC/Mac/Raspberry Pi/Android
- Streams data in CSV (easy parsing)
- You can set sampling frequency (0.1–3200 Hz) and measurement range (±2/4/8/16 g) with simple ASCII commands.
- Mountable form factor (50 × 26 × 5 mm, 2.5 mm holes)
For tilt sensing specifically, you typically don’t need extreme bandwidth, what you need is repeatability, filtering, and robust threshold logic. This tutorial focuses on that.
What you need
Hardware
- Electromake Three Axes USB Accelerometer sensor
- Micro-USB cable (or the Electromake cable accessory, if you want a known-good cable)
- Mounting: screws + bracket / adhesive + a flat reference surface
Software
- SerialPlot for instant real-time plotting (great for commissioning)
- Python 3 + pyserial for the tilt sensor logic (alarm + logs)
Step 1 — Connect and validate the CSV stream
1) Plug in the sensor
The device presents itself as a serial/virtual COM device (Windows may use ST’s VCP driver; on newer Windows versions the native driver is recommended).
2) Quick reality check in SerialPlot
Electromake recommends SerialPlot for real-time visualization and sending commands.
SerialPlot supports ASCII CSV input, multi-channel plotting, and sending commands, perfect for setting frequency/range during testing.
What you should see: three channels (X, Y, Z). When the sensor is still, the vector magnitude should be close to 1 g (gravity), with most of it appearing on the axis pointing “up/down”.
3) Set a sensible sampling rate for tilt
For tilt monitoring, start with 25–100 Hz:
- low enough to smooth noise easily
- high enough for responsive alarms
The device supports frequency changes via: FREQ 25, FREQ 50, FREQ 100, etc.
Step 2 — Convert acceleration to tilt (pitch & roll)
The key idea (why tilt works at all)
An accelerometer measures the combination of linear acceleration and gravity. If your device is not moving quickly (no strong linear acceleration), the sensor mostly “sees” gravity, and you can compute orientation angles from that gravity vector.
Pitch/roll formulas (stable, widely used)
A common approach is:
- Roll angle: $$\varphi = \arctan2{(a_y,a_z)}$$
- Pitch angle: $$\theta=\arctan2{\left(-a_x, \sqrt{a_y^2+a_z^2}\right)}$$
These are standard tilt-from-accelerometer relationships and are widely documented.
Note: Accelerometers cannot determine yaw (rotation around gravity) from gravity alone.
Step 3 — Build the tilt sensor logic in Python (threshold + hysteresis + time)
Electromake provides a Python serial example showing:
- reading CSV lines from the serial port
- sending commands like
RANGEandFREQ
Below is a production-friendly tilt monitor script (Python 3) that:
- reads X/Y/Z
- converts ADC samples to g
- computes pitch/roll (degrees)
- applies a moving average filter
- triggers an alarm only if tilt exceeds a threshold for N consecutive samples
- uses hysteresis to prevent alarm chattering
Python code (left click to expand)
import math
import time
from collections import deque
import serial
# -----------------------
# User settings
# -----------------------
PORT = "/dev/ttyACM0" # Windows example: "COM5"
BAUD = 115200 # Typical for VCP devices; adjust if needed
RANGE_G = 4 # ±2/4/8/16 supported by device
FREQ_HZ = 50 # 25–100 Hz is a good start for tilt monitoring
WINDOW = 15 # moving average length (samples)
TILT_ON_DEG = 10.0 # alarm ON threshold
TILT_OFF_DEG = 8.0 # alarm OFF threshold (hysteresis)
HOLD_TIME_S = 0.30 # must exceed threshold this long to trigger
# If your device outputs raw counts (as in Electromake sample),
# scale similar to their example. Keep it configurable:
COUNTS_PER_2G = 512.0 # from Electromake sample scaling approach
# -----------------------
def send_cmd(ser: serial.Serial, cmd: str) -> None:
# Commands are ASCII strings (device supports START/STOP/FREQ/RANGE)
ser.write((cmd.strip() + "\n").encode("ascii", errors="ignore"))
def parse_csv_line(line: bytes):
# Expect: b"40,-100,127\r\n"
try:
s = line.decode("ascii", errors="ignore").strip()
parts = s.split(",")
if len(parts) != 3:
return None
return [float(parts[0]), float(parts[1]), float(parts[2])]
except Exception:
return None
def counts_to_g(v_counts, range_g: float) -> float:
# Electromake demo scales by (RANGE / 512.0) for a magnitude estimate. :contentReference[oaicite:20]{index=20}
# Here we apply a similar scale per-axis (assumes ±2g corresponds to ~512 counts).
return (v_counts / COUNTS_PER_2G) * (range_g / 2.0)
def tilt_angles_deg(ax_g, ay_g, az_g):
roll = math.degrees(math.atan2(ay_g, az_g))
pitch = math.degrees(math.atan2(-ax_g, math.sqrt(ay_g * ay_g + az_g * az_g)))
return pitch, roll
def mean(vals):
return sum(vals) / len(vals)
def main():
ser = serial.Serial(PORT, BAUD, timeout=1)
# Configure device (commands listed on product page) :contentReference[oaicite:21]{index=21}
send_cmd(ser, f"RANGE {RANGE_G}")
send_cmd(ser, f"FREQ {FREQ_HZ}")
send_cmd(ser, "START")
ax_buf, ay_buf, az_buf = deque(maxlen=WINDOW), deque(maxlen=WINDOW), deque(maxlen=WINDOW)
samples_needed = max(1, int(HOLD_TIME_S * FREQ_HZ))
above_counter = 0
alarm = False
print("timestamp,pitch_deg,roll_deg,alarm")
try:
while True:
raw = ser.readline()
parsed = parse_csv_line(raw)
if not parsed:
continue
ax_c, ay_c, az_c = parsed
ax_g = counts_to_g(ax_c, RANGE_G)
ay_g = counts_to_g(ay_c, RANGE_G)
az_g = counts_to_g(az_c, RANGE_G)
ax_buf.append(ax_g)
ay_buf.append(ay_g)
az_buf.append(az_g)
# Wait until filter window fills
if len(ax_buf) < WINDOW:
continue
ax_f = mean(ax_buf)
ay_f = mean(ay_buf)
az_f = mean(az_buf)
pitch, roll = tilt_angles_deg(ax_f, ay_f, az_f)
tilt = max(abs(pitch), abs(roll)) # simple “worst-axis” tilt metric
# Alarm logic with hysteresis + hold time
if not alarm:
if tilt >= TILT_ON_DEG:
above_counter += 1
if above_counter >= samples_needed:
alarm = True
above_counter = 0
else:
above_counter = 0
else:
if tilt <= TILT_OFF_DEG:
alarm = False
ts = time.time()
print(f"{ts:.3f},{pitch:.2f},{roll:.2f},{int(alarm)}")
finally:
try:
send_cmd(ser, "STOP")
except Exception:
pass
ser.close()
if __name__ == "__main__":
main()
Why this logic is “industrial-friendly”
- Hysteresis prevents rapid toggling near the threshold (common in vibration environments).
- Time qualification prevents alarms caused by single-sample spikes.
- Moving average improves stability without heavy math (and you can upgrade later to FIR/IIR).
If your environment is vibration-heavy, consider stronger filtering. Electromake’s own tutorial shows how FIR filtering can remove high-frequency noise and bias effects in accelerometer signals.
- How to remove noise from accelerometer data
- IIR — Infinite impulse response filter design with examples
Step 4 — Logging and integration ideas (from prototype to plant floor)
Once you have timestamp,pitch,roll,alarm in CSV, you can route it almost anywhere:
Quick wins
- Write to a file and attach logs to maintenance tickets
- Feed the stream into Grafana/Influx via Telegraf exec input
- Add a “tilt event” row to your MES/QA database
Common industrial integration patterns
- Edge gateway (Raspberry Pi / industrial PC) → MQTT → SCADA / IIoT platform
- Edge gateway → OPC UA server → PLC/SCADA clients
- Edge gateway → Modbus TCP (expose alarm bit + angles as registers)
The reason this is easy with the Electromake USB Accelerometer is the CSV serial stream, no proprietary decoding required.
Mounting & calibration checklist (don’t skip this)
Tilt sensors are only as good as their mechanical reference.
Mounting best practices
- Mount on a flat, repeatable surface.
- Mark the axes (X/Y/Z) so technicians can reinstall consistently.
- Avoid soft foam tapes if you need repeatability (they creep).
Zero calibration (recommended)
- Place the device in your “known level” reference position
- Record 5–10 seconds and compute the average pitch/roll
- Subtract those offsets in software (gives you “tilt relative to your machine”, not Earth)
Sampling & range tips
- Tilt sensing: 25–100 Hz, ±2g or ±4g is usually enough
- Mixed tilt + shocks: raise range (±8/±16g) and filter more
Device command set (START/STOP/FREQ/RANGE) is documented on the product page.
Troubleshooting
No COM port appears on Windows
- Check driver guidance on the product page (ST VCP / native inbox driver notes).
Angles are noisy
- Lower FREQ, increase WINDOW, or apply FIR/IIR filtering (Electromake FIR tutorial is a good starting point).
Tilt looks wrong when the machine moves
- That’s expected: linear acceleration contaminates gravity-based tilt. Tilt from accelerometer is best in static/quasi-static conditions.
Ready to build yours?
If you want the quickest route from idea → mounted hardware → real tilt data, the Three axes USB accelerometer sensor is built for that plug-and-play workflow: CSV streaming, adjustable sampling/range, and a mountable form factor.
- Product: Three axes USB accelerometer sensor (Electromake)
- Optional accessory: Micro-USB cable (known-good for installations)

0 Comments