IMU rate is too low

Hello,

I’ve bought some Xiao NRF52840 sense boards and trying to read IMU as fast as possible.
In the following piece of code, I can barely reach 700 Hz while I need 1kHz to perform a FFT on my computer.
How to speed it up ?
If I enable BLE, the rate drops to 70Hz :confused:

#include <LSM6DS3.h>
#include <Wire.h>

#define SAMPLING_RATE                 1000   //Hz

// Declare IMU devices
LSM6DS3 myIMU(I2C_MODE, 0x6A);
float ax, ay, az;
float gx, gy, gz;
uint8_t readData = 0;
int numSamples = 0;
int samplesRead = numSamples;

void setup() {
  Serial.begin(9600);
  unsigned long startTime = millis();
  while (!Serial && millis() - startTime < 1000);
  
  // Toggle LEDs
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  pinMode(LED_BLUE, OUTPUT);
  digitalWrite(LED_RED, HIGH);
  digitalWrite(LED_BLUE, HIGH);
  digitalWrite(LED_GREEN, LOW);

  // Initialize IMU
  myIMU.settings.gyroEnabled = 0;
  if (myIMU.begin() != 0) {
    Serial.println("[FAILURE] Failed to start IMU.");
    while (1);
  }
  Wire1.setClock(400000UL);
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_CTRL2_G, 0x8C);     //1.66kHz 2000dps
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_CTRL1_XL, 0x8A);    //1.66kHz 4G
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_CTRL7_G, 0x00);
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_CTRL8_XL, 0x09);

  Serial.println("[INFO] Ready.");
  Serial.println("aX,aY,aZ,gX,gY,gZ");
}

void loop() {
  // Collect samples
  numSamples = SAMPLING_RATE * 5;

  unsigned long startTime = micros();
  unsigned long elapsedTime;

  // Wait until IMU is ready
  unsigned int timestamp = micros();
  do {
    myIMU.readRegister(&readData, LSM6DS3_ACC_GYRO_STATUS_REG); // 0,0,0,0,0,TDA,GDA,XLDA 
  } while ((readData & 0x07) != 0x07);
  Serial.println("Ready to read samples");

  // Read samples
  uint8_t dataBuff[12];
  unsigned long previousMicros = 0;
  unsigned long delayTime = 1000000 / SAMPLING_RATE;
  String data;
  while (samplesRead < numSamples) {
    unsigned long currentMicros = micros();
    if (currentMicros - previousMicros < delayTime) continue;
    previousMicros = currentMicros;

    // Read raw data
    myIMU.readRegisterRegion(dataBuff, LSM6DS3_ACC_GYRO_OUTX_L_G, 12);
    //gx = myIMU.calcGyro((int16_t)dataBuff[0] | int16_t(dataBuff[1] << 8));
    //gy = myIMU.calcGyro((int16_t)dataBuff[2] | int16_t(dataBuff[3] << 8));
    //gz = myIMU.calcGyro((int16_t)dataBuff[4] | int16_t(dataBuff[5] << 8));
    ax = myIMU.calcAccel((int16_t)dataBuff[6] | int16_t(dataBuff[7] << 8));
    ay = myIMU.calcAccel((int16_t)dataBuff[8] | int16_t(dataBuff[9] << 8));
    az = myIMU.calcAccel((int16_t)dataBuff[10] | int16_t(dataBuff[11] << 8));

    // Format data
    data = String(samplesRead)+","+ String(ax, 3)+","+String(ay, 3)+","+String(az, 3);
    //data = data + ","+ String(_calcGyro(gX), 3)+","+String(_calcGyro(gY), 3)+","+String(_calcGyro(gZ), 3);
    //Serial.println(data);
    samplesRead++;
  }

  elapsedTime = micros() - startTime;
  float frequency = (float)samplesRead / (elapsedTime / 1000000.0);
  Serial.print("Frequency : ");
  Serial.print(frequency);
  Serial.println(" Hz");

  // Reset counter
  samplesRead = 0;
}

Hi there,

Yes I too ran into this issue some time ago, several have and there are posts about how to handle it. The most Obvious is “USE THE FIFO”

the Assistant breaks it down this way…
To achieve higher sampling rates for reading IMU data with the LSM6DS3 on the Xiao nRF52840 Sense, you can focus on optimizing the communication and processing of data. Here are a few key strategies to improve your sampling rate:

1. Utilize Direct FIFO Reading

The LSM6DS3 has a built-in FIFO (First-In, First-Out) buffer that allows for continuous sampling without waiting for data availability flags. By configuring the FIFO and reading data in batches, you can significantly reduce the overhead of individual read operations.

2. Optimize I2C Speed

You’ve already set the I2C clock to 400 kHz. If the hardware supports it, consider increasing the clock speed to 1 MHz (Fast Mode Plus). This reduces the time spent on I2C transactions.

Wire1.setClock(1000000UL); // Set I2C speed to 1 MHz

3. Configure the IMU for Maximum Output Data Rate

The LSM6DS3 supports an accelerometer output data rate (ODR) of up to 1.66 kHz. To utilize the highest rate:

  • Set CTRL1_XL to 0x80 (1.66 kHz).
  • Disable filters or unnecessary processing in the IMU.

4. Reduce String Formatting Overhead

String operations in Arduino are relatively slow. Instead of building strings in real-time, store raw values in a buffer and process them later.

5. Minimize Interruptions

Disable unnecessary interrupts and avoid using Serial.print inside the high-frequency sampling loop. Use a large buffer to store samples and print data after sampling is complete.

6. Use Interrupts for Data Availability

Instead of polling the status register (STATUS_REG), use the LSM6DS3’s data-ready interrupt pin. This reduces CPU time spent on checking for new data.

Optimized Code Example:

Below is a modified version of your code that incorporates these optimizations:

#include <Arduino.h>
#include <LSM6DS3.h>
#include <Wire.h>

#define SAMPLING_RATE 1660 // Maximum supported by LSM6DS3
#define BUFFER_SIZE 1024   // Adjust buffer size as needed
//#define IMU_INT_PIN 2      // Connect INT1 or INT2 to this pin

LSM6DS3 myIMU(I2C_MODE, 0x6A);
//#define int2Pin PIN_LSM6DS3TR_C_INT1
#define int2Pin P0_11
volatile bool dataReady = false;
float ax, ay, az;
int16_t accelData[BUFFER_SIZE][3];
int sampleIndex = 0;

// Interrupt handler for IMU
void imuInterruptHandler() {
  dataReady = true;
}

void setup() {
  Serial.begin(9600);
  while (!Serial);

  pinMode(int2Pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(int2Pin), imuInterruptHandler, RISING);

  // Initialize IMU
  myIMU.settings.accelEnabled = 1;
  myIMU.settings.accelRange = 16; // Set range to ±16g for better resolution
  myIMU.settings.accelSampleRate = 1660; // 1.66 kHz
  if (myIMU.begin() != 0) {
    Serial.println("Failed to initialize IMU!");
    while (1);
  }

  // Configure IMU interrupts
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_INT1_CTRL, 0x01); // Enable data-ready interrupt
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_CTRL1_XL, 0x80);  // 1.66 kHz, ±16g

  Serial.println("IMU initialized. Starting sampling...");
}

void loop() {
  if (dataReady) {
    dataReady = false;

    uint8_t dataBuff[6];
    myIMU.readRegisterRegion(dataBuff, LSM6DS3_ACC_GYRO_OUTX_L_XL, 6);

    accelData[sampleIndex][0] = (int16_t)(dataBuff[0] | (dataBuff[1] << 8));
    accelData[sampleIndex][1] = (int16_t)(dataBuff[2] | (dataBuff[3] << 8));
    accelData[sampleIndex][2] = (int16_t)(dataBuff[4] | (dataBuff[5] << 8));

    sampleIndex++;
    if (sampleIndex >= BUFFER_SIZE) {
      printData();
      sampleIndex = 0;
    }
  }
}

void printData() {
  for (int i = 0; i < BUFFER_SIZE; i++) {
    Serial.print(accelData[i][0]);
    Serial.print(",");
    Serial.print(accelData[i][1]);
    Serial.print(",");
    Serial.println(accelData[i][2]);
  }
}

