No output on UART Xiao NRF52840 sense + GPS Quectel L76K

Hi everyone!

I wrote code in arduino IDE but wanted to use all potential of Xiao NRF52840 with use of Nordic SDK and Zephyr RTOS as well as zigbee on my small side project but I’m struggling with getting readings from UART. I’m successfully reading data from builtin IMU but cannot get anything from GPS (arduino code show results). I’ve attached my main.c file as well as prj.conf and .overlay file. Maybe it’s caused by connecting using USB? And my second question is: is there any way to synchronize GPS and IMU readings?

overlay

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>;
};

&i2c0 {
    compatible = "nordic,nrf-twim";
    /* Cannot be used together with spi0. */
    status = "okay";
    pinctrl-0 = <&i2c0_default>;
    pinctrl-1 = <&i2c0_sleep>;
    pinctrl-names = "default", "sleep";
    clock-frequency = <I2C_BITRATE_FAST>;
    lsm6ds3tr_c: lsm6ds3tr-c@6a {
        compatible = "st,lsm6dsl";
        reg = <0x6a>;
        irq-gpios = <&gpio0 11 GPIO_ACTIVE_HIGH>;
        status = "okay";
    };
 };

&pinctrl {
        uart0_default: uart0_default {
                group1 {
                        psels = <NRF_PSEL(UART_TX, 1, 11)>;
                };
                group2 {
                        psels = <NRF_PSEL(UART_RX, 1, 12)>;
                        bias-pull-up;
                };
        };
        uart0_sleep: uart0_sleep {
                group1 {
                        psels = <NRF_PSEL(UART_TX, 1, 11)>,
                                <NRF_PSEL(UART_RX, 1, 12)>;
                        low-power-enable;
                };
        };
};


&uart0 {
        compatible = "nordic,nrf-uarte";
        status = "okay";
        current-speed = <9600>;
        pinctrl-0 = <&uart0_default>;
        pinctrl-1 = <&uart0_sleep>;
        pinctrl-names = "default", "sleep";
};

prj.conf

CONFIG_STDOUT_CONSOLE=y
CONFIG_I2C=y
CONFIG_SPI=y
CONFIG_SENSOR=y
CONFIG_LSM6DSL_TRIGGER_GLOBAL_THREAD=y
CONFIG_CBPRINTF_FP_SUPPORT=y

# CONFIG_USB_DEVICE_STACK=y
# CONFIG_USB_DEVICE_PRODUCT="XIAO_GPS_IMU"

CONFIG_UART_0_ASYNC=y
CONFIG_UART_0_INTERRUPT_DRIVEN=y

Main.c

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/uart.h>

#include <zephyr/drivers/gnss.h>
#include <zephyr/logging/log.h>
#include <stdio.h>
#include <zephyr/sys/util.h>


// 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;
// // const struct device *uart = DEVICE_DT_GET(DT_NODELABEL(uart0)); 

// #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;
//  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 (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;
//  }

// }
// #endif

const struct device *gps_dev = DEVICE_DT_GET(DT_NODELABEL(uart0));

void gps_uart_callback(const struct device *dev, void *user_data) {
    uint8_t byte;
    if (!uart_irq_update(dev)) return;

    while (uart_irq_rx_ready(dev)) {
        if (uart_fifo_read(dev, &byte, 1) > 0) {
            printk("0x%02x\n", byte); 
        }
    }
}

