Running the LSM6DLS (IMU) zephyr example with nrf52840 based Xiao BLE Sense

Hi all,

I've been trying to get the samples/sensor/lsm6dsl example working with the nrf52840-based Xiao BLE Sense and have had no luck.

I guess that it's due to the power line for the sensor being connected to the P1.08

I dug into the xiao_ble_sense.dts and I can indeed see:

	lsm6ds3tr-c-en {
		compatible = "regulator-fixed-sync", "regulator-fixed";
		enable-gpios = <&gpio1 8 (NRF_GPIO_DRIVE_S0H1 | GPIO_ACTIVE_HIGH)>;
		regulator-name = "LSM6DS3TR_C_EN";
		regulator-boot-on;
		startup-delay-us = <3000>;
	};

However no matter how I try and drive it from the code the sensor refuses to initialize with the console output:

[00:00:00.794,647] <dbg> LSM6DSL: lsm6dsl_init_chip: failed to reboot device
[00:00:00.794,677] <err> LSM6DSL: Failed to initialize chip
*** Booting nRF Connect SDK d96769faceca ***
Begin init of lsm6ds3tr and regulator 
sensor: device not ready.

My code to run is based of the sample code for the sensor with the lsm6ds3tr_c_en modifications:

/*
 * Copyright (c) 2018 STMicroelectronics
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <stdio.h>
#include <zephyr/sys/util.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/regulator.h>

static const struct device *lsm6ds3tr_c_en;

static inline float out_ev(struct sensor_value *val)
{
	return (val->val1 + (float)val->val2 / 1000000);
}

static int print_samples;
static int lsm6dsl_trig_cnt;

static struct sensor_value accel_x_out, accel_y_out, accel_z_out;
static struct sensor_value gyro_x_out, gyro_y_out, gyro_z_out;
#if defined(CONFIG_LSM6DSL_EXT0_LIS2MDL)
static struct sensor_value magn_x_out, magn_y_out, magn_z_out;
#endif
#if defined(CONFIG_LSM6DSL_EXT0_LPS22HB)
static struct sensor_value press_out, temp_out;
#endif

#ifdef CONFIG_LSM6DSL_TRIGGER
static void lsm6dsl_trigger_handler(const struct device *dev,
				    const struct sensor_trigger *trig)
{
	static struct sensor_value accel_x, accel_y, accel_z;
	static struct sensor_value gyro_x, gyro_y, gyro_z;
#if defined(CONFIG_LSM6DSL_EXT0_LIS2MDL)
	static struct sensor_value magn_x, magn_y, magn_z;
#endif
#if defined(CONFIG_LSM6DSL_EXT0_LPS22HB)
	static struct sensor_value press, temp;
#endif
	lsm6dsl_trig_cnt++;

	sensor_sample_fetch_chan(dev, SENSOR_CHAN_ACCEL_XYZ);
	sensor_channel_get(dev, SENSOR_CHAN_ACCEL_X, &accel_x);
	sensor_channel_get(dev, SENSOR_CHAN_ACCEL_Y, &accel_y);
	sensor_channel_get(dev, SENSOR_CHAN_ACCEL_Z, &accel_z);

	/* lsm6dsl gyro */
	sensor_sample_fetch_chan(dev, SENSOR_CHAN_GYRO_XYZ);
	sensor_channel_get(dev, SENSOR_CHAN_GYRO_X, &gyro_x);
	sensor_channel_get(dev, SENSOR_CHAN_GYRO_Y, &gyro_y);
	sensor_channel_get(dev, SENSOR_CHAN_GYRO_Z, &gyro_z);

#if defined(CONFIG_LSM6DSL_EXT0_LIS2MDL)
	/* lsm6dsl external magn */
	sensor_sample_fetch_chan(dev, SENSOR_CHAN_MAGN_XYZ);
	sensor_channel_get(dev, SENSOR_CHAN_MAGN_X, &magn_x);
	sensor_channel_get(dev, SENSOR_CHAN_MAGN_Y, &magn_y);
	sensor_channel_get(dev, SENSOR_CHAN_MAGN_Z, &magn_z);