Key Features:

  1. Interrupt-Driven Sampling:
  • Data-ready interrupts eliminate polling overhead.
  1. FIFO Buffering:
  • Use the BUFFER_SIZE to temporarily store samples and print them in bulk.
  1. Minimal Processing in the Loop:
  • Data is read directly and stored in a buffer without additional computation.
  1. I2C at Maximum Speed:
  • The Wire1.setClock(1000000UL); ensures fast communication.

With these optimizations, you should be able to get closer to the IMU’s maximum output rate of 1.66 kHz. Let US know how it does. :+1:

HTH
GL :slight_smile: PJ :v:

2 Likes

I measured the I2C read time with a digital scope.

With the SCL clock set to the maximum value of 400 kHz, it takes 600 uS to read 12 bytes of data from consecutive registers using the readRegisterRegion() function.
Even with interrupts and FIFOs, it would be difficult to achieve a sample rate of 1.666 kHz.

2 Likes

Hi there,

Hmm, I didn’t measure it , sorry but my estimate was:

  • I2C Speed: At 400 kHz, each clock cycle is 2.5 µs.
  • Data Transfer: Reading 12 bytes (let’s assume 8-bit data per byte) will involve 96 bits to transfer, which at 400 kHz would take roughly 240 µs (96 bits / 400 kHz = 240 µs).
    I know there is some overhead but that seems slow to me?

Correct, this assumes I2C the clock is Higher too. and the buffer is HUGE…
Plus I’m not certain the read register region ()function is the optimum method.
I always felt like the I2C wasn’t the fastest way too, SPI would be way better IMO.
can you triple the buffer and measure again?
TIA
GL :slight_smile: PJ :v:

SO I thought the Nrf52840 Supported the Fast Mode Plus?

Wire.setClock(1000000);  // Set I2C to 1 MHz (Fast Mode Plus)

It may be the IMU that doesn’t , SMH?

1 Like

The maximum clock frequency in IMU fast mode is 400 kHz.
Reading LSM6DS3.cpp:165, readRegisterRegion()
is the same as the method described in “6.3.1 I2C operation” in the datasheet.
I can’t analyze further how much overhead there is because the IMU is under the can, so I can’t access the I2C pins.

When compiled with mbed, it is 600uS, but when compiled with non-mbed, it is 400uS. This may be usable.

3 Likes

Hello, may a please how to use (initialise) IMU for get data with non-mbed version ? In my board when I use any example from I got message “device error”
Thanks

Install and use “Seeed Arduino LSM6DS3 2.0.4” from the ArduinoIDE library manager.

From the menu bar,
select File > Examples > Example from Custom Libraries > Seeed Arduino LSM6DS3 > HighLevelExample.
Select the Board Support Package “Seeed nRF52 Boards 1.1.10”, and choose the board as “Seeed nRF52 Boards > Seeed XIAO nRF52840 Sense”. Compiling it will work without any issues.

Rather than calling micros() and comparing it each time, you can track the elapsed time more simply using millis() or a counter-based system to control the read rate more precisely.

unsigned long startTime = millis();
unsigned long endTime = startTime + 1000 / SAMPLING_RATE;

while (millis() < endTime) {
    // Read sensor data
}

You can also use an MPU6050 to cross-check your data. Introduction to MPU6050 - The Engineering Projects

1 Like

I also want to get IMU data at about 400Hz with BLE. anyone can explain if easyDMA + fifo + timer is feasible?if yes, any code example?

Hi there,

And welcome here…

So Have you tried ANYTHING ? I would recommend testing some and see what you can discover. If your just asking then the Answer is YES!
There are a number of VERY useful threads on the IMU subject, Go and read those and get some idea of what it is you are asking, No substitute for hands on testing.
What 400HZ, means to the sensor and your process.
Assume 6-axis @ 16-bit = 12 B/sample (+ 4 B timestamp) → 16 B/sample.
400 samples/s → 6.4 kB/s ≈ 51 kbps. BLE 1M with DLE easily handles ~100–200 kbps practical; BLE 2M does ~300+ kbps in practice. You’ve got headroom.

Sensor side (easyDMA + FIFO + timer)

  • Useing SPI + EasyDMA (SPIM), not I²C, at 8–10 MHz. The LSM6DS3’s FIFO can sample at 416 Hz ODR—perfect match.
  • Configure FIFO in continuous mode with a watermark (e.g., 8 or 16 samples).
  • Use INT pin → GPIOTE/DPPI to trigger a SPIM read of the FIFO burst (EasyDMA = near-zero CPU).
  • Timestamp either:
    • one timestamp per burst (cheap), or
    • capture a timer at first sample and reconstruct (sample_idx * Ts).

Result: sensor → burst read → push N packed samples into a lock-free ring buffer (k_fifo/k_msgq).

You don’t need SPI to hit 400 Hz if you use the IMU’s FIFO + watermark interrupt and move data in bursts. The nRF I²C (TWIM) driver uses EasyDMA under the hood, so large i2c_write_read() bursts cost very little CPU.

While I won’t provide the code for you, (give a fish) I will offer that this works and is in use today (teach a Man to Fish) in the BLE connected “Deff-Punk Helmet” check that out too.

Code algorithm Pattern that works…

  1. IMU setup (e.g., LSM6DS3/DSO):
  • ODR 416 Hz (lines up nicely with BLE intervals).
  • FIFO continuous mode.
  • Watermark = 8 or 16 samples.
  • INT → GPIO on watermark (edge).
  1. GPIO ISR: just queue work (don’t read I²C in ISR).
  2. Work handler: do one burst i2c_write_read to drain N FIFO samples into a buffer (driver’s EasyDMA handles the copy), push into a ring buffer.
  3. BLE notify worker: every connection interval (e.g., 20–30 ms), pop 8–16 samples and send one notification (e.g., 128–256 B) using DLE (251B) and MTU 247. Request 2M PHY if the central supports it.

This is a GREAT topic and has been since the Xiao Sense showed up. I think it’s one of Seeeds Best Xiao’s

HTH
GL :slight_smile: PJ :v:

What not to do

  • Don’t send 400 notifications/sec of 16 B each. Phones (esp. iOS) will throttle per-event packet counts; you’ll see jitter and drops.
  • Don’t poll-read the LSM6DS3 every 2.5 ms over I²C—you’ll waste CPU and likely miss deadlines. FIFO + SPI burst is the way.
  • Don’t leave LED timers/logging on during throughput measurements; they add jitter and current.

Back-of-napkin timing

At CI = 20 ms, 50 notifies/s means ~1 notify every 20 ms. Each notify carries 8 samples (128 B payload + ATT overhead), which fits under a single 251-byte LL packet with DLE. Even with 1M PHY you’ve got tons of margin; with 2M, even more. :+1:

for the Peanut Gallery , this is one case that and INTERRUPT PIN is required. PERIOD! :grin:
Key point: I²C is only touched at watermark cadence (e.g., 16 samples @ 416 Hz → ~26 reads/s). That’s tiny CPU load and plays very nicely with BLE.

Here are my code, its seems easyDMA + TWIM is work, but no data received in dma_buffer. can you help to check? attached source code and its printed message. Many thanks!

#include <Arduino.h>
#include "nrf.h"
#include <LSM6DS3.h>

#define IMU_ADDR         0x6A
#define FIFO_DATA_OUT_L  0x3E
#define FIFO_READ_SIZE   32

volatile bool imu_ready   = false;   // TIMER1 interrup ready
volatile bool twim_busy   = false;   // TWIM DMA busy
volatile bool dma_done    = false;   // DMA done
volatile bool dma_error   = false;   // DMA error
volatile uint32_t dma_error_src = 0; // DMA error source

uint8_t dma_buffer[FIFO_READ_SIZE];

// ================= LSM6DS3  =================
LSM6DS3 myIMU(I2C_MODE, IMU_ADDR);

// ==================== TIMER1 interrup ====================
extern "C" void TIMER1_IRQHandler(void)
{
  if (NRF_TIMER1->EVENTS_COMPARE[0]) {
    NRF_TIMER1->EVENTS_COMPARE[0] = 0;  // clear event
    imu_ready = true;                   //  IMU ready flag
  }
}

