IMU Wake-on-Motion
XIAO nRF54LM20A Sense · accelerometro → sveglia
L'IMU LSM6DS3TR-C rileva il movimento e alza INT1 per svegliare l'MCU dal deep sleep. Mostra le accelerazioni live + stato FERMO / MOVIMENTO sull'OLED Expansion, con contatore eventi. È la base del wake-on-motion del Visla Tag: dormire a ~4.76 µA e risvegliarsi solo quando l'oggetto si muove.
Seeed XIAO nRF54LM20A Sense Arduino · core nrf54l15clean
Componenti usati
nRF54LM20A
MCU · Cortex-M33 128 MHz
Nordic Semiconductor
LSM6DS3TR-C
IMU 6 assi (accel + gyro)
STMicroelectronics
nPM1300
PMIC · rail IMU via LDO1 3V3
Nordic Semiconductor
SSD1306
OLED 128×64 (Expansion)
—
imu_wake_motion.ino
216 righe GitHub
/*
* VISLA — XIAO nRF54LM20A Sense · IMU wake-on-motion + OLED Expansion
*
* Dimostra la sveglia-al-movimento del Tag: l'IMU LSM6DS3TR-C rileva
* il movimento e alza INT1; l'OLED mostra accelerazioni live, stato
* FERMO/MOVIMENTO e un contatore di eventi. LED RGB: verde=fermo, rosso=motion.
*
* Hardware (dal core nrf54l15clean, variante xiao_nrf54lm20b):
* OLED Expansion -> Wire (D4/D5, P1.03/P1.07) SSD1306 0x3C
* IMU LSM6DS3TR-C-> Wire1 (P0.08 SDA / P0.07 SCL) addr 0x6A
* IMU INT1 -> PIN_IMU_INT (P0.06) [pin wake]
* IMU CS -> PIN_IMU_CS (P3.12) HIGH = modo I2C
* Rail IMU&MIC 3V3 -> nPM1300 LDO1 (va acceso via PMIC)
*/
#include <Arduino.h>
#include <Wire.h>
#include <U8g2lib.h>
#include "npm1300.h"
U8G2_SSD1306_128X64_NONAME_F_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE); // Wire (D4/D5)
// ---- LSM6DS3TR-C ----
static uint8_t IMU_ADDR = 0x6A;
#define REG_WHO_AM_I 0x0F
#define REG_CTRL1_XL 0x10
#define REG_CTRL3_C 0x12
#define REG_WAKE_SRC 0x1B
#define REG_OUTX_L_XL 0x28
#define REG_TAP_CFG 0x58
#define REG_WAKE_THS 0x5B
#define REG_WAKE_DUR 0x5C
#define REG_MD1_CFG 0x5E
static void imuW(uint8_t reg, uint8_t val) {
Wire1.beginTransmission(IMU_ADDR); Wire1.write(reg); Wire1.write(val); Wire1.endTransmission();
}
static uint8_t imuR(uint8_t reg) {
Wire1.beginTransmission(IMU_ADDR); Wire1.write(reg); Wire1.endTransmission(false);
Wire1.requestFrom((int)IMU_ADDR, 1); return Wire1.available() ? Wire1.read() : 0;
}
static void imuReadN(uint8_t reg, uint8_t *buf, uint8_t n) {
Wire1.beginTransmission(IMU_ADDR); Wire1.write(reg); Wire1.endTransmission(false);
Wire1.requestFrom((int)IMU_ADDR, (int)n);
for (uint8_t i = 0; i < n && Wire1.available(); i++) buf[i] = Wire1.read();
}
static bool imuOk = false;
static uint8_t whoami = 0;
static float ax = 0, ay = 0, az = 0;
static uint32_t motionCount = 0;
static uint32_t lastMotionMs = 0;
static bool moving = false;
static int battPct() {
int32_t mv = npm1300_read_vbat_mv();
if (mv <= 0) return -1;
int p = (mv - 3300) * 100 / (4200 - 3300);
return p < 0 ? 0 : (p > 100 ? 100 : p);
}
static bool imuBegin() {
// CS HIGH -> forza modo I2C sull'LSM6DS3TR-C
pinMode(PIN_IMU_CS, OUTPUT); digitalWrite(PIN_IMU_CS, HIGH);
pinMode(PIN_IMU_INT, INPUT);
Wire1.begin();
Wire1.setClock(100000);
// scan bus IMU (Wire1) per diagnostica
Serial.println("[imu] scan Wire1 (P0.08/P0.07):");
int found = 0;
for (uint8_t a = 1; a < 127; a++) {
Wire1.beginTransmission(a);
if (Wire1.endTransmission() == 0) { Serial.printf(" trovato 0x%02X\n", a); found++; }
}
Serial.printf("[imu] device trovati: %d\n", found);
// prova 0x6A poi 0x6B
for (uint8_t a = 0x6A; a <= 0x6B; a++) {
IMU_ADDR = a;
whoami = imuR(REG_WHO_AM_I);
Serial.printf("[imu] WHO_AM_I @0x%02X = 0x%02X\n", a, whoami);
if (whoami == 0x6A) break; // WHO_AM_I dell'LSM6DS3TR-C = 0x6A
}
if (whoami != 0x6A) return false;
imuW(REG_CTRL1_XL, 0x50); // accel ODR 208 Hz, ±2 g
imuW(REG_CTRL3_C, 0x44); // BDU + IF_INC (auto-increment registri)
// wake-up (activity) detection -> INT1
imuW(REG_TAP_CFG, 0x90); // INTERRUPTS_ENABLE + SLOPE_FDS (filtro HP)
imuW(REG_WAKE_THS, 0x02); // soglia (≈ 2 * 2g/64 ≈ 62 mg): sensibile
imuW(REG_WAKE_DUR, 0x00); // reazione immediata
imuW(REG_MD1_CFG, 0x20); // instrada WU su INT1
return true;
}
static void imuUpdate() {
uint8_t b[6];
imuReadN(REG_OUTX_L_XL, b, 6);
int16_t rx = (int16_t)(b[0] | (b[1] << 8));
int16_t ry = (int16_t)(b[2] | (b[3] << 8));
int16_t rz = (int16_t)(b[4] | (b[5] << 8));
const float s = 0.000061f; // ±2g -> 0.061 mg/LSB
ax = rx * s; ay = ry * s; az = rz * s;
// evento wake: pin INT1 alto O bit WU_IA nel WAKE_UP_SRC
bool intPin = digitalRead(PIN_IMU_INT);
uint8_t src = imuR(REG_WAKE_SRC); // la lettura pulisce l'evento
bool wu = intPin || (src & 0x08); // bit3 = WU_IA
if (wu) { motionCount++; lastMotionMs = millis(); }
moving = (millis() - lastMotionMs) < 700;
}
static void setLed(bool motion) {
pinMode(LED_RED, OUTPUT); pinMode(LED_GREEN, OUTPUT);
digitalWrite(LED_RED, motion ? LED_STATE_ON : !LED_STATE_ON);
digitalWrite(LED_GREEN, motion ? !LED_STATE_ON : LED_STATE_ON);
}
static void draw() {
oled.clearBuffer();
oled.setFont(u8g2_font_6x12_tf);
oled.drawStr(0, 9, "IMU WAKE-ON-MOTION");
int bp = battPct();
char t[24];
if (bp >= 0) { snprintf(t, sizeof t, "%d%%", bp); oled.drawStr(104, 9, t); }
oled.drawHLine(0, 12, 128);
if (!imuOk) {
oled.setFont(u8g2_font_7x14B_tf);
oled.drawStr(0, 34, "IMU NON TROVATO");
oled.setFont(u8g2_font_6x12_tf);
snprintf(t, sizeof t, "WHO=0x%02X (att.0x6A)", whoami);
oled.drawStr(0, 50, t);
oled.drawStr(0, 62, "rail LDO1? Wire1?");
oled.sendBuffer();
return;
}
oled.setFont(u8g2_font_6x12_tf);
snprintf(t, sizeof t, "ax%+.2f ay%+.2f", ax, ay); oled.drawStr(0, 26, t);
snprintf(t, sizeof t, "az%+.2f g WHO %02X", az, whoami); oled.drawStr(0, 38, t);
snprintf(t, sizeof t, "eventi: %lu", (unsigned long)motionCount); oled.drawStr(0, 62, t);
// stato grande
oled.setFont(u8g2_font_9x15B_tf);
if (moving) oled.drawStr(0, 54, ">> MOVIMENTO <<");
else oled.drawStr(0, 54, "FERMO");
oled.sendBuffer();
}
static bool oledPresent = false;
void setup() {
// 0) LED SUBITO: verde acceso = "vivo, in setup" (prima di qualsiasi I2C che potrebbe bloccare)
pinMode(LED_RED, OUTPUT); pinMode(LED_GREEN, OUTPUT); pinMode(LED_BLUE, OUTPUT);
digitalWrite(LED_RED, !LED_STATE_ON); digitalWrite(LED_BLUE, !LED_STATE_ON);
digitalWrite(LED_GREEN, LED_STATE_ON);
Serial.begin(115200);
delay(300);
// 1) accendi il rail IMU&MIC (nPM1300 LDO1 = 3.3V)
npm1300_begin();
npm1300_imu_mic_power_enable(true);
npm1300_ldo1_set_mode(NPM1300_LDSW_MODE_LDO);
npm1300_ldo1_set_voltage(NPM1300_LDO_VOLTAGE_3V3);
npm1300_ldo1_enable(true);
delay(150); // rail settle + boot IMU
// 2) OLED: verifica presenza su Wire (0x3C) — se assente NON inizializzo (evita hang)
Wire.begin();
Wire.setClock(400000);
Wire.beginTransmission(0x3C);
oledPresent = (Wire.endTransmission() == 0);
if (oledPresent) {
oled.setBusClock(400000);
oled.begin();
oled.clearBuffer();
oled.setFont(u8g2_font_6x12_tf);
oled.drawStr(0, 20, "VISLA Tag");
oled.drawStr(0, 34, "IMU wake test...");
oled.sendBuffer();
}
imuOk = imuBegin();
lastMotionMs = millis();
}
void loop() {
if (imuOk) imuUpdate();
setLed(moving);
if (oledPresent) draw();
else {
// niente OLED: stato via LED — rosso se movimento, verde-heartbeat se fermo, blu-blink se IMU KO
static uint32_t t = 0; static bool on = false;
if (millis() - t > 250) { t = millis(); on = !on;
if (!imuOk) { digitalWrite(LED_BLUE, on ? LED_STATE_ON : !LED_STATE_ON); }
else if (!moving){ digitalWrite(LED_GREEN, on ? LED_STATE_ON : !LED_STATE_ON); }
}
}
static uint32_t lastDbg = 0;
if (millis() - lastDbg > 1500) {
lastDbg = millis();
uint8_t save = IMU_ADDR;
IMU_ADDR = 0x6A; uint8_t w6a = imuR(REG_WHO_AM_I);
IMU_ADDR = 0x6B; uint8_t w6b = imuR(REG_WHO_AM_I);
IMU_ADDR = save;
int found = 0;
for (uint8_t a = 1; a < 127; a++) { Wire1.beginTransmission(a); if (Wire1.endTransmission() == 0) found++; }
Serial.printf("[dbg] pmic=%d ldo1=%d vbat=%ld who6A=%02X who6B=%02X wire1_dev=%d imuOk=%d cs=%d int=%d\n",
npm1300_is_present(), npm1300_ldo1_is_enabled(), (long)npm1300_read_vbat_mv(),
w6a, w6b, found, imuOk, digitalRead(PIN_IMU_CS), digitalRead(PIN_IMU_INT));
}
delay(60);
}