DIY lean-angle logger
Record your own cycling lean angle with a small ESP32 microcontroller, a BNO055 orientation sensor and a microSD card. This guide covers the parts, wiring, mounting, calibration and code examples that explain the principles.
Krengo is a project under development. This page describes the general approach we are using; it does not promise specific measured results.
What you need
- ESP32 dev board. Check pin availability, power input and I2C compatibility with BNO055. Do not assume a Li-Po cell can connect to the 3.3 V pin; battery input, regulation and charging must suit the board.
- BNO055 IMU. A 9-axis sensor with built-in sensor fusion. It outputs calibrated orientation as Euler angles or quaternions over I2C. Default address is usually 0x28; some modules have a solder jumper to select 0x29.
- microSD module. Use a module rated for 3.3 V signals and the chosen supply. A module designed for 5 V input may have too much regulator voltage drop when supplied at 3.3 V.
- Battery. A single-cell Li-Po or Li-Ion with enough capacity for the intended ride length. A small 1000 mAh cell is a common starting point.
- Enclosure and mounting. A small box or 3D-printed case, plus straps, tape or screws to attach it under the saddle or on the seat tube.
Wiring
The wiring below is an example for a classic ESP32 with these pins available, not every ESP32 variant. Set I2C and SPI pins explicitly in software. Use 3.3 V logic, common ground and modules rated for the chosen supply.
| Signal | ESP32 pin | Notes |
|---|---|---|
| BNO055 VCC | 3.3 V | Not 5 V tolerant |
| BNO055 GND | GND | Common ground |
| BNO055 SDA | GPIO 21 | I2C data |
| BNO055 SCL | GPIO 22 | I2C clock |
| SD VCC | 3.3 V | Use a 3.3 V module |
| SD GND | GND | Common ground |
| SD CS | GPIO 5 | Chip select |
| SD MOSI | GPIO 23 | SPI MOSI |
| SD MISO | GPIO 19 | SPI MISO |
| SD SCK | GPIO 18 | SPI clock |
Some BNO055 boards have a small voltage regulator and can accept 5 V on their Vin pin, but the I2C lines must still be 3.3 V. Always check the module schematic before wiring.
Mounting the sensor
The sensor should be fixed firmly to the bike. Any vibration or rotation relative to the frame corrupts the measurement. Good mounting locations are:
- Under the saddle. Can be shielded from spray with a suitable enclosure. The sensor should be aligned with the bike's forward axis.
- On the seat tube. Also central and rigid, though slightly lower.
Static lean angle does not depend on sensor height. During motion, mounting position affects vibration and acceleration. Mount the sensor rigidly, document its axes and record orientation with the bike held upright. Compensate for the mounting transform in analysis.
Calibration
The BNO055 reports calibration status for four subsystems: system, gyroscope, accelerometer and magnetometer. Each returns a value from 0 to 3, where 3 is fully calibrated. You can read them with:
uint8_t sys, gyro, accel, mag;
bno.getCalibration(&sys, &gyro, &accel, &mag);
// Gyro: stationary. Accelerometer: six stable orientations.
Keep the sensor still for gyro calibration. Calibrate the accelerometer in six stable orientations: ±X, ±Y and ±Z upward. Use multi-axis movement, often a figure eight, for the magnetometer, away from magnetic interference. A status of 3 means that subsystem is calibrated; it does not prove riding accuracy.
Reading roll angle
The Adafruit library returns heading, roll and pitch in x(), y() and z() respectively for VECTOR_EULER. These are output names, not an automatic mapping to bike axes. Check sensor axis mapping and mounting with known movements before calling an output bike lean.
imu::Vector<3> euler = bno.getVector(Adafruit_BNO055::VECTOR_EULER);
float heading = euler.x();
float roll = euler.y();
float pitch = euler.z();
The BNO055 Euler output has limitations during combined rotations, not only near 90°. Adafruit recommends quaternions for general orientation. The expression below gives conventional ZYX roll from a normalised quaternion in the appropriate coordinate frame; it is not automatically bike lean. Converted Euler angles still have singularities, at pitch ±90° in this convention.
imu::Quaternion q = bno.getQuat();
float sinr_cosp = 2 * (q.w() * q.x() + q.y() * q.z());
float cosr_cosp = 1 - 2 * (q.x() * q.x() + q.y() * q.y());
float rollZYX = atan2(sinr_cosp, cosr_cosp) * 180.0 / PI;
Logging to SD card
20 Hz can be an initial logging design choice. Record actual timestamps, quaternions and calibration status so timing and mounting can be checked later. The CSV snippet below only illustrates file writing using an already calculated roll value; it is not a complete or hardware-verified logger program.
File log = SD.open("/lean.csv", FILE_APPEND);
if (log) {
log.print(millis());
log.print(",");
log.println(roll);
log.close();
}
Opening and closing for each sample is simple but adds SD work. Continuous logging can keep the file open, using buffered writes and a chosen flush interval. Check return values and measure gaps in sampling. Neither close() nor flush() guarantees protection from data loss or file corruption during power failure; use a controlled shutdown.
Power and runtime
Measure runtime on the actual board, sensor, SD card and power supply combination. Ideally, 1000 mAh / 200 mA gives five hours when current is measured at the battery. Usable capacity, regulator losses, temperature and cutoff voltage change this estimate. The supply also needs headroom for SD write current peaks.
From log file to insight
Check timestamps, missing samples and calibration status before interpreting peak angles. Compare known static tilts and dynamic motion against an independent reference. A moving average can hide peaks and cannot correct systematic orientation errors. Preserve raw data and document any filtering.
Where Krengo fits in
Krengo is building a logger along these lines: ESP32 + BNO055, microSD storage, battery power, and a mount under the saddle. The long-term idea is to make the data useful on a Garmin Edge via Bluetooth. For now, the project is focused on reliable logging and repeatable analysis.
Sources: Adafruit BNO055 guide and FAQ · Adafruit_BNO055 driver source.