// ==================== TWIM0 DMA  interrupt ====================
extern "C" void SPIM0_TWIM0_IRQHandler(void)
{
    if (NRF_TWIM0->EVENTS_LASTRX) {
        NRF_TWIM0->EVENTS_LASTRX = 0;
        twim_busy = false;
        dma_done = true;
        Serial.println("LASTRX interrupt");
    }

    if (NRF_TWIM0->EVENTS_ERROR) {
        NRF_TWIM0->EVENTS_ERROR = 0;
        Serial.print("Error source: ");
        Serial.println(NRF_TWIM0->ERRORSRC);
        dma_error_src = NRF_TWIM0->ERRORSRC;
        twim_busy = false;
        dma_error = true;
    }
}

// ==================== 初始化 TWIM0 + EasyDMA ====================
void init_twim0()
{
     // ===== 1️⃣ start  HFCLK (same as PDM)=====
    NRF_CLOCK->TASKS_HFCLKSTART = 1;
    while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0);
    NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;  // clear event flag
    NRF_TWIM0->ENABLE = 0;
    NRF_TWIM0->PSEL.SCL = PIN_WIRE_SCL;
    NRF_TWIM0->PSEL.SDA = PIN_WIRE_SDA;
    NRF_TWIM0->FREQUENCY = 0x01980000; // 100kHz
    NRF_TWIM0->ADDRESS = IMU_ADDR;

    // 清除事件
    NRF_TWIM0->EVENTS_LASTTX  = 0;
    NRF_TWIM0->EVENTS_LASTRX  = 0;
    NRF_TWIM0->EVENTS_STOPPED = 0;
    NRF_TWIM0->EVENTS_ERROR   = 0;

    NRF_TWIM0->ENABLE = TWIM_ENABLE_ENABLE_Enabled << TWIM_ENABLE_ENABLE_Pos;

    // enable LASTRX / ERROR interrupt
    NRF_TWIM0->INTENSET = TWIM_INTENSET_LASTRX_Msk | TWIM_INTENSET_ERROR_Msk;
    

    NRF_TWIM0->SHORTS = TWIM_SHORTS_LASTTX_STOP_Msk;


    // SHORTS start RX automatic
    NRF_TWIM0->SHORTS = TWIM_SHORTS_LASTTX_STARTRX_Msk;

    NVIC_SetPriority(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQn, 2);
    NVIC_ClearPendingIRQ(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQn);
    NVIC_EnableIRQ(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQn);

    // ===== 8️⃣ enable TWIM =====
    NRF_TWIM0->ENABLE = TWIM_ENABLE_ENABLE_Enabled << TWIM_ENABLE_ENABLE_Pos;
    Serial.println("TWIM0 + EasyDMA Initialized.");
}

void init_IMU_fifo(){
      //Over-ride default settings if desired
  myIMU.settings.gyroEnabled = 1;  //Can be 0 or 1
  myIMU.settings.gyroRange = 2000;   //Max deg/s.  Can be: 125, 245, 500, 1000, 2000
  myIMU.settings.gyroSampleRate = 833;   //Hz.  Can be: 13, 26, 52, 104, 208, 416, 833, 1666
  myIMU.settings.gyroBandWidth = 200;  //Hz.  Can be: 50, 100, 200, 400;
  myIMU.settings.gyroFifoEnabled = 1;  //Set to include gyro in FIFO
  myIMU.settings.gyroFifoDecimation = 1;  //set 1 for on /1

  myIMU.settings.accelEnabled = 1;
  myIMU.settings.accelRange = 16;      //Max G force readable.  Can be: 2, 4, 8, 16
  myIMU.settings.accelSampleRate = 833;  //Hz.  Can be: 13, 26, 52, 104, 208, 416, 833, 1666, 3332, 6664, 13330
  myIMU.settings.accelBandWidth = 200;  //Hz.  Can be: 50, 100, 200, 400;
  myIMU.settings.accelFifoEnabled = 1;  //Set to include accelerometer in the FIFO
  myIMU.settings.accelFifoDecimation = 1;  //set 1 for on /1
  myIMU.settings.tempEnabled = 1;
  
  //Non-basic mode settings
  myIMU.settings.commMode = 1;

  myIMU.settings.timestampEnabled=1;    // 1: enable timestamp ; 0: disable timestamp
  myIMU.settings.timestampFifoEnabled=1;// 1: enable write timestamp into fifo ; 0: disable
  myIMU.settings.timestampResolution=1; // 1: Set timestamp resolution ; 0: 6.4ms  1: 25us  

  //FIFO control settings
  myIMU.settings.fifoThreshold = 100;  //Can be 0 to 4096 (16 bit bytes)
  myIMU.settings.fifoSampleRate = 50;  //Hz.  Can be: 10, 25, 50, 100, 200, 400, 800, 1600, 3300, 6600
  myIMU.settings.fifoModeWord = 6;  //FIFO mode.
  //FIFO mode.  Can be:
  //  0 (Bypass mode, FIFO off)
  //  1 (Stop when full)
  //  3 (Continuous during trigger)
  //  4 (Bypass until trigger)
  //  6 (Continous mode)
  
}

// ==================== 启动一次 DMA FIFO 读取 ====================
void start_dma_read(uint8_t *buf, uint16_t len)
{
    static uint8_t reg_addr = FIFO_DATA_OUT_L;
    if (twim_busy) return;
    twim_busy = true;

    NRF_TWIM0->EVENTS_LASTTX  = 0;
    NRF_TWIM0->EVENTS_LASTRX  = 0;
    NRF_TWIM0->EVENTS_STOPPED = 0;
    NRF_TWIM0->EVENTS_ERROR   = 0;

    NRF_TWIM0->TXD.PTR    = (uint32_t)&reg_addr;
    NRF_TWIM0->TXD.MAXCNT = 1;
    NRF_TWIM0->RXD.PTR    = (uint32_t)buf;
    NRF_TWIM0->RXD.MAXCNT = len;

    NRF_TWIM0->SHORTS = TWIM_SHORTS_LASTTX_STOP_Msk;  
    NRF_TWIM0->TASKS_STARTTX = 1;

    // waiting STOP
    while (!NRF_TWIM0->EVENTS_STOPPED);
    NRF_TWIM0->EVENTS_STOPPED = 0;

    // clear error
    NRF_TWIM0->ERRORSRC = 0;

    // start RX
    NRF_TWIM0->SHORTS = TWIM_SHORTS_LASTRX_STOP_Msk;
    NRF_TWIM0->TASKS_STARTRX = 1;

    // waiting 
    while (!NRF_TWIM0->EVENTS_LASTRX && !NRF_TWIM0->EVENTS_STOPPED);

    // clear event, ready for next run
    NRF_TWIM0->EVENTS_LASTRX = 0;
    NRF_TWIM0->EVENTS_STOPPED = 0;
    NRF_TWIM0->EVENTS_ERROR = 0;
    NRF_TWIM0->ERRORSRC = 0;

    twim_busy = false;
}



// ==================== 初始化 TIMER1 ====================
void initTimer() {
  NRF_TIMER1->MODE = TIMER_MODE_MODE_Timer;           
  NRF_TIMER1->PRESCALER = 4;                          // 16MHz / 2^4 = 1MHz (1us per tick)
  NRF_TIMER1->BITMODE = TIMER_BITMODE_BITMODE_32Bit;  
  NRF_TIMER1->CC[0] = 200000;               // 5000us = 200Hz
  NRF_TIMER1->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Msk;
  NRF_TIMER1->INTENSET = TIMER_INTENSET_COMPARE0_Msk;
  NVIC_SetPriority(TIMER1_IRQn, 3); 
  NVIC_EnableIRQ(TIMER1_IRQn);                        
  NRF_TIMER1->TASKS_START = 1;                        
}
// ==================== 打印 FIFO 数据 ====================
void print_fifo_bytes(const uint8_t *buf, size_t len)
{
    Serial.print("FIFO bytes: ");
    for (size_t i = 0; i < len; ++i) {
        Serial.printf("%02X ", buf[i]);
    }
    Serial.println();
}