int main(void)
{
    const struct uart_config uart_cfg = {
        .baudrate = 9600,
        .parity = UART_CFG_PARITY_NONE,
        .stop_bits = UART_CFG_STOP_BITS_1,
        .data_bits = UART_CFG_DATA_BITS_8,
        .flow_ctrl = UART_CFG_FLOW_CTRL_NONE
    };

    int err = uart_configure(gps_dev, &uart_cfg);
    if (err < 0) {
        return err;
    }

    printk("Setting up UART\n");
    if (!device_is_ready(gps_dev)) {
        printk("UART device not ready\n");
        return 0;
    }

    uart_irq_callback_user_data_set(gps_dev, gps_uart_callback, NULL);
    uart_irq_rx_enable(gps_dev);


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

//  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) {
        printk("Waiting for getting UART signal...\n");
        // /* 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",
        //                    sensor_value_to_double(&accel_x_out),
        //                    sensor_value_to_double(&accel_y_out),
        //                    sensor_value_to_double(&accel_z_out));
        // printk("%s\n", out_str);

        // sprintf(out_str, "G-X: %d G-Y: %d G-Z: %d",
        //                     sensor_ms2_to_g(&accel_x_out),
        //                     sensor_ms2_to_g(&accel_y_out),
        //                     sensor_ms2_to_g(&accel_z_out));
        // printk("%s\n", out_str);

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

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

        // print_samples = 1;
        k_sleep(K_MSEC(200));

        k_sleep(K_SECONDS(2));
    }
}
1 Like

Hi there,

SO, the clasic Gotcha…
for You this line

CONFIG_STDOUT_CONSOLE=y

is kryptonite :grin: :pinching_hand:

Try this instead,

# Core
CONFIG_MAIN_STACK_SIZE=4096

# Logging over RTT, not over UART
CONFIG_LOG=y
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_RTT_CONSOLE=y
CONFIG_CONSOLE=y
CONFIG_PRINTK=y

# Important: do not use UART0 as system console
CONFIG_UART_CONSOLE=n

# UART for GPS
CONFIG_SERIAL=y
CONFIG_UART_USE_RUNTIME_CONFIGURE=y

# I2C + sensor for IMU
CONFIG_I2C=y
CONFIG_SENSOR=y

# BLE advertising only
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="XIAO_GPS_IMU"
CONFIG_BT_DEVICE_APPEARANCE=0

# LSM6DSL trigger support is optional; polling is enough for this test
# CONFIG_LSM6DSL_TRIGGER_GLOBAL_THREAD=y

Someone tried to swim into the deep without his snorkel…
:diving_mask:
This smells like the console/UART setup is fighting you, and the USB cable is probably adding confusion.

The key points are:

  • On XIAO nRF52840, D6 = P1.11 = TX and D7 = P1.12 = RX for the external UART. Seeed’s pinout sheet shows that mapping directly.
  • Zephyr can use a UART as the system console, and boards typically define a default console through zephyr,console. If uart0 is being used for console output, it is a bad place to also hang a GPS receiver.
  • The USB-C connection is not the same thing as the GPS UART pins. Unless you explicitly enable a USB CDC serial function, plugging in USB does not magically route GPS bytes from D6/D7 to your PC.
  • Zephyr gives you polling, interrupt-driven, and async UART APIs. For first bring-up, polling is the least fussy and is the best way to prove the wiring and baud settings are correct before layering in async code.

I use the RTT, never gets in the way. :v:
(note , there are several threads on the uart0) conflict
Also make sure you’ve got the right hardware version, 5v or 3.3v, double check the chip is the Sense variety not the BLE only
I’m sure you have checked all this but just throwing it all out there.

BTW,

On syncing GPS and IMU

Yes, absolutely.
For a first pass, just timestamp both with k_uptime_get() as the sample does. That gives you software-level correlation. For tighter sync, use the usual next steps: at the end. Get it working first :+1:

Do the basics Start simple:

  1. Put the GPS on uart0 at D6/D7.
  2. Disable UART console so Zephyr does not steal that port.
  3. Use RTT for logs during bring-up.
  4. Configure the GPS UART explicitly to the module’s actual format.
  5. Read GPS with polling first, not async.
  6. Read the IMU with Zephyr’s sensor API.
  7. Add BLE advertising only as a lightweight beacon for now.

Start with a Basic Zephyr test app

  • IMU polling
  • GPS UART polling on D6/D7
  • BLE advertising
  • RTT logging
  • simple timestamps so IMU and GPS data can be correlated

src/main.c Try this

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/logging/log.h>
#include <string.h>
#include <stdio.h>

LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);

/* Use aliases from the overlay */
#define GPS_UART_NODE DT_NODELABEL(uart0)
#define IMU_NODE      DT_ALIAS(imu0)

#if !DT_NODE_HAS_STATUS(GPS_UART_NODE, okay)
#error "uart0 is not enabled in the devicetree"
#endif

#if !DT_NODE_HAS_STATUS(IMU_NODE, okay)
#error "imu0 alias is missing or disabled in the devicetree"
#endif

static const struct device *gps_uart = DEVICE_DT_GET(GPS_UART_NODE);
static const struct device *imu = DEVICE_DT_GET(IMU_NODE);

static char gps_line[128];
static size_t gps_idx;

static const struct bt_data ad[] = {
	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
	BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME,
		sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};

static int gps_uart_setup(void)
{
	struct uart_config cfg = {
		.baudrate = 9600,
		.parity = UART_CFG_PARITY_NONE,
		.stop_bits = UART_CFG_STOP_BITS_1,
		.data_bits = UART_CFG_DATA_BITS_8,
		.flow_ctrl = UART_CFG_FLOW_CTRL_NONE,
	};

	if (!device_is_ready(gps_uart)) {
		LOG_ERR("GPS UART device not ready");
		return -ENODEV;
	}

	int ret = uart_configure(gps_uart, &cfg);
	if (ret) {
		LOG_ERR("uart_configure failed: %d", ret);
		return ret;
	}

	LOG_INF("GPS UART configured: 9600 8N1");
	return 0;
}

static void gps_poll_once(void)
{
	unsigned char c;
	int ret;

	while ((ret = uart_poll_in(gps_uart, &c)) == 0) {
		if (c == '\r') {
			continue;
		}

		if (c == '\n') {
			if (gps_idx > 0) {
				gps_line[gps_idx] = '\0';
				int64_t ts = k_uptime_get();

				/* Print complete NMEA line with timestamp */
				LOG_INF("[%lld ms] GPS: %s", ts, gps_line);

				gps_idx = 0;
			}
			continue;
		}

		if (gps_idx < sizeof(gps_line) - 1) {
			gps_line[gps_idx++] = (char)c;
		} else {
			/* overflow, reset line buffer */
			gps_idx = 0;
		}
	}
}

static void imu_read_once(void)
{
	struct sensor_value accel[3];
	struct sensor_value gyro[3];
	int ret;
	int64_t ts = k_uptime_get();

	if (!device_is_ready(imu)) {
		LOG_ERR("IMU not ready");
		return;
	}

	ret = sensor_sample_fetch(imu);
	if (ret) {
		LOG_ERR("sensor_sample_fetch failed: %d", ret);
		return;
	}

	ret = sensor_channel_get(imu, SENSOR_CHAN_ACCEL_XYZ, accel);
	if (ret) {
		LOG_ERR("ACCEL read failed: %d", ret);
		return;
	}

	ret = sensor_channel_get(imu, SENSOR_CHAN_GYRO_XYZ, gyro);
	if (ret) {
		LOG_ERR("GYRO read failed: %d", ret);
		return;
	}

	LOG_INF("[%lld ms] IMU A[%.3f %.3f %.3f] G[%.3f %.3f %.3f]",
		ts,
		sensor_value_to_double(&accel[0]),
		sensor_value_to_double(&accel[1]),
		sensor_value_to_double(&accel[2]),
		sensor_value_to_double(&gyro[0]),
		sensor_value_to_double(&gyro[1]),
		sensor_value_to_double(&gyro[2]));
}

static void bt_ready(int err)
{
	if (err) {
		LOG_ERR("Bluetooth init failed: %d", err);
		return;
	}

	err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), NULL, 0);
	if (err) {
		LOG_ERR("Advertising start failed: %d", err);
		return;
	}

	LOG_INF("BLE advertising started");
}

