Skip to content

Commit 8af407d

Browse files
authored
feat(lsm6dso): Add LSM6DSO 6-axis IMU component (#460)
* feat(lsm6dso): Add LSM6DSO 6-axis IMU component * add screenshots * remove bad kconfig * fix spelling * readme update * add datasheet link
1 parent 79b755a commit 8af407d

18 files changed

+1445
-0
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ jobs:
117117
target: esp32
118118
- path: 'components/logger/example'
119119
target: esp32
120+
- path: 'components/lsm6dso/example'
121+
target: esp32s3
120122
- path: 'components/math/example'
121123
target: esp32
122124
- path: 'components/matouch-rotary-display/example'

.github/workflows/upload_components.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ jobs:
7676
components/led
7777
components/led_strip
7878
components/logger
79+
components/lsm6dso
7980
components/math
8081
components/matouch-rotary-display
8182
components/max1704x

components/lsm6dso/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
idf_component_register(
2+
INCLUDE_DIRS "include"
3+
SRC_DIRS "src"
4+
REQUIRES "base_peripheral" "math"
5+
)

components/lsm6dso/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# LSM6DSO - 6-Axis IMU Driver (espp component)
2+
3+
[![Badge](https://components.espressif.com/components/espp/lsm6dso/badge.svg)](https://components.espressif.com/components/espp/lsm6dso)
4+
5+
This is an espp component for the LSM6DSO 6-axis IMU (3-axis accelerometer +
6+
3-axis gyroscope) from STMicroelectronics. It supports both I2C and SPI
7+
interfaces, FIFO, interrupts, tap/event detection, and on-chip filtering. The
8+
driver is designed for use with the ESP-IDF and espp framework, and is modeled
9+
after the ICM42607 and MT6701 components.
10+
11+
## Features
12+
- Templated C++ driver supporting I2C and SPI
13+
- Configurable accelerometer and gyroscope range, output data rate, and on-chip filtering.
14+
- Orientation filtering (algorithmic)
15+
- Example application for ESP-IDF
16+
17+
## Example
18+
19+
The [example](./example) shows how to use the `espp::Lsm6dso` component to
20+
initialize and communicate with an LSM6DSO 6-axis IMU.
21+
22+
## Documentation
23+
See the [documentation](https://esp-cpp.github.io/espp/imu/lsm6dso.html) for
24+
full API details.
25+
26+
## License
27+
See LICENSE file in the root of the repository.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# The following lines of boilerplate have to be in your project's CMakeLists
2+
# in this exact order for cmake to work correctly
3+
cmake_minimum_required(VERSION 3.20)
4+
5+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
6+
7+
# add the component directories that we want to use
8+
set(EXTRA_COMPONENT_DIRS
9+
"../../../components/"
10+
)
11+
12+
set(
13+
COMPONENTS
14+
"main esptool_py i2c lsm6dso filters"
15+
CACHE STRING
16+
"List of components to include"
17+
)
18+
19+
project(lsm6dso_example)
20+
21+
set(CMAKE_CXX_STANDARD 20)

components/lsm6dso/example/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# LSM6DSO Example
2+
3+
This example demonstrates how to use the espp LSM6DSO 6-axis IMU driver with the
4+
ESP-IDF. The example is modeled after the ICM42607 example and shows how to
5+
configure the IMU, read accelerometer and gyroscope data, and use orientation
6+
filtering (e.g., Madgwick filter).
7+
8+
![CleanShot 2025-06-18 at 14 16 01](https://github.com/user-attachments/assets/10b15539-688b-4711-b1ce-e7bb33f7e343)
9+
10+
## Features
11+
- I2C communication with the LSM6DSO
12+
- Configurable accelerometer and gyroscope range and output data rate
13+
- Periodic reading of accelerometer, gyroscope, and temperature data
14+
- Orientation filtering using Madgwick filter
15+
16+
## Usage
17+
- Configure the I2C pins and address in `sdkconfig` or via Kconfig options
18+
- Build and flash the example to your ESP32/ESP-IDF target
19+
- The example will print IMU data and orientation to the serial console
20+
21+
### Build and Flash
22+
23+
Build the project and flash it to the board, then run monitor tool to view
24+
serial output:
25+
26+
```
27+
idf.py -p PORT flash monitor
28+
```
29+
30+
(Replace PORT with the name of the serial port to use.)
31+
32+
(To exit the serial monitor, type ``Ctrl-]``.)
33+
34+
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
35+
36+
## Example Output
37+
38+
![CleanShot 2025-06-18 at 14 15 13](https://github.com/user-attachments/assets/26b07fa0-4e4a-4025-a5a7-135322da8d3b)
39+
![CleanShot 2025-06-18 at 14 16 01](https://github.com/user-attachments/assets/10b15539-688b-4711-b1ce-e7bb33f7e343)
40+
41+
## Example Code
42+
See `main/lsm6dso_example.cpp` for the full example source code.
43+
44+
## Configuration
45+
- Default I2C address: 0x6A (can be changed in Kconfig or via config struct)
46+
- Example I2C pins: SDA = 21, SCL = 22
47+
48+
## Documentation
49+
See the [documentation](https://esp-cpp.github.io/espp/imu/lsm6dso.html) for
50+
full API details.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
idf_component_register(SRC_DIRS "."
2+
INCLUDE_DIRS ".")
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
menu "Example Configuration"
2+
3+
choice EXAMPLE_HARDWARE
4+
prompt "Hardware"
5+
default EXAMPLE_HARDWARE_QTPYPICO
6+
help
7+
Select the hardware to run this example on.
8+
9+
config EXAMPLE_HARDWARE_QTPYPICO
10+
depends on IDF_TARGET_ESP32
11+
bool "Qt Py PICO"
12+
13+
config EXAMPLE_HARDWARE_QTPYS3
14+
depends on IDF_TARGET_ESP32S3
15+
bool "Qt Py S3"
16+
17+
config EXAMPLE_HARDWARE_CUSTOM
18+
bool "Custom"
19+
endchoice
20+
21+
config EXAMPLE_I2C_SCL_GPIO
22+
int "SCL GPIO Num"
23+
range 0 50
24+
default 19 if EXAMPLE_HARDWARE_QTPYPICO
25+
default 40 if EXAMPLE_HARDWARE_QTPYS3
26+
default 19 if EXAMPLE_HARDWARE_CUSTOM
27+
help
28+
GPIO number for I2C Master clock line.
29+
30+
config EXAMPLE_I2C_SDA_GPIO
31+
int "SDA GPIO Num"
32+
range 0 50
33+
default 22 if EXAMPLE_HARDWARE_QTPYPICO
34+
default 41 if EXAMPLE_HARDWARE_QTPYS3
35+
default 22 if EXAMPLE_HARDWARE_CUSTOM
36+
help
37+
GPIO number for I2C Master data line.
38+
39+
config EXAMPLE_I2C_CLOCK_SPEED_HZ
40+
int "I2C Clock Speed"
41+
range 100 1000000
42+
default 400000
43+
help
44+
I2C clock speed in Hz.
45+
46+
endmenu
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#include <chrono>
2+
#include <cmath>
3+
#include <vector>
4+
5+
#include "i2c.hpp"
6+
#include "kalman_filter.hpp"
7+
#include "logger.hpp"
8+
#include "lsm6dso.hpp"
9+
#include "madgwick_filter.hpp"
10+
11+
using namespace std::chrono_literals;
12+
13+
extern "C" void app_main(void) {
14+
espp::Logger logger({.tag = "LSM6DSO Example", .level = espp::Logger::Verbosity::INFO});
15+
logger.info("Starting LSM6DSO example!");
16+
17+
//! [lsm6dso example]
18+
using Imu = espp::Lsm6dso<espp::lsm6dso::Interface::I2C>;
19+
20+
// I2C config (customize as needed)
21+
static constexpr auto i2c_port = I2C_NUM_0;
22+
static constexpr auto i2c_clock_speed = CONFIG_EXAMPLE_I2C_CLOCK_SPEED_HZ; // Set in sdkconfig
23+
static constexpr gpio_num_t i2c_sda = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO; // Set in sdkconfig
24+
static constexpr gpio_num_t i2c_scl = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO; // Set in sdkconfig
25+
espp::I2c i2c({.port = i2c_port,
26+
.sda_io_num = i2c_sda,
27+
.scl_io_num = i2c_scl,
28+
.sda_pullup_en = GPIO_PULLUP_ENABLE,
29+
.scl_pullup_en = GPIO_PULLUP_ENABLE,
30+
.clk_speed = i2c_clock_speed});
31+
32+
// make the orientation filter to compute orientation from accel + gyro
33+
static constexpr float angle_noise = 0.001f;
34+
static constexpr float rate_noise = 0.1f;
35+
static espp::KalmanFilter<2> kf;
36+
kf.set_process_noise(rate_noise);
37+
kf.set_measurement_noise(angle_noise);
38+
39+
auto kalman_filter_fn = [](float dt, const Imu::Value &accel,
40+
const Imu::Value &gyro) -> Imu::Value {
41+
// Apply Kalman filter
42+
float accelRoll = atan2(accel.y, accel.z);
43+
float accelPitch = atan2(-accel.x, sqrt(accel.y * accel.y + accel.z * accel.z));
44+
kf.predict({espp::deg_to_rad(gyro.x), espp::deg_to_rad(gyro.y)}, dt);
45+
kf.update({accelRoll, accelPitch});
46+
float roll, pitch;
47+
std::tie(roll, pitch) = kf.get_state();
48+
// return the computed orientation
49+
Imu::Value orientation{};
50+
orientation.roll = roll;
51+
orientation.pitch = pitch;
52+
orientation.yaw = 0.0f;
53+
return orientation;
54+
};
55+
56+
// Madgwick filter for orientation
57+
static constexpr float beta = 0.1f;
58+
static espp::MadgwickFilter madgwick(beta);
59+
auto madgwick_filter_fn = [](float dt, const Imu::Value &accel,
60+
const Imu::Value &gyro) -> Imu::Value {
61+
madgwick.update(dt, accel.x, accel.y, accel.z, espp::deg_to_rad(gyro.x),
62+
espp::deg_to_rad(gyro.y), espp::deg_to_rad(gyro.z));
63+
float roll, pitch, yaw;
64+
madgwick.get_euler(roll, pitch, yaw);
65+
Imu::Value orientation{};
66+
orientation.roll = espp::deg_to_rad(roll);
67+
orientation.pitch = espp::deg_to_rad(pitch);
68+
orientation.yaw = espp::deg_to_rad(yaw);
69+
return orientation;
70+
};
71+
72+
// IMU config
73+
Imu::Config config{
74+
.device_address = Imu::DEFAULT_I2C_ADDRESS,
75+
.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2,
76+
std::placeholders::_3),
77+
.read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2,
78+
std::placeholders::_3),
79+
.imu_config =
80+
{
81+
.accel_range = Imu::AccelRange::RANGE_2G,
82+
.accel_odr = Imu::AccelODR::ODR_416_HZ,
83+
.gyro_range = Imu::GyroRange::DPS_2000,
84+
.gyro_odr = Imu::GyroODR::ODR_416_HZ,
85+
},
86+
.orientation_filter = kalman_filter_fn,
87+
.auto_init = true,
88+
.log_level = espp::Logger::Verbosity::INFO,
89+
};
90+
91+
logger.info("Creating LSM6DSO IMU");
92+
Imu imu(config);
93+
94+
std::error_code ec;
95+
96+
// set the accel / gyro on-chip filters
97+
static constexpr uint8_t accel_filter_bandwidth = 0b001; // ODR / 10
98+
static constexpr uint8_t gyro_lpf_bandwidth = 0b001; // ODR / 3
99+
static constexpr bool gyro_hpf_enabled = false; // disable high-pass filter on gyro
100+
static constexpr auto gyro_hpf_bandwidth = Imu::GyroHPF::HPF_0_26_HZ; // 0.26Hz
101+
if (!imu.set_accelerometer_filter(accel_filter_bandwidth, Imu::AccelFilter::LOWPASS, ec)) {
102+
logger.error("Failed to set accelerometer filter: {}", ec.message());
103+
}
104+
// set the gyroscope filter to have lowpass
105+
if (!imu.set_gyroscope_filter(gyro_lpf_bandwidth, gyro_hpf_enabled, gyro_hpf_bandwidth, ec)) {
106+
logger.error("Failed to set gyroscope filter: {}", ec.message());
107+
}
108+
109+
// make a task to read out the IMU data and print it to console
110+
espp::Task imu_task({.callback = [&](std::mutex &m, std::condition_variable &cv) -> bool {
111+
static auto start = std::chrono::steady_clock::now();
112+
113+
auto now = esp_timer_get_time(); // time in microseconds
114+
static auto t0 = now;
115+
auto t1 = now;
116+
float dt = (t1 - t0) / 1'000'000.0f; // convert us to s
117+
t0 = t1;
118+
119+
std::error_code ec;
120+
// update the imu data
121+
if (!imu.update(dt, ec)) {
122+
return false;
123+
}
124+
125+
// get accel
126+
auto accel = imu.get_accelerometer();
127+
auto gyro = imu.get_gyroscope();
128+
auto temp = imu.get_temperature();
129+
auto orientation = imu.get_orientation();
130+
auto gravity_vector = imu.get_gravity_vector();
131+
132+
[[maybe_unused]] auto t2 = esp_timer_get_time(); // time in microseconds
133+
134+
auto madgwick_orientation = madgwick_filter_fn(dt, accel, gyro);
135+
float roll = madgwick_orientation.roll;
136+
float pitch = madgwick_orientation.pitch;
137+
float yaw = madgwick_orientation.yaw;
138+
float vx = sin(pitch);
139+
float vy = -cos(pitch) * sin(roll);
140+
float vz = -cos(pitch) * cos(roll);
141+
142+
// print time and raw IMU data
143+
std::string text = "";
144+
text += fmt::format("{:.3f},", now / 1'000'000.0f);
145+
text += fmt::format("{:02.3f},{:02.3f},{:02.3f},", (float)accel.x,
146+
(float)accel.y, (float)accel.z);
147+
text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", (float)gyro.x,
148+
(float)gyro.y, (float)gyro.z);
149+
text += fmt::format("{:02.1f},", temp);
150+
// print kalman filter outputs
151+
text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", (float)orientation.x,
152+
(float)orientation.y, (float)orientation.z);
153+
text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", (float)gravity_vector.x,
154+
(float)gravity_vector.y, (float)gravity_vector.z);
155+
// print madgwick filter outputs
156+
text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", roll, pitch, yaw);
157+
text += fmt::format("{:03.3f},{:03.3f},{:03.3f}", vx, vy, vz);
158+
159+
fmt::print("{}\n", text);
160+
161+
// fmt::print("IMU update took {:.3f} ms\n", (t2 - t0) / 1000.0f);
162+
163+
// sleep first in case we don't get IMU data and need to exit early
164+
{
165+
std::unique_lock<std::mutex> lock(m);
166+
cv.wait_until(lock, start + 10ms);
167+
}
168+
169+
return false;
170+
},
171+
.task_config = {
172+
.name = "IMU",
173+
.stack_size_bytes = 6 * 1024,
174+
.priority = 10,
175+
.core_id = 0,
176+
}});
177+
178+
// print the header for the IMU data (for plotting)
179+
fmt::print("% Time (s), "
180+
// raw IMU data (accel, gyro, temp)
181+
"Accel X (m/s^2), Accel Y (m/s^2), Accel Z (m/s^2), "
182+
"Gyro X (rad/s), Gyro Y (rad/s), Gyro Z (rad/s), "
183+
"Temp (C), "
184+
// kalman filter outputs
185+
"Kalman Roll (rad), Kalman Pitch (rad), Kalman Yaw (rad), "
186+
"Kalman Gravity X, Kalman Gravity Y, Kalman Gravity Z, "
187+
// madgwick filter outputs
188+
"Madgwick Roll (rad), Madgwick Pitch (rad), Madgwick Yaw (rad), "
189+
"Madgwick Gravity X, Madgwick Gravity Y, Madgwick Gravity Z\n");
190+
191+
logger.info("Starting IMU task");
192+
imu_task.start();
193+
194+
// loop forever
195+
while (true) {
196+
std::this_thread::sleep_for(1s);
197+
}
198+
//! [lsm6dso example]
199+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
CONFIG_FREERTOS_HZ=1000
2+
3+
# set compiler optimization level to -O2 (compile for performance)
4+
CONFIG_COMPILER_OPTIMIZATION_PERF=y
5+
6+
# ESP32-specific
7+
#
8+
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
9+
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240
10+
11+
# Common ESP-related
12+
#
13+
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
14+
CONFIG_ESP_MAIN_TASK_STACK_SIZE=16384
15+
16+
# Set esp-timer task stack size to 6KB
17+
CONFIG_ESP_TIMER_TASK_STACK_SIZE=6144
18+
19+
# set the functions into IRAM
20+
CONFIG_SPI_MASTER_IN_IRAM=y

0 commit comments

Comments
 (0)