// ==================== 打印 TWIM0 状态 ====================
void print_twim_status()
{
  NRF_TWIM_Type *twim = NRF_TWIM0;

  Serial.println("---- TWIM0 Registers ----");
  Serial.print("ENABLE: "); Serial.println(twim->ENABLE);
  Serial.print("ADDRESS: 0x"); Serial.println(twim->ADDRESS, HEX);
  Serial.print("FREQUENCY: 0x"); Serial.println(twim->FREQUENCY,HEX);
  Serial.print("TASKS_STARTRX: "); Serial.println(twim->TASKS_STARTRX);
  Serial.print("TASKS_STARTTX: "); Serial.println(twim->TASKS_STARTTX);
  Serial.print("EVENTS_TXSTARTED: "); Serial.println(twim->EVENTS_TXSTARTED);
  Serial.print("EVENTS_RXSTARTED: "); Serial.println(twim->EVENTS_RXSTARTED);
  Serial.print("EVENTS_LASTTX: "); Serial.println(twim->EVENTS_LASTTX);
  Serial.print("EVENTS_LASTRX: "); Serial.println(twim->EVENTS_LASTRX);
  Serial.print("EVENTS_STOPPED: "); Serial.println(twim->EVENTS_STOPPED);
  Serial.print("EVENTS_ERROR: "); Serial.println(twim->EVENTS_ERROR);
  Serial.print("ERRORSRC: "); Serial.println(twim->ERRORSRC);

  Serial.print("RXD.PTR: 0x"); Serial.println((uint32_t)twim->RXD.PTR, HEX);
  Serial.print("RXD.MAXCNT: "); Serial.println(twim->RXD.MAXCNT);
  Serial.print("TXD.PTR: 0x"); Serial.println((uint32_t)twim->TXD.PTR, HEX);
  Serial.print("TXD.MAXCNT: "); Serial.println(twim->TXD.MAXCNT);

  Serial.println("--------------------------");

}

// ==================== setup ====================
void setup()
{
    Serial.begin(115200);
    while (!Serial) delay(10);

    Serial.println("nRF52840 EasyDMA + TIMER1 FIFO Read");
    

    // init IMU
    if (myIMU.begin() != 0) {
        Serial.println("Failed to initialize LSM6DS3!");
        while (true) delay(100);
    }else    Serial.println("initialized LSM6DS3!");
    init_IMU_fifo();
    Serial.print("Configuring FIFO...");
    myIMU.fifoBegin();
    Serial.println("Done!");

    Serial.print("Clearing FIFO...");
    myIMU.fifoClear();
    Serial.println("Done!");

    // 初始化 TWIM0 + DMA
    init_twim0();

    // 初始化 TIMER1,每 2 s 触发一次
    initTimer(); // 2s

}

// ==================== loop ====================
void loop()
{
    NRF_TWIM_Type *twim = NRF_TWIM0;
    Serial.print("imu_ready: "); Serial.println(imu_ready);
    Serial.print("twim_busy: "); Serial.println(twim_busy);
    Serial.print("dma_done: ");  Serial.println(dma_done);
    Serial.print("dma_error: "); Serial.println(dma_error);
    print_fifo_bytes(dma_buffer, FIFO_READ_SIZE);
    print_twim_status();
    if (imu_ready && !twim_busy) {
        imu_ready = false;
        Serial.println("TIMER1 IRQ - start DMA read");
        start_dma_read(dma_buffer, FIFO_READ_SIZE);

        // 打印 DMA 启动后的 TWIM 状态
        print_twim_status();
    }

    if (dma_done) {
        dma_done = false;
        Serial.println("DMA done, FIFO data:");
        print_fifo_bytes(dma_buffer, FIFO_READ_SIZE);
    }

    if (dma_error) {
        dma_error = false;
        Serial.print("TWIM error, source: 0x");
        Serial.println(dma_error_src, HEX);
        print_twim_status();
    }
}

the printed message as below.

---- TWIM0 Registers ----
ENABLE: 6
ADDRESS: 0x6A
FREQUENCY: 0x1980000
TASKS_STARTRX: 0
TASKS_STARTTX: 0
EVENTS_TXSTARTED: 1
EVENTS_RXSTARTED: 1
EVENTS_LASTTX: 0
EVENTS_LASTRX: 0
EVENTS_STOPPED: 0
EVENTS_ERROR: 0
ERRORSRC: 2
RXD.PTR: 0x20006328
RXD.MAXCNT: 32
TXD.PTR: 0x20006000
TXD.MAXCNT: 1
--------------------------
imu_ready: 0
twim_busy: 0
dma_done: 0
dma_error: 0
FIFO bytes: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

Hi there,

Ok, So a lot to unpack here. You mixed methods, for the Easy DMA for one. The peripheral never actually gets a valid read, so EasyDMA never writes anything into it. If you use Arduino LSMDS3.h it uses WIRE under the Hood , Wire uses I2C different, than TWIM . Twim is a manual method. pick one or the other.
So I compiled it using the Seeed Nrf52840 Sense BSP at 1.1.11, I had to comment out the Timestamp business, because it is not there or wrong. I get the same serial output… verifies your process. :+1: SO there’s That :grin:

Also the DMA read is mixing polling with a callback interrupt, and a manual StartRX and even a Manual STOP the shorts get over written multiple times (buffer count ?) TOO much activity no way you can debug all that.

Start with a Polling only first version. then enable interrupts after you get some buffer data.

On nRF52 TWIM, ERRORSRC bits are:

  • 0x01 – OVERRUN
  • 0x02 – ANACK (address not acknowledged) :point_left: :wink:
  • 0x04 – DNACK (data not acknowledged)

Get rid of the ANACK and it will work.
Wire use twim0 already, so you reconfigure it and it breaks after the LMS6DS3 sets it… fix that.
So ERRORSRC = 2 means ANACK: the slave did not acknowledge the address phase.
If the address phase fails, the RX part never happens → EasyDMA never moves any bytes → your dma_buffer stays all zeros, exactly like you’re seeing.

So the real issue is before the buffer: the I²C transaction itself is failing.
On the XIAO nRF52840 Sense the LSM6DS3 is not on the external SDA/SCL header. It’s on an internal I²C bus wired to TWIM1 with private pins.

When you configured NRF_TWIM0 (or overwrote PSEL on TWIM1), you were talking to an empty bus, which is why you always saw ERRORSRC = 2 (ANACK) and a zeroed DMA buffer.

The trick is:

  1. Let the Seeed LSM6DS3 library run myIMU.begin() so it configures the right TWIM and pins.
  2. After that, detect which TWIM instance has valid PSEL (not 0xFFFFFFFF) and reuse that peripheral for your EasyDMA transfers without changing its PSEL or ADDRESS.
  3. Just set TXD.PTR/MAXCNT, RXD.PTR/MAXCNT, SHORTS = LASTTX_STARTRX | LASTRX_STOP, then TASKS_STARTTX = 1 and poll for EVENTS_STOPPED / EVENTS_ERROR.

Once you do that, DMA works and the FIFO bytes come out correctly.

This works :+1:

// ============================================================================
// XIAO nRF52840 Sense - LSM6DS3 FIFO via EasyDMA (Auto-detect TWIM, Polling)
// Rev 1.3 - 2025-11-12
// PJGlasso & AI adv model NSD
// ----------------------------------------------------------------------------
// - Board: Seeed XIAO nRF52840 Sense (Seeed nRF52 Boards BSP 1.1.11)
// - IMU:   LSM6DS3TR-C on internal I2C bus (not external SDA/SCL header)
// - Idea:  Let the LSM6DS3 library configure the I2C bus, then reuse the
//          same TWIM instance for raw EasyDMA FIFO reads.
// ============================================================================

#include <Arduino.h>
#include <Adafruit_TinyUSB.h>
#include "nrf.h"
#include <LSM6DS3.h>

#define IMU_ADDR        0x6A
#define FIFO_DATA_OUT_L 0x3E
#define FIFO_READ_SIZE  32