int main(void)
{
	int ret;
	int64_t last_imu = 0;

	LOG_INF("XIAO nRF52840 GPS + IMU test starting");

	if (!device_is_ready(imu)) {
		LOG_ERR("IMU device not ready");
		return 0;
	}

	ret = gps_uart_setup();
	if (ret) {
		return 0;
	}

	ret = bt_enable(bt_ready);
	if (ret) {
		LOG_ERR("bt_enable failed: %d", ret);
		return 0;
	}

	while (1) {
		/* Poll GPS continuously */
		gps_poll_once();

		/* Read IMU every 100 ms */
		if ((k_uptime_get() - last_imu) >= 100) {
			last_imu = k_uptime_get();
			imu_read_once();
		}

		k_sleep(K_MSEC(10));
	}

	return 0;
}

use this one,
prj.conf

# Core
CONFIG_MAIN_STACK_SIZE=4096

# Logging over RTT, not over UART
CONFIG_LOG=y
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_RTT_CONSOLE=y
CONFIG_CONSOLE=y
CONFIG_PRINTK=y

# Important: do not use UART0 as system console
CONFIG_UART_CONSOLE=n

# UART for GPS
CONFIG_SERIAL=y
CONFIG_UART_USE_RUNTIME_CONFIGURE=y

# I2C + sensor for IMU
CONFIG_I2C=y
CONFIG_SENSOR=y

# BLE advertising only
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="XIAO_GPS_IMU"
CONFIG_BT_DEVICE_APPEARANCE=0

# LSM6DSL trigger support is optional; polling is enough for this test
# CONFIG_LSM6DSL_TRIGGER_GLOBAL_THREAD=y

this app.overlay assumes you did the conf

/ {
	aliases {
		imu0 = &lsm6ds3tr_c;
	};
};

&i2c0 {
	status = "okay";
	clock-frequency = <I2C_BITRATE_FAST>;

	lsm6ds3tr_c: imu@6a {
		compatible = "st,lsm6dsl";
		reg = <0x6a>;
		irq-gpios = <&gpio0 11 GPIO_ACTIVE_HIGH>;
		status = "okay";
	};
};

&pinctrl {
	uart0_default: uart0_default {
		group1 {
			psels = <NRF_PSEL(UART_TX, 1, 11)>;
		};
		group2 {
			psels = <NRF_PSEL(UART_RX, 1, 12)>;
			bias-pull-up;
		};
	};

	uart0_sleep: uart0_sleep {
		group1 {
			psels = <NRF_PSEL(UART_TX, 1, 11)>,
				<NRF_PSEL(UART_RX, 1, 12)>;
			low-power-enable;
		};
	};
};

&uart0 {
	status = "okay";
	current-speed = <9600>;
	pinctrl-0 = <&uart0_default>;
	pinctrl-1 = <&uart0_sleep>;
	pinctrl-names = "default", "sleep";
};

This assumes:

  • GPS TX → XIAO D7 / P1.12 RX
  • GPS RX → XIAO D6 / P1.11 TX
  • external LSM6DS3TR-C on I2C at 0x6A
  • IMU INT on P0.11 if you want it later

get this running , then try the usual next step is:

  • use the GPS PPS output on a GPIO interrupt
  • timestamp that interrupt edge
  • timestamp IMU samples relative to that timing reference

That gets you much closer to real synchronization.

HTH
GL :slight_smile: PJ :v:

You may want to drop in the arduino sketch on the GPS test thread on Com2 I have on here somewhere as does @cgwaltney , both the nrf52840 and The Esp32C3 Xiao held it downsolid, with the Grove GPS unit. :+1: I beleive @msfujino has a thread on the syncing the IMU data wit a time stamp? have a looksie :grin:

1 Like

I’ve started working on it after Easter :smiley: and after slight change to prj.conf I’ve managed to run this code but my board doesn’t show as COM port which I assume is because of
CONFIG_STDOUT_CONSOLE=n

and I belive that without proper debuger I can’t go anywhere :confused: or maybe I should change from beacon to broadcast information about GPS and IMU