drivers/sensors: add LSM6DS3TR-C uORB driver for the XIAO ESP32-S3

No driver exists for this exact chip. lsm6dsl.c is the closest
register-compatible match but is the deprecated legacy char-device
style; lsm6dso32_uorb.c is the closest uORB-style match but is for a
different chip variant. The new driver borrows lsm6dso32_uorb.c's
structure (dual sensor_lowerhalf_s, raw I2C_TRANSFER helpers) and
lsm6dsl.h's register map -- fixing a bug in the header it was ported
from along the way: LSM6DSL_FIFO_CTRL2_SHIFT is defined as 255 instead
of 0.

Delivery mode is chosen the same way mpu6050 does: kthread polling by
default, or interrupt-driven if the board supplies attach(). Unlike
the earlier lsm6dso32-style design this went through first -- one INT
pin and one activate()/interrupt path per sub-sensor -- the shipped
version uses a single shared INT pin for both, mirroring mpu6050's own
one-handler-one-worker design (#19601) instead. The two-independent-
paths version worked for accel alone but was intermittently broken for
gyro: activate() sometimes never actually turned CTRL2_G on even
though the interrupt-enable bit was written correctly, and other times
the whole console hung -- a real race, never conclusively root-caused
on a serial console with no JTAG available. The LSM6DS3TR-C supports
OR'ing both DRDY_XL and DRDY_G onto one pin via independent enable
bits in that pin's INTn_CTRL register, so there was no need for two
paths in the first place: one ISR times the burst, one HPWORK worker
reads OUT_TEMP_L..OUTZ_H_A (14 contiguous bytes covering temp, gyro
and accel in one I2C transaction) and pushes whichever topic(s) are
currently subscribed. activate() now just flips each sub-sensor's own
bit in the shared register instead of running its own attach.

On the XIAO ESP32-S3 with Seeed's IMU Breakout Board, INT1/INT2 route
to GPIO3/GPIO4 (confirmed from the breakout board's schematic, not
guessed). Only INT1/GPIO3 is wired up, since one pin is now enough;
GPIO4/INT2 is documented as available but unused.

Also: CTRL1_XL's FS_XL bits were never actually written to match the
driver's own software default (4g) -- registration set the in-memory
value but the chip stayed at its 2g reset default until a caller
issued an explicit SNIOC_SETFULLSCALE. register() now writes it.

Validated on the bench, both modes, reproduced across multiple fresh
reboots: WHO_AM_I reads 0x6a, sensor_accel0/sensor_gyro0 stream
continuously. Interrupt mode delivers ~300 samples of each per 6s
window with shared timestamps down to the microsecond between the two
topics per event, confirming both come from the same burst read.

Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Felipe Moura <moura.fmo@gmail.com>
This commit is contained in:
Felipe Moura 2026-08-28 13:17:04 -03:00 committed by Alan C. Assis
parent bd2081aeec
commit 300c7363d7
9 changed files with 1581 additions and 0 deletions

View file

@ -66,6 +66,10 @@ ifeq ($(CONFIG_SENSORS_BMP180),y)
CSRCS += esp32s3_board_bmp180.c
endif
ifeq ($(CONFIG_SENSORS_LSM6DS3TRC),y)
CSRCS += esp32s3_board_lsm6ds3trc.c
endif
ifeq ($(CONFIG_ESP32S3_OTG),y)
CSRCS += esp32s3_board_usb.c
endif

View file

@ -0,0 +1,131 @@
/****************************************************************************
* boards/xtensa/esp32s3/common/src/esp32s3_board_lsm6ds3trc.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <errno.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/irq.h>
#include <nuttx/sensors/lsm6ds3trc.h>
#include "espressif/esp_gpio.h"
#include "esp32s3_i2c.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* INT1/INT2 routing on Seeed's "IMU Breakout Board for XIAO" (XIAOML Kit),
* confirmed from the board's schematic (IMU_Breakout_Board_for_XIAO_SCH,
* rev V1.0): LSM6DS3TR-C INT1 -> XIAO GPIO3 (D2), INT2 -> XIAO GPIO4 (D3),
* each through a 33ohm series resistor, both populated. The chip can OR
* both DRDY_XL and DRDY_G onto either pin, so only one is used -- INT1 is
* an arbitrary but fixed choice. GPIO4/INT2 stays unused by the driver.
*/
#define LSM6DS3TRC_IRQ_PIN 3 /* INT1 */
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: board_lsm6ds3trc_attach
*
* Description:
* Attach (or detach, if handler is NULL) the shared data-ready
* interrupt handler to INT1 (XIAO GPIO3).
*
****************************************************************************/
static int board_lsm6ds3trc_attach(xcpt_t handler, FAR void *arg)
{
int ret;
esp_gpioirqdisable(LSM6DS3TRC_IRQ_PIN);
ret = esp_gpio_irq(LSM6DS3TRC_IRQ_PIN, handler, arg);
if (ret < 0)
{
return ret;
}
esp_gpioirqenable(LSM6DS3TRC_IRQ_PIN);
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: board_lsm6ds3trc_initialize
*
* Description:
* Initialize and register the LSM6DS3TR-C 6-axis IMU driver, exposing
* it through uORB as /dev/uorb/sensor_accelN and /dev/uorb/sensor_gyroN.
* Data is delivered from the shared INT1 data-ready interrupt rather
* than kthread polling.
*
* Input Parameters:
* devno - The device number, used to build the uORB device paths
* busno - The I2C bus number
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
int board_lsm6ds3trc_initialize(int devno, int busno)
{
struct i2c_master_s *i2c;
static const struct lsm6ds3trc_config_s config =
{
.int_pin = LSM6DS3TRC_INT1,
.attach = board_lsm6ds3trc_attach,
};
/* The IMU drives INT1 push-pull, active high by default (CTRL3_C
* H_LACTIVE reset value) -- rising edge signals data ready. No pull
* needed once the sensor drives the line, but PULLDOWN gives a defined
* idle state before CTRL registers are written during registration.
*/
esp_configgpio(LSM6DS3TRC_IRQ_PIN, INPUT_FUNCTION_2 | PULLDOWN | RISING);
i2c = esp32s3_i2cbus_initialize(busno);
if (i2c == NULL)
{
return -ENODEV;
}
/* SA0 tied low on this board -> 0x6a. Use 0x6b if SA0 is pulled high. */
return lsm6ds3trc_register(i2c, 0x6a, devno,
(FAR struct lsm6ds3trc_config_s *)&config);
}

View file

@ -70,5 +70,33 @@ int esp32s3_bringup(void);
int esp32s3_gpio_init(void);
#endif
/****************************************************************************
* Name: board_i2c_init
****************************************************************************/
#ifdef CONFIG_I2C_DRIVER
int board_i2c_init(void);
#endif
/****************************************************************************
* Name: board_lsm6ds3trc_initialize
*
* Description:
* Initialize and register the LSM6DS3TR-C 6-axis IMU driver, exposing
* it through uORB as /dev/uorb/sensor_accelN and /dev/uorb/sensor_gyroN.
*
* Input Parameters:
* devno - The device number, used to build the uORB device paths
* busno - The I2C bus number
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
#ifdef CONFIG_SENSORS_LSM6DS3TRC
int board_lsm6ds3trc_initialize(int devno, int busno);
#endif
#endif /* __ASSEMBLY__ */
#endif /* __BOARDS_XTENSA_ESP32S3_ESP32S3_XIAO_SRC_ESP32S3_XIAO_H */

View file

@ -47,6 +47,10 @@
# include "espressif/esp_hr_timer.h"
#endif
#ifdef CONFIG_ESP32S3_I2C
# include "esp32s3_i2c.h"
#endif
#include "esp32s3-xiao.h"
#ifdef CONFIG_USERLED
@ -119,6 +123,28 @@ int esp32s3_bringup(void)
}
#endif
#ifdef CONFIG_I2C_DRIVER
/* Configure I2C peripheral interfaces */
ret = board_i2c_init();
if (ret < 0)
{
syslog(LOG_ERR, "Failed to initialize I2C driver: %d\n", ret);
}
#endif
#ifdef CONFIG_SENSORS_LSM6DS3TRC
/* Try to register the LSM6DS3TR-C device on I2C0 (D4/D5 = SDA/SCL) */
ret = board_lsm6ds3trc_initialize(0, ESP32S3_I2C0);
if (ret < 0)
{
syslog(LOG_ERR,
"Failed to initialize LSM6DS3TR-C driver for I2C0: %d\n",
ret);
}
#endif
/* If we got here then perhaps not all initialization was successful, but
* at least enough succeeded to bring-up NSH with perhaps reduced
* capabilities.

View file

@ -151,6 +151,10 @@ if(CONFIG_SENSORS)
list(APPEND SRCS lsm6dso32_uorb.c)
endif()
if(CONFIG_SENSORS_LSM6DS3TRC)
list(APPEND SRCS lsm6ds3trc_uorb.c)
endif()
if(CONFIG_SENSORS_LSM9DS1)
list(APPEND SRCS lsm9ds1_base.c)
if(CONFIG_SENSORS_LSM9DS1_UORB)

View file

@ -1074,6 +1074,49 @@ config SENSORS_LSM6DSO32_GYRO_ORB_BUFSIZE
endif # SENSORS_LSM6DSO32
config SENSORS_LSM6DS3TRC
bool "STM LSM6DS3TR-C support"
default n
select I2C
select SCHED_HPWORK
---help---
Enable uORB driver support for the STM LSM6DS3TR-C 6-axis IMU
over I2C. Needs the high-priority work queue even in kthread
polling mode: the driver's interrupt handlers reference it
unconditionally (they're only dead code until a future phase wires
up INT1/INT2).
if SENSORS_LSM6DS3TRC
config SENSORS_LSM6DS3TRC_I2C_FREQUENCY
int "LSM6DS3TR-C I2C frequency"
default 400000
range 1 400000
---help---
The I2C frequency used to communicate with the LSM6DS3TR-C.
config SENSORS_LSM6DS3TRC_THREAD_STACKSIZE
int "LSM6DS3TR-C worker thread stack size"
default 1024
---help---
The stack size for the worker threads that perform measurements.
config SENSORS_LSM6DS3TRC_ACCEL_ORB_BUFSIZE
int "LSM6DS3TR-C accelerometer uORB buffer size"
default 10
range 0 500
---help---
The circular buffer size for the accelerometer uORB measurements
config SENSORS_LSM6DS3TRC_GYRO_ORB_BUFSIZE
int "LSM6DS3TR-C gyroscope uORB buffer size"
default 10
range 0 500
---help---
The circular buffer size for the gyroscope uORB measurements
endif # SENSORS_LSM6DS3TRC
config SENSORS_LSM9DS1
bool "STMicro LSM9DS1 support"
default n

View file

@ -163,6 +163,10 @@ ifeq ($(CONFIG_SENSORS_LSM6DSO32),y)
CSRCS += lsm6dso32_uorb.c
endif
ifeq ($(CONFIG_SENSORS_LSM6DS3TRC),y)
CSRCS += lsm6ds3trc_uorb.c
endif
ifeq ($(CONFIG_SENSORS_LSM9DS1),y)
CSRCS += lsm9ds1_base.c
ifeq ($(CONFIG_SENSORS_LSM9DS1_UORB),y)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,99 @@
/****************************************************************************
* include/nuttx/sensors/lsm6ds3trc.h
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __INCLUDE_NUTTX_SENSORS_LSM6DS3TRC_H
#define __INCLUDE_NUTTX_SENSORS_LSM6DS3TRC_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/irq.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/sensors/ioctl.h>
/****************************************************************************
* Public Types
****************************************************************************/
/* LSM6DS3TR-C interrupt pins */
enum lsm6ds3trc_int_e
{
LSM6DS3TRC_INT1 = 0, /* Interrupt pin 1 */
LSM6DS3TRC_INT2 = 1, /* Interrupt pin 2 */
};
typedef int (*lsm6ds3trc_attach)(xcpt_t handler, FAR void *arg);
/* Configuration for the LSM6DS3TR-C driver.
*
* The chip can OR both the accelerometer and gyroscope data-ready flags
* onto a single INT pin (each has its own enable bit in that pin's
* INT<n>_CTRL register), so one interrupt line is enough for both
* sub-sensors -- there's no need for the two independent INT1/INT2 paths
* a chip with a single combined DRDY bit would require two pins for.
* Mirrors mpu6050_config_s: one shared handler, one shared HPWORK worker
* that bursts accel+gyro+temp from one contiguous I2C read and pushes
* whichever topic(s) are currently subscribed.
*
* Leave `attach` NULL to use kthread polling instead (each sub-sensor
* polls independently at its own rate, same as always).
*/
struct lsm6ds3trc_config_s
{
enum lsm6ds3trc_int_e int_pin; /* INT pin both DRDY_XL and DRDY_G use */
lsm6ds3trc_attach attach; /* Attach the interrupt (NULL for kthread) */
};
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
/****************************************************************************
* Name: lsm6ds3trc_register
*
* Description:
* Register the LSM6DS3TR-C as a UORB sensor with accel and gyro topics.
* If used with interrupts and device registration fails, it is the
* caller's responsibility to detach the interrupt handler.
*
* Input Parameters:
* i2c - An instance of the I2C interface to use to communicate with
* the LSM6DS3TR-C
* addr - The I2C address of the LSM6DS3TR-C (0x6a with SA0 low, 0x6b
* with SA0 high).
* devno - The device number for the UORB topics registered (i.e.
* sensor_accel<n>)
* config - Configuration setup for interrupt-driven or polling driven
* data fetching. Leave `*_attach` function NULL to use kthread
* polling instead of interrupt handling. Must not be NULL.
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
int lsm6ds3trc_register(FAR struct i2c_master_s *i2c, uint8_t addr,
uint8_t devno,
FAR struct lsm6ds3trc_config_s *config);
#endif /* __INCLUDE_NUTTX_SENSORS_LSM6DS3TRC_H */