#ifdef PIN_LSM6DS3TR_C_POWER
  #define IMU_POWER_PIN PIN_LSM6DS3TR_C_POWER
#endif

const char FW_REV[] = "Rev 1.3 - 2025-11-12";

NRF_TWIM_Type *imu_twim = nullptr;

bool     dma_done      = false;
bool     dma_error     = false;
uint32_t dma_error_src = 0;

uint8_t dma_buffer[FIFO_READ_SIZE];

LSM6DS3 myIMU(I2C_MODE, IMU_ADDR);

// ---------------- IMU FIFO config (no timestamp fields) ----------------
void init_IMU_fifo() {
  myIMU.settings.gyroEnabled        = 1;
  myIMU.settings.gyroRange          = 2000;
  myIMU.settings.gyroSampleRate     = 833;
  myIMU.settings.gyroBandWidth      = 200;
  myIMU.settings.gyroFifoEnabled    = 1;
  myIMU.settings.gyroFifoDecimation = 1;

  myIMU.settings.accelEnabled       = 1;
  myIMU.settings.accelRange         = 16;
  myIMU.settings.accelSampleRate    = 833;
  myIMU.settings.accelBandWidth     = 200;
  myIMU.settings.accelFifoEnabled   = 1;
  myIMU.settings.accelFifoDecimation= 1;

  myIMU.settings.tempEnabled        = 1;
  myIMU.settings.commMode           = 1;    // I2C

  myIMU.settings.fifoThreshold      = 100;
  myIMU.settings.fifoSampleRate     = 50;
  myIMU.settings.fifoModeWord       = 6;    // continuous mode
}

// ---------------- Detect which TWIM the IMU is on ----------------------
static bool twim_has_valid_pins(NRF_TWIM_Type *twim) {
  return (twim->PSEL.SCL != 0xFFFFFFFFu) && (twim->PSEL.SDA != 0xFFFFFFFFu);
}

void detect_imu_twim() {
  bool t0 = twim_has_valid_pins(NRF_TWIM0);
  bool t1 = twim_has_valid_pins(NRF_TWIM1);

  Serial.println("Detecting IMU TWIM instance...");
  Serial.print("  TWIM0 PSEL.SCL: 0x"); Serial.println(NRF_TWIM0->PSEL.SCL, HEX);
  Serial.print("  TWIM0 PSEL.SDA: 0x"); Serial.println(NRF_TWIM0->PSEL.SDA, HEX);
  Serial.print("  TWIM1 PSEL.SCL: 0x"); Serial.println(NRF_TWIM1->PSEL.SCL, HEX);
  Serial.print("  TWIM1 PSEL.SDA: 0x"); Serial.println(NRF_TWIM1->PSEL.SDA, HEX);

  if (t0 && !t1) {
    imu_twim = NRF_TWIM0;
    Serial.println("Selected IMU TWIM: TWIM0");
  } else if (!t0 && t1) {
    imu_twim = NRF_TWIM1;
    Serial.println("Selected IMU TWIM: TWIM1");
  } else if (t0 && t1) {
    imu_twim = NRF_TWIM1; // if both, prefer TWIM1
    Serial.println("Both TWIM0 and TWIM1 valid, defaulting to TWIM1.");
  } else {
    imu_twim = nullptr;
    Serial.println("ERROR: Could not find an active TWIM for IMU.");
  }
}

// ---------------- Prepare EasyDMA on that TWIM -------------------------
void init_twim_for_dma() {
  if (!imu_twim) return;

  // Start HFCLK (shared for all TWIMs)
  NRF_CLOCK->TASKS_HFCLKSTART = 1;
  while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0);
  NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;

  // Do NOT touch PSEL or ADDRESS here, reuse what the library set
  imu_twim->ENABLE = TWIM_ENABLE_ENABLE_Enabled << TWIM_ENABLE_ENABLE_Pos;

  imu_twim->EVENTS_STOPPED   = 0;
  imu_twim->EVENTS_LASTTX    = 0;
  imu_twim->EVENTS_LASTRX    = 0;
  imu_twim->EVENTS_TXSTARTED = 0;
  imu_twim->EVENTS_RXSTARTED = 0;
  imu_twim->EVENTS_ERROR     = 0;
  imu_twim->ERRORSRC         = 0;
  imu_twim->SHORTS           = 0;

  Serial.println("TWIM + EasyDMA prepared (reusing IMU bus config).");
}

// ---------------- Polling DMA FIFO read --------------------------------
void dma_read_fifo_polling(uint8_t *buf, uint16_t len) {
  static uint8_t reg_addr = FIFO_DATA_OUT_L;

  if (!imu_twim) {
    Serial.println("dma_read_fifo_polling(): imu_twim is NULL.");
    return;
  }

  dma_done      = false;
  dma_error     = false;
  dma_error_src = 0;

  imu_twim->EVENTS_STOPPED   = 0;
  imu_twim->EVENTS_LASTTX    = 0;
  imu_twim->EVENTS_LASTRX    = 0;
  imu_twim->EVENTS_TXSTARTED = 0;
  imu_twim->EVENTS_RXSTARTED = 0;
  imu_twim->EVENTS_ERROR     = 0;
  imu_twim->ERRORSRC         = 0;

  imu_twim->TXD.PTR    = (uint32_t)&reg_addr;
  imu_twim->TXD.MAXCNT = 1;

  imu_twim->RXD.PTR    = (uint32_t)buf;
  imu_twim->RXD.MAXCNT = len;

  imu_twim->SHORTS =
      TWIM_SHORTS_LASTTX_STARTRX_Msk |
      TWIM_SHORTS_LASTRX_STOP_Msk;

  imu_twim->TASKS_STARTTX = 1;

  uint32_t guard = 0;
  while (!imu_twim->EVENTS_STOPPED && !imu_twim->EVENTS_ERROR) {
    if (guard++ > 2000000) {
      Serial.println("Timeout waiting for TWIM STOP/ERROR.");
      break;
    }
  }

  if (imu_twim->EVENTS_ERROR) {
    dma_error     = true;
    dma_error_src = imu_twim->ERRORSRC;
    imu_twim->EVENTS_ERROR = 0;
    imu_twim->ERRORSRC     = 0;
  } else if (imu_twim->EVENTS_STOPPED) {
    dma_done = true;
  }

  imu_twim->EVENTS_STOPPED  = 0;
  imu_twim->EVENTS_LASTTX   = 0;
  imu_twim->EVENTS_LASTRX   = 0;
}

// ---------------- Helpers ----------------------------------------------
void print_fifo_bytes(const uint8_t *buf, size_t len) {
  Serial.print("FIFO bytes: ");
  for (size_t i = 0; i < len; ++i) {
    Serial.printf("%02X ", buf[i]);
  }
  Serial.println();
}

// ---------------- setup() ----------------------------------------------
void setup() {
  Serial.begin(115200);
  while (!Serial) { delay(10); }

  Serial.println();
  Serial.println("=================================================");
  Serial.println(" XIAO nRF52840 Sense - LSM6DS3 FIFO DMA Demo (Auto TWIM)");
  Serial.println(" Using EasyDMA on the same TWIM as the IMU library");
  Serial.print  (" Firmware: "); Serial.println(FW_REV);
  Serial.println(" MCU: nRF52840 @ 64 MHz");
  Serial.println(" Board: Seeed XIAO nRF52840 Sense");
  Serial.println("=================================================");

#ifdef IMU_POWER_PIN
  pinMode(IMU_POWER_PIN, OUTPUT);
  digitalWrite(IMU_POWER_PIN, HIGH);
  Serial.println("IMU power pin enabled (PIN_LSM6DS3TR_C_POWER).");
#endif

  Serial.println("Initializing LSM6DS3 via library...");
  if (myIMU.begin() != 0) {
    Serial.println("ERROR: Failed to initialize LSM6DS3!");
    while (true) { delay(500); }
  }
  Serial.println("LSM6DS3 initialized OK.");

  init_IMU_fifo();
  Serial.print("Configuring FIFO..."); myIMU.fifoBegin();  Serial.println("Done!");
  Serial.print("Clearing FIFO..."   ); myIMU.fifoClear();  Serial.println("Done!");

  detect_imu_twim();
  init_twim_for_dma();

  Serial.println("Setup complete. Starting main loop...");
}

// ---------------- loop() ------------------------------------------------
void loop() {
  dma_read_fifo_polling(dma_buffer, FIFO_READ_SIZE);

  Serial.print("dma_done: ");  Serial.println(dma_done);
  Serial.print("dma_error: "); Serial.println(dma_error);
  if (dma_error) {
    Serial.print("dma_error_src: 0x"); Serial.println(dma_error_src, HEX);
  }

  print_fifo_bytes(dma_buffer, FIFO_READ_SIZE);
  Serial.println();

  delay(200);
}

Output is this

=================================================
 XIAO nRF52840 Sense - LSM6DS3 FIFO DMA Demo (Auto TWIM)
 Using EasyDMA on the same TWIM as the IMU library
 Firmware: Rev 1.3 - 2025-11-12
 MCU: nRF52840 @ 64 MHz
 Board: Seeed XIAO nRF52840 Sense
=================================================
IMU power pin enabled (PIN_LSM6DS3TR_C_POWER).
Initializing LSM6DS3 via library...
LSM6DS3 initialized OK.
Configuring FIFO...Done!
Clearing FIFO...Done!
Detecting IMU TWIM instance...
  TWIM0 PSEL.SCL: 0xFFFFFFFF
  TWIM0 PSEL.SDA: 0xFFFFFFFF
  TWIM1 PSEL.SCL: 0x1B
  TWIM1 PSEL.SDA: 0x7
Selected IMU TWIM: TWIM1
TWIM + EasyDMA prepared (reusing IMU bus config).
Setup complete. Starting main loop...
dma_done: 1
dma_error: 0
FIFO bytes: 47 05 8F FF 5C FB 2B 00 54 00 FA 07 FA 07 FA 07 FA 07 FA 07 FA 07 FA 07 FA 07 FA 07 FA 07 FA 07 

dma_done: 1
dma_error: 0
FIFO bytes: EF 00 76 F9 5F FE 4E 00 5C 00 3D 08 15 00 E3 FF F5 FF 48 00 5E 00 45 08 14 00 E5 FF F3 FF 45 00 
dma_done: 1
dma_error: 0
FIFO bytes: 59 00 3D 08 05 00 E5 FF F5 FF 4B 00 5C 00 40 08 00 00 E8 FF F7 FF 4C 00 5F 00 46 08 03 00 E5 FF 

HTH
GL :slight_smile: PJ :v:

Add the Interrupt part now we know how the TWIM is setup.

1 Like

That’s great! Many thanks!
Still have problem. The IMU data dont change when download the code to my XIAO, and move the chip, the printed message as below. The printed message from your reply, the data is changing. :frowning_face:

=================================================
 XIAO nRF52840 Sense - LSM6DS3 FIFO DMA Demo (Auto TWIM)
 Using EasyDMA on the same TWIM as the IMU library
 Firmware: Rev 1.3 - 2025-11-12
 MCU: nRF52840 @ 64 MHz
 Board: Seeed XIAO nRF52840 Sense
=================================================
IMU power pin enabled (PIN_LSM6DS3TR_C_POWER).
Initializing LSM6DS3 via library...
LSM6DS3 initialized OK.
Configuring FIFO...Done!
Clearing FIFO...Done!
Detecting IMU TWIM instance...
  TWIM0 PSEL.SCL: 0xFFFFFFFF
  TWIM0 PSEL.SDA: 0xFFFFFFFF
  TWIM1 PSEL.SCL: 0x1B
  TWIM1 PSEL.SDA: 0x7
Selected IMU TWIM: TWIM1
TWIM + EasyDMA prepared (reusing IMU bus config).
Setup complete. Starting main loop...
dma_done: 1
dma_error: 0
FIFO bytes: 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 

dma_done: 1
dma_error: 0
FIFO bytes: 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 


Hi there

Can you take a picture of it connected ?

Yes, the data I’m getting is constantly changing when I move the device (no filters)

GL :slight_smile: PJ :v:

Put Xiao in Bootloader mode and Drag and drop the UF2 that is this ZIP file to it, Does the LED do the 3 colors ?
LMK

Xiao_RGB_test.zip (60.8 KB)

Yes, the 3 color LED turn on one by one.

Hi there,

Ok, Great now try the code again,
here is the first 10 'ish lines of compiler output…
yours match ?

FQBN: Seeeduino:nrf52:xiaonRF52840Sense
Using board 'xiaonRF52840Sense' from platform in folder: C:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11
Using core 'nRF5' from platform in folder: C:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11

Detecting libraries used...
C:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\tools\arm-none-eabi-gcc\9-2019q4/bin/arm-none-eabi-g++ -mcpu=cortex-m4 -mthumb -c -g -Werror=return-type -mfloat-abi=hard -mfpu=fpv4-sp-d16 -u _printf_float -std=gnu++11 -ffunction-sections -fdata-sections -fno-threadsafe-statics -nostdlib --param max-inline-insns-single=500 -fno-rtti -fno-exceptions -w -x c++ -E -CC -DF_CPU=64000000 -DARDUINO=10607 -DARDUINO_Seeed_XIAO_nRF52840_Sense -DARDUINO_ARCH_NRF52 -DARDUINO_BSP_VERSION="1.1.11" -DNRF52840_XXAA -DUSBCON -DUSE_TINYUSB -DUSB_VID=0x2886 -DUSB_PID=0x8045 -DUSB_MANUFACTURER="Seeed" -DUSB_PRODUCT="XIAO nRF52840 Sense" -DSOFTDEVICE_PRESENT -DARDUINO_NRF52_ADAFRUIT -DNRF52_SERIES -DDX_CC_TEE -DLFS_NAME_MAX=64 -Ofast -DCFG_DEBUG=0 -DCFG_LOGGER=0 -DCFG_SYSVIEW=0 -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\tools\CMSIS\5.7.0/CMSIS/Core/Include/ -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\tools\CMSIS\5.7.0/CMSIS/DSP/Include/ -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/nrfx -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/nrfx/hal -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/nrfx/mdk -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/nrfx/soc -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/nrfx/drivers/include -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/nrfx/drivers/src -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/softdevice/s140_nrf52_7.3.0_API/include -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/softdevice/s140_nrf52_7.3.0_API/include/nrf52 -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/freertos/Source/include -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/freertos/config -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/freertos/portable/GCC/nrf52 -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/freertos/portable/CMSIS/nrf52 -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/sysview/SEGGER -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/sysview/Config -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11/libraries/Adafruit_TinyUSB_Arduino/src/arduino -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5 -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\variants\Seeed_XIAO_nRF52840_Sense C:\Users\Dude\AppData\Local\arduino\sketches\6C59E73C67F4065822A2D0C2B195F725\sketch\sketch_nov12a.ino.cpp -o nul
Alternatives for Adafruit_TinyUSB.h: [Adafruit TinyUSB [email protected]]
ResolveLibrary(Adafruit_TinyUSB.h)
  -> candidates: [Adafruit TinyUSB [email protected]]
C:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\tools\arm-none-eabi-gcc\9-2019q4/bin/arm-none-eabi-g++ -mcpu=cortex-m4 -mthumb -c -g -Werror=return-type -mfloat-abi=hard -mfpu=fpv4-sp-d16 -u _printf_float -std=gnu++11 -ffunction-sections -fdata-sections -fno-threadsafe-statics -nostdlib --param max-inline-insns-single=500 -fno-rtti -fno-exceptions -w -x c++ -E -CC -DF_CPU=64000000 -DARDUINO=10607 -DARDUINO_Seeed_XIAO_nRF52840_Sense -DARDUINO_ARCH_NRF52 -DARDUINO_BSP_VERSION="1.1.11" -DNRF52840_XXAA -DUSBCON -DUSE_TINYUSB -DUSB_VID=0x2886 -DUSB_PID=0x8045 -DUSB_MANUFACTURER="Seeed" -DUSB_PRODUCT="XIAO nRF52840 Sense" -DSOFTDEVICE_PRESENT -DARDUINO_NRF52_ADAFRUIT -DNRF52_SERIES -DDX_CC_TEE -DLFS_NAME_MAX=64 -Ofast -DCFG_DEBUG=0 -DCFG_LOGGER=0 -DCFG_SYSVIEW=0 -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\tools\CMSIS\5.7.0/CMSIS/Core/Include/ -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\tools\CMSIS\5.7.0/CMSIS/DSP/Include/ -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/nrfx -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/nrfx/hal -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/nrfx/mdk -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/nrfx/soc -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/nrfx/drivers/include -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/nrfx/drivers/src -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/softdevice/s140_nrf52_7.3.0_API/include -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/nordic/softdevice/s140_nrf52_7.3.0_API/include/nrf52 -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/freertos/Source/include -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/freertos/config -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/freertos/portable/GCC/nrf52 -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/freertos/portable/CMSIS/nrf52 -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/sysview/SEGGER -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5/sysview/Config -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11/libraries/Adafruit_TinyUSB_Arduino/src/arduino -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\cores\nRF5 -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\variants\Seeed_XIAO_nRF52840_Sense -IC:\Users\Dude\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.11\libraries\Adafruit_TinyUSB_Arduino\src C:\Users\Dude\AppData\Local\arduino\sketches\6C59E73C67F4065822A2D0C2B195F725\sketch\sketch_nov12a.ino.cpp -o nul
Alternatives for LSM6DS3.h: [Seeed Arduino [email protected]]
ResolveLibrary(LSM6DS3.h)
  -> candidates: [Seeed Arduino [email protected]]

GL :slight_smile: PJ :v:

I’m betting a LIB version difference, or BSP :crossed_fingers:

also throw this one at it , any difference it’s your orginal with adjustments to TWIM 1

// ============================================================================
// XIAO nRF52840 Sense - LSM6DS3 FIFO via EasyDMA (Auto-detect TWIM, Polling)
// Rev 1.2 - 2025-11-12
// ----------------------------------------------------------------------------
// - Board: Seeed XIAO nRF52840 Sense (Seeed nRF52 Boards 1.1.11, non-mbed)
// - IMU:   LSM6DS3TR-C on internal I2C bus (NOT on external SDA/SCL header)
// - Idea:  Let Seeed's LSM6DS3 lib configure the I2C bus, then re-use the
//          same TWIM instance for raw EasyDMA FIFO reads without touching PSEL.
// ============================================================================

#include <Arduino.h>
#include <Adafruit_TinyUSB.h>
#include "nrf.h"
#include <LSM6DS3.h>

// ------------------------- Config / Defines ---------------------------------
#define IMU_ADDR            0x6A
#define FIFO_DATA_OUT_L     0x3E
#define FIFO_READ_SIZE      32

#ifdef PIN_LSM6DS3TR_C_POWER
  #define IMU_POWER_PIN PIN_LSM6DS3TR_C_POWER
#endif

const char FW_REV[] = "Rev 1.2 - 2025-11-12";

// ------------------------- Globals ------------------------------------------
NRF_TWIM_Type *imu_twim = nullptr;   // will point to whichever TWIM talks to IMU

bool     dma_done      = false;
bool     dma_error     = false;
uint32_t dma_error_src = 0;

uint8_t dma_buffer[FIFO_READ_SIZE];

// LSM6DS3 object using the standard IMU address
LSM6DS3 myIMU(I2C_MODE, IMU_ADDR);

// ==================== IMU FIFO Config (no timestamp fields) =================
void init_IMU_fifo() {
  myIMU.settings.gyroEnabled        = 1;
  myIMU.settings.gyroRange          = 2000;
  myIMU.settings.gyroSampleRate     = 833;
  myIMU.settings.gyroBandWidth      = 200;
  myIMU.settings.gyroFifoEnabled    = 1;
  myIMU.settings.gyroFifoDecimation = 1;

  myIMU.settings.accelEnabled       = 1;
  myIMU.settings.accelRange         = 16;
  myIMU.settings.accelSampleRate    = 833;
  myIMU.settings.accelBandWidth     = 200;
  myIMU.settings.accelFifoEnabled   = 1;
  myIMU.settings.accelFifoDecimation= 1;

  myIMU.settings.tempEnabled        = 1;

  myIMU.settings.commMode           = 1;    // I2C

  myIMU.settings.fifoThreshold      = 100;
  myIMU.settings.fifoSampleRate     = 50;
  myIMU.settings.fifoModeWord       = 6;    // continuous mode
}

// ==================== Detect which TWIM the IMU uses ========================
bool twim_has_valid_pins(NRF_TWIM_Type *twim) {
  // On nRF52, "disconnected" PSEL is usually 0xFFFFFFFF.
  return (twim->PSEL.SCL != 0xFFFFFFFFu) && (twim->PSEL.SDA != 0xFFFFFFFFu);
}

void detect_imu_twim() {
  bool t0 = twim_has_valid_pins(NRF_TWIM0);
  bool t1 = twim_has_valid_pins(NRF_TWIM1);

  Serial.println("Detecting IMU TWIM instance...");
  Serial.print("  TWIM0 PSEL.SCL: 0x"); Serial.println(NRF_TWIM0->PSEL.SCL, HEX);
  Serial.print("  TWIM0 PSEL.SDA: 0x"); Serial.println(NRF_TWIM0->PSEL.SDA, HEX);
  Serial.print("  TWIM1 PSEL.SCL: 0x"); Serial.println(NRF_TWIM1->PSEL.SCL, HEX);
  Serial.print("  TWIM1 PSEL.SDA: 0x"); Serial.println(NRF_TWIM1->PSEL.SDA, HEX);

  if (t0 && !t1) {
    imu_twim = NRF_TWIM0;
    Serial.println("Selected IMU TWIM: TWIM0");
  } else if (!t0 && t1) {
    imu_twim = NRF_TWIM1;
    Serial.println("Selected IMU TWIM: TWIM1");
  } else if (t0 && t1) {
    // If both look valid (unlikely), prefer TWIM1 (historically used for IMU)
    imu_twim = NRF_TWIM1;
    Serial.println("Both TWIM0 and TWIM1 have pins; defaulting to TWIM1.");
  } else {
    imu_twim = nullptr;
    Serial.println("ERROR: Could not find an active TWIM for IMU.");
  }
}

// ==================== Init EasyDMA on the detected TWIM =====================
void init_twim_for_dma() {
  if (!imu_twim) {
    Serial.println("init_twim_for_dma(): imu_twim is NULL, abort.");
    return;
  }

  // Start HFCLK (shared for all TWIMs)
  NRF_CLOCK->TASKS_HFCLKSTART = 1;
  while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0);
  NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;

  // DO NOT touch PSEL or ADDRESS here – keep whatever the library set up.
  // Just make sure the peripheral is enabled.
  imu_twim->ENABLE = TWIM_ENABLE_ENABLE_Enabled << TWIM_ENABLE_ENABLE_Pos;

  // Clear events / errors
  imu_twim->EVENTS_STOPPED   = 0;
  imu_twim->EVENTS_LASTTX    = 0;
  imu_twim->EVENTS_LASTRX    = 0;
  imu_twim->EVENTS_TXSTARTED = 0;
  imu_twim->EVENTS_RXSTARTED = 0;
  imu_twim->EVENTS_ERROR     = 0;
  imu_twim->ERRORSRC         = 0;
  imu_twim->SHORTS           = 0;

  Serial.println("TWIM + EasyDMA prepared (reusing IMU bus config).");
}