#endif

#if defined(CONFIG_LSM6DSL_EXT0_LPS22HB)
	/* lsm6dsl external press/temp */
	sensor_sample_fetch_chan(dev, SENSOR_CHAN_PRESS);
	sensor_channel_get(dev, SENSOR_CHAN_PRESS, &press);

	sensor_sample_fetch_chan(dev, SENSOR_CHAN_AMBIENT_TEMP);
	sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
#endif

	if (print_samples) {
		print_samples = 0;

		accel_x_out = accel_x;
		accel_y_out = accel_y;
		accel_z_out = accel_z;

		gyro_x_out = gyro_x;
		gyro_y_out = gyro_y;
		gyro_z_out = gyro_z;

#if defined(CONFIG_LSM6DSL_EXT0_LIS2MDL)
		magn_x_out = magn_x;
		magn_y_out = magn_y;
		magn_z_out = magn_z;
#endif

#if defined(CONFIG_LSM6DSL_EXT0_LPS22HB)
		press_out = press;
		temp_out = temp;
#endif
	}

}
#endif

int main(void)
{
	int cnt = 0;
	char out_str[64];
	struct sensor_value odr_attr;
    const struct device *const lsm6dsl_dev = DEVICE_DT_GET_ONE(st_lsm6dsl);

	printk("Begin init of lsm6ds3tr and regulator \n");
    /* Initialize the regulator device */
	lsm6ds3tr_c_en = DEVICE_DT_GET(DT_PATH(lsm6ds3tr_c_en));
    if (!device_is_ready(lsm6ds3tr_c_en)) {
        printk("Regulator device not ready\n");
        return 0;
    }

    /* Enable the regulator */
    if (regulator_enable(lsm6ds3tr_c_en) < 0) {
        printk("Failed to enable regulator\n");
        return 0;
    }

	 k_sleep(K_MSEC(10000));

	
	if (!device_is_ready(lsm6dsl_dev)) {
		printk("sensor: device not ready.\n");
		return 0;
	}

	/* set accel/gyro sampling frequency to 104 Hz */
	odr_attr.val1 = 104;
	odr_attr.val2 = 0;

	if (sensor_attr_set(lsm6dsl_dev, SENSOR_CHAN_ACCEL_XYZ,
			    SENSOR_ATTR_SAMPLING_FREQUENCY, &odr_attr) < 0) {
		printk("Cannot set sampling frequency for accelerometer.\n");
		return 0;
	}

	if (sensor_attr_set(lsm6dsl_dev, SENSOR_CHAN_GYRO_XYZ,
			    SENSOR_ATTR_SAMPLING_FREQUENCY, &odr_attr) < 0) {
		printk("Cannot set sampling frequency for gyro.\n");
		return 0;
	}

#ifdef CONFIG_LSM6DSL_TRIGGER
	struct sensor_trigger trig;

	trig.type = SENSOR_TRIG_DATA_READY;
	trig.chan = SENSOR_CHAN_ACCEL_XYZ;

	if (sensor_trigger_set(lsm6dsl_dev, &trig, lsm6dsl_trigger_handler) != 0) {
		printk("Could not set sensor type and channel\n");
		return 0;
	}
#endif

	if (sensor_sample_fetch(lsm6dsl_dev) < 0) {
		printk("Sensor sample update error\n");
		return 0;
	}

	while (1) {
		/* Erase previous */
		printk("\0033\014");
		printf("LSM6DSL sensor samples:\n\n");

		/* lsm6dsl accel */
		sprintf(out_str, "accel x:%f ms/2 y:%f ms/2 z:%f ms/2",
							  out_ev(&accel_x_out),
							  out_ev(&accel_y_out),
							  out_ev(&accel_z_out));
		printk("%s\n", out_str);

		/* lsm6dsl gyro */
		sprintf(out_str, "gyro x:%f dps y:%f dps z:%f dps",
							   out_ev(&gyro_x_out),
							   out_ev(&gyro_y_out),
							   out_ev(&gyro_z_out));
		printk("%s\n", out_str);

#if defined(CONFIG_LSM6DSL_EXT0_LIS2MDL)
		/* lsm6dsl external magn */
		sprintf(out_str, "magn x:%f gauss y:%f gauss z:%f gauss",
							   out_ev(&magn_x_out),
							   out_ev(&magn_y_out),
							   out_ev(&magn_z_out));
		printk("%s\n", out_str);
#endif

#if defined(CONFIG_LSM6DSL_EXT0_LPS22HB)
		/* lsm6dsl external press/temp */
		sprintf(out_str, "press: %f kPa - temp: %f deg",
			out_ev(&press_out), out_ev(&temp_out));
		printk("%s\n", out_str);
#endif

		printk("loop:%d trig_cnt:%d\n\n", ++cnt, lsm6dsl_trig_cnt);

		print_samples = 1;
		k_sleep(K_MSEC(2000));
	}
}


And my prj.conf:
CONFIG_STDOUT_CONSOLE=y
CONFIG_I2C=y
CONFIG_SPI=n
CONFIG_SENSOR=y
CONFIG_LSM6DSL_TRIGGER_GLOBAL_THREAD=y
CONFIG_CBPRINTF_FP_SUPPORT=y

CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_SENSOR_LOG_LEVEL_DBG=y

CONFIG_UART_CONSOLE=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_LINE_CTRL=y

I am trying to drive this the proper way but am I missing something obvious?

Should I just ignore the config and drive P1.08 manually?

Thanks for taking a look!
I'm using nRF Connect SDK v2.6.0 (latest available at the time of posting)

  • Thank you for putting time in it. Bugs like this in the framework are really frustrating and time consuming. Hopefully Nordic will fix this soon. And indeed switching back to a previous framework is a headache too, for instance, when I set the SDK to 2.5.3 and make a new build configuration, the debugger does not do anything.

    (debug with SDK 2.5.3)

    JLinkGDBServerCLExe: SEGGER J-Link GDB Server V7.96h Command Line Version
    JLinkGDBServerCLExe: 
    JLinkGDBServerCLExe: JLinkARM.dll V7.96h (DLL compiled May 15 2024 15:39:51)
    JLinkGDBServerCLExe: 
    JLinkGDBServerCLExe: -----GDB Server start settings-----
    JLinkGDBServerCLExe: GDBInit file:                  none
    JLinkGDBServerCLExe: GDB Server Listening port:     43081
    JLinkGDBServerCLExe: SWO raw output listening port: 2332
    JLinkGDBServerCLExe: Terminal I/O port:             2333
    JLinkGDBServerCLExe: Accept remote connection:      yes
    JLinkGDBServerCLExe: Generate logfile:              off
    JLinkGDBServerCLExe: Verify download:               off
    JLinkGDBServerCLExe: Init regs on start:            off
    JLinkGDBServerCLExe: Silent mode:                   on
    JLinkGDBServerCLExe: Single run mode:               on
    JLinkGDBServerCLExe: Target connection timeout:     0 ms
    JLinkGDBServerCLExe: ------J-Link related settings------
    JLinkGDBServerCLExe: J-Link Host interface:         USB
    JLinkGDBServerCLExe: J-Link script:                 none
    JLinkGDBServerCLExe: J-Link settings file:          none
    JLinkGDBServerCLExe: ------Target related settings------
    JLinkGDBServerCLExe: Target device:                 nRF52840_xxAA
    JLinkGDBServerCLExe: Target device parameters:      none
    JLinkGDBServerCLExe: Target interface:              SWD
    JLinkGDBServerCLExe: Target interface speed:        12000kHz
    JLinkGDBServerCLExe: Target endian:                 little
    JLinkGDBServerCLExe: 
    =thread-group-added,id="i1"
    =cmd-param-changed,param="pagination",value="off"
    0x00003958 in ?? ()

    (debug with SDK 2.6.1)

    JLinkGDBServerCLExe: SEGGER J-Link GDB Server V7.96h Command Line Version
    JLinkGDBServerCLExe: 
    JLinkGDBServerCLExe: JLinkARM.dll V7.96h (DLL compiled May 15 2024 15:39:51)
    JLinkGDBServerCLExe: 
    JLinkGDBServerCLExe: -----GDB Server start settings-----
    JLinkGDBServerCLExe: GDBInit file:                  none
    JLinkGDBServerCLExe: GDB Server Listening port:     33091
    JLinkGDBServerCLExe: SWO raw output listening port: 2332
    JLinkGDBServerCLExe: Terminal I/O port:             2333
    JLinkGDBServerCLExe: Accept remote connection:      yes
    JLinkGDBServerCLExe: Generate logfile:              off
    JLinkGDBServerCLExe: Verify download:               off
    JLinkGDBServerCLExe: Init regs on start:            off
    JLinkGDBServerCLExe: Silent mode:                   on
    JLinkGDBServerCLExe: Single run mode:               on
    JLinkGDBServerCLExe: Target connection timeout:     0 ms
    JLinkGDBServerCLExe: ------J-Link related settings------
    JLinkGDBServerCLExe: J-Link Host interface:         USB
    JLinkGDBServerCLExe: J-Link script:                 none
    JLinkGDBServerCLExe: J-Link settings file:          none
    JLinkGDBServerCLExe: ------Target related settings------
    JLinkGDBServerCLExe: Target device:                 nRF52840_xxAA
    JLinkGDBServerCLExe: Target device parameters:      none
    JLinkGDBServerCLExe: Target interface:              SWD
    JLinkGDBServerCLExe: Target interface speed:        12000kHz
    JLinkGDBServerCLExe: Target endian:                 little
    JLinkGDBServerCLExe: 
    =thread-group-added,id="i1"
    =cmd-param-changed,param="pagination",value="off"
    z_arm_reset () at /home/bergm006/ncs/v2.6.1/zephyr/arch/arm/core/cortex_m/reset.S:73
    73	    movs.n r0, #0
    [New Remote target]
    [New Thread 536872392]
    [New Thread 536871944]
    [New Thread 536872776]
    [New Thread 536872152]
    [New Thread 536872568]
    [Switching to Thread 536872568]
    
    Thread 7 hit Breakpoint 1, main () at /home/bergm006/nrf-projects/lsm6dsl/src/main.c:99
    99	{

  • Might verify the Toolchain version required for v2.6.1??

    I was having some trouble debugging v2.6.1 with Segger Ozone. I noticed when I switched SDK versions from 2.5.0 to 2.6.1 there were a number of "outdated" messages for various tools reported in the VSCode output view.

    So I updated the nrf toolchain version to 2.6.0. The issue with Ozone seemed to clear up, though it could be for unrelated reasons.

  • So with the debugger behaving properly I may have a work around. 

    It seems if I put a small k_sleep() at the entrance to   lsm6dsl_init() it would start behaving. 

     $InstallDir/v2.6.1/zephyr/drivers/sensor/lsm6dsl/lsm6dsl.c

    <

    static int lsm6dsl_init(const struct device *dev)
    {
    	int ret;
    	k_sleep(K_MSEC(1));
    	const struct lsm6dsl_config * const config = dev->config;
    
    	ret = config->bus_init(dev);
    	
    	...

    You might give it a shot. I don't know why at this point.

    SDK: 2.6.1
    ToolChain: 2.6.0
    Then again, not always work. Some sort of timing issue I guess.
  • In trying to debug this  (V2.6.1) the only thing I've found is that when setting breakpoints at various points through the sensor init processing, it will then work from then on. Nothing consistent enough to run down. Could be timing with the i2c xfers, new thread interactions seen with 2.6.1, debugger changing timing, etc...

    It's beyond my ability to look at it further.

  • Instead of using the Zephyr sensor abstraction I just use the i2c port directly, as I don't like them (or I don't understand it, as no documentation about it anywhere to be found).

    Since quite a lot of people are watching this, I'll taught sharing my solution. The driver files can directly be downloaded from STMicroelectronics Git. Paste lsm6dsl.c and lsm6dsl.h in your project.

    You can just use an example of STMicro and replace/add these snippets:

    tatic int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp,
                                  uint16_t len)
    {  
    
    	int error = i2c_burst_write(i2c, LSM6DSL_ADDR, reg, bufp, len);
      if (error) {
        return -EIO;
      } else {
        return 0;
      };
    }
    
    /*
     * @brief  Read generic device register (platform dependent)
     *
     * @param  handle    customizable argument. In this examples is used in
     *                   order to select the correct sensor bus handler.
     * @param  reg       register to read
     * @param  bufp      pointer to buffer that store the data read
     * @param  len       number of consecutive register to read
     *
     */
    static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp,
                                 uint16_t len)
    {  
    	 if(i2c_burst_read(i2c, LSM6DSL_ADDR, reg, bufp, len)){    
        return -EIO;
       }   
       return 0;
    }
    
    /*
     * @brief  Send buffer to console (platform dependent)
     *
     * @param  tx_buffer     buffer to transmit
     * @param  len           number of byte to send
     *
     */
    static void tx_com(uint8_t *tx_buffer, uint16_t len)
    {
        LOG_INF("%s", tx_buffer);
    }
    
    /*
     * @brief  platform specific delay (platform dependent)
     *
     * @param  ms        delay in ms
     *
     */
    static void platform_delay(uint32_t ms)
    {
        k_sleep(K_MSEC(ms));
    }
    
    /*
     * @brief  platform specific initialization (platform dependent)
     */
    static void platform_init(void)
    {
    	i2c = DEVICE_DT_GET(DT_NODELABEL(i2c0));
    	__ASSERT(device_is_ready(i2c), "LSM6DSL device not ready");
    }

    xiao_ble_sense.overlay

    &zephyr_udc0 {
    	cdc_acm_uart0 {
    		compatible = "zephyr,cdc-acm-uart";
    	};
    };
    
    &i2c0 {	
        lsm6ds3tr_c: lsm6ds3tr-c@6a {
            status = "disabled";
        };
    };
    

    prj.conf

    CONFIG_STDOUT_CONSOLE=y
    
    CONFIG_I2C=y
    CONFIG_SPI=n
    
    CONFIG_USB_DEVICE_PID=0x0001
    CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y
    CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y
    CONFIG_SERIAL=y
    CONFIG_UART_INTERRUPT_DRIVEN=y
    CONFIG_UART_LINE_CTRL=y
    # CONFIG_SENSOR=y
    # CONFIG_LSM6DSL_TRIGGER_GLOBAL_THREAD=y
    CONFIG_CBPRINTF_FP_SUPPORT=y
    CONFIG_REGULATOR=y
    CONFIG_LOG=y
    
    CONFIG_USB_DEVICE_STACK=y
    CONFIG_USB_DEVICE_PRODUCT="lsm6dsl_sense"
    
    CONFIG_UART_LINE_CTRL=y
    CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n
    CONFIG_PRINTK=y
    
    
    
    CONFIG_BUILD_OUTPUT_UF2=n
    CONFIG_USE_DT_CODE_PARTITION=n
    
    #debug section
    CONFIG_CORTEX_M_DEBUG_MONITOR_HOOK=y
    CONFIG_SEGGER_DEBUGMON=y
    CONFIG_LOG_DEFAULT_LEVEL=3
    CONFIG_I2C_LOG_LEVEL_DBG=n
    CONFIG_I2C_DUMP_MESSAGES=n
    CONFIG_DEBUG_OPTIMIZATIONS=y
    CONFIG_DEBUG_THREAD_INFO=y
    CONFIG_ASSERT=y
    CONFIG_USB_DEVICE_VID=0x2FE3

    This will work on any nrf sdk, and you'll have full access to all functions of the most recent library, and have much better documentation. Please share me your thoughts.

Related