// ==================== Polling DMA FIFO Read on imu_twim =====================
void dma_read_fifo_polling(uint8_t *buf, uint16_t len) {
  static uint8_t reg_addr = FIFO_DATA_OUT_L;

  if (!imu_twim) {
    Serial.println("dma_read_fifo_polling(): imu_twim is NULL.");
    return;
  }

  dma_done      = false;
  dma_error     = false;
  dma_error_src = 0;

  // Clear events / ERRORSRC
  imu_twim->EVENTS_STOPPED   = 0;
  imu_twim->EVENTS_LASTTX    = 0;
  imu_twim->EVENTS_LASTRX    = 0;
  imu_twim->EVENTS_TXSTARTED = 0;
  imu_twim->EVENTS_RXSTARTED = 0;
  imu_twim->EVENTS_ERROR     = 0;
  imu_twim->ERRORSRC         = 0;

  // Configure TX (register address)
  imu_twim->TXD.PTR    = (uint32_t)&reg_addr;
  imu_twim->TXD.MAXCNT = 1;

  // Configure RX (FIFO buffer)
  imu_twim->RXD.PTR    = (uint32_t)buf;
  imu_twim->RXD.MAXCNT = len;

  // One combined transaction: write reg, repeated START, read len, STOP
  imu_twim->SHORTS =
      TWIM_SHORTS_LASTTX_STARTRX_Msk |
      TWIM_SHORTS_LASTRX_STOP_Msk;

  Serial.print("Starting DMA read, len = ");
  Serial.println(len);

  imu_twim->TASKS_STARTTX = 1;

  // Poll until STOP or ERROR
  uint32_t guard = 0;
  while (!imu_twim->EVENTS_STOPPED && !imu_twim->EVENTS_ERROR) {
    if (guard++ > 2000000) { // crude timeout
      Serial.println("Timeout waiting for TWIM STOP/ERROR.");
      break;
    }
  }

  if (imu_twim->EVENTS_ERROR) {
    dma_error     = true;
    dma_error_src = imu_twim->ERRORSRC;
    imu_twim->EVENTS_ERROR = 0;
    imu_twim->ERRORSRC     = 0;
  } else if (imu_twim->EVENTS_STOPPED) {
    dma_done = true;
  }

  imu_twim->EVENTS_STOPPED  = 0;
  imu_twim->EVENTS_LASTTX   = 0;
  imu_twim->EVENTS_LASTRX   = 0;
}

// ==================== Helpers: Print FIFO bytes ============================
void print_fifo_bytes(const uint8_t *buf, size_t len) {
  Serial.print("FIFO bytes: ");
  for (size_t i = 0; i < len; ++i) {
    Serial.printf("%02X ", buf[i]);
  }
  Serial.println();
}

// ==================== Helpers: Print TWIM status ===========================
void print_twim_status() {
  if (!imu_twim) {
    Serial.println("TWIM status: imu_twim is NULL.");
    return;
  }

  NRF_TWIM_Type *twim = imu_twim;

  Serial.println("---- IMU TWIM Registers ----");
  Serial.print("ENABLE: ");            Serial.println(twim->ENABLE);
  Serial.print("ADDRESS: 0x");         Serial.println(twim->ADDRESS, HEX);
  Serial.print("FREQUENCY: 0x");       Serial.println(twim->FREQUENCY, HEX);
  Serial.print("EVENTS_TXSTARTED: ");  Serial.println(twim->EVENTS_TXSTARTED);
  Serial.print("EVENTS_RXSTARTED: ");  Serial.println(twim->EVENTS_RXSTARTED);
  Serial.print("EVENTS_LASTTX: ");     Serial.println(twim->EVENTS_LASTTX);
  Serial.print("EVENTS_LASTRX: ");     Serial.println(twim->EVENTS_LASTRX);
  Serial.print("EVENTS_STOPPED: ");    Serial.println(twim->EVENTS_STOPPED);
  Serial.print("EVENTS_ERROR: ");      Serial.println(twim->EVENTS_ERROR);
  Serial.print("ERRORSRC: ");          Serial.println(twim->ERRORSRC);

  Serial.print("RXD.PTR: 0x");         Serial.println((uint32_t)twim->RXD.PTR, HEX);
  Serial.print("RXD.MAXCNT: ");        Serial.println(twim->RXD.MAXCNT);
  Serial.print("TXD.PTR: 0x");         Serial.println((uint32_t)twim->TXD.PTR, HEX);
  Serial.print("TXD.MAXCNT: ");        Serial.println(twim->TXD.MAXCNT);

  Serial.println("----------------------------");
}

// ==================== setup() ==============================================
void setup() {
  Serial.begin(115200);
  while (!Serial) { delay(10); }

  Serial.println();
  Serial.println("=================================================");
  Serial.println(" XIAO nRF52840 Sense - LSM6DS3 FIFO DMA Demo (Auto TWIM)");
  Serial.println(" Using EasyDMA on the same TWIM as the IMU library");
  Serial.print  (" Firmware: "); Serial.println(FW_REV);
  Serial.println(" MCU: nRF52840 @ 64 MHz");
  Serial.println(" Board: Seeed XIAO nRF52840 Sense");
  Serial.println("=================================================");

#ifdef IMU_POWER_PIN
  pinMode(IMU_POWER_PIN, OUTPUT);
  digitalWrite(IMU_POWER_PIN, HIGH);
  Serial.println("IMU power pin enabled (PIN_LSM6DS3TR_C_POWER).");
#endif

  Serial.println("Initializing LSM6DS3 via library...");
  if (myIMU.begin() != 0) {
    Serial.println("ERROR: Failed to initialize LSM6DS3!");
    while (true) {
      delay(500);
    }
  }
  Serial.println("LSM6DS3 initialized OK.");

  init_IMU_fifo();
  Serial.print("Configuring FIFO...");
  myIMU.fifoBegin();
  Serial.println("Done!");

  Serial.print("Clearing FIFO...");
  myIMU.fifoClear();
  Serial.println("Done!");

  // After the library has configured the bus, detect which TWIM is used.
  detect_imu_twim();
  init_twim_for_dma();

  Serial.println("Setup complete. Starting main loop...");
}

// ==================== loop() ===============================================
void loop() {
  dma_read_fifo_polling(dma_buffer, FIFO_READ_SIZE);

  Serial.print("dma_done: ");  Serial.println(dma_done);
  Serial.print("dma_error: "); Serial.println(dma_error);
  if (dma_error) {
    Serial.print("dma_error_src: 0x");
    Serial.println(dma_error_src, HEX);
  }

  print_fifo_bytes(dma_buffer, FIFO_READ_SIZE);
  print_twim_status();

  Serial.println();
  delay(200);
}

I am using Arduino IDE 2.3.6. I dont find the “compiler output” in the debug window. the serial output as below.


=================================================
 XIAO nRF52840 Sense - LSM6DS3 FIFO DMA Demo (Auto TWIM)
 Using EasyDMA on the same TWIM as the IMU library
 Firmware: Rev 1.2 - 2025-11-12
 MCU: nRF52840 @ 64 MHz
 Board: Seeed XIAO nRF52840 Sense
=================================================
IMU power pin enabled (PIN_LSM6DS3TR_C_POWER).
Initializing LSM6DS3 via library...
LSM6DS3 initialized OK.
Configuring FIFO...Done!
Clearing FIFO...Done!
Detecting IMU TWIM instance...
  TWIM0 PSEL.SCL: 0xFFFFFFFF
  TWIM0 PSEL.SDA: 0xFFFFFFFF
  TWIM1 PSEL.SCL: 0x1B
  TWIM1 PSEL.SDA: 0x7
Selected IMU TWIM: TWIM1
TWIM + EasyDMA prepared (reusing IMU bus config).
Setup complete. Starting main loop...
Starting DMA read, len = 32
dma_done: 1
dma_error: 0
FIFO bytes: 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 0B A8 
---- IMU TWIM Registers ----
ENABLE: 6
ADDRESS: 0x6A
FREQUENCY: 0x1980000

Hi there,

So, the Verbose check mark is in the preferences; turn that on.

close and open,

HTH

GL :slight_smile: PJ :v:

You are right. The LSM6DS3 version is different. It should be v2.0.4, not v2.0.5. Many Thanks!

1 Like

HI there,

Sweet, :grin: Glad you got it going finally. Those LIBS are all old, and some need updating. But I always look at the dates on Demos & examples and see around when they were created, and sometimes you find the LIB was an updated version and the Older code doesn’t run with it. Hence the “RollBack” was created so you could work with the versions of stuff available back then.

You can Post A new Topic Next! :+1: Since we can’t mark it as the Solution.

HTH

GL :slight_smile: PJ :v: