diff --git a/input_devices/imu/LSM6DSV16X/DM00851721.pdf b/input_devices/imu/LSM6DSV16X/DM00851721.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..ffc542ddaf48d4d9a14bad9bfa382d3be5a2c3b3
Binary files /dev/null and b/input_devices/imu/LSM6DSV16X/DM00851721.pdf differ
diff --git a/input_devices/imu/LSM6DSV16X/hello.LSM6DSV16X.fusion.ino b/input_devices/imu/LSM6DSV16X/hello.LSM6DSV16X.fusion.ino
new file mode 100644
index 0000000000000000000000000000000000000000..35c585f2b5f4ff607ba1ab19513309d6149b007a
--- /dev/null
+++ b/input_devices/imu/LSM6DSV16X/hello.LSM6DSV16X.fusion.ino
@@ -0,0 +1,252 @@
+//
+// hello.LSM6DSV16X.fusion.ino
+//    LSM6DSV16X IMU sensor fusion hello-world
+//
+// Neil Gershenfeld 5/18/25
+//
+// This work may be reproduced, modified, distributed,
+// performed, and displayed for any purpose, but must
+// acknowledge this project. Copyright is retained and
+// must be preserved. The work is provided as is; no
+// warranty is provided, and users accept all liability.
+//
+#include <Wire.h>
+#define address 0x6B
+//
+// I2C register write
+//
+void I2C_write_reg(uint8_t addr,uint8_t reg,uint8_t value) {
+   Wire.beginTransmission(addr);
+   Wire.write(reg);
+   Wire.write(value);
+   if (Wire.endTransmission() != 0)
+      Serial.println("I2C_write_reg failure");
+   }
+//
+// I2C register read
+//
+uint8_t I2C_read_reg(uint8_t addr,uint8_t reg) {
+   Wire.beginTransmission(addr);
+   Wire.write(reg);
+   if (Wire.endTransmission() != 0)
+      Serial.println("I2C_read_reg failure");
+   Wire.requestFrom(0x6B,1);
+   return Wire.read();
+   }
+//
+// Converts uint16_t half-precision number into a uint32_t single-precision number
+// from: https://github.com/stm32duino/LSM6DSV16X/blob/main/src/LSM6DSV16XSensor.cpp
+//
+void npy_halfbits_to_floatbits(uint16_t h,uint32_t *f) {
+  uint16_t h_exp,h_sig;
+  uint32_t f_sgn,f_exp,f_sig;
+  h_exp = (h & 0x7c00u);
+  f_sgn = ((uint32_t)h & 0x8000u) << 16;
+  switch (h_exp) {
+    case 0x0000u: /* 0 or subnormal */
+      h_sig = (h & 0x03ffu);
+      /* Signed zero */
+      if (h_sig == 0) {
+        *f = f_sgn;
+        return;
+        }
+      /* Subnormal */
+      h_sig <<= 1;
+      while ((h_sig & 0x0400u) == 0) {
+        h_sig <<= 1;
+        h_exp++;
+        }
+      f_exp = ((uint32_t)(127-15-h_exp)) << 23;
+      f_sig = ((uint32_t)(h_sig & 0x03ffu)) << 13;
+      *f = f_sgn + f_exp + f_sig;
+      return;
+    case 0x7c00u: /* inf or NaN */
+      /* All-ones exponent and a copy of the significand */
+      *f = f_sgn+0x7f800000u+(((uint32_t)(h & 0x03ffu)) << 13);
+      return;
+    default: /* normalized */
+      /* Just need to adjust the exponent and shift */
+      *f = f_sgn+(((uint32_t)(h & 0x7fffu)+0x1c000u) << 13);
+      return;
+    }
+  }
+//
+// Converts uint16_t half-precision number into a float single-precision number
+// from: https://github.com/stm32duino/LSM6DSV16X/blob/main/src/LSM6DSV16XSensor.cpp
+//
+void npy_half_to_float(uint16_t h,float *f) {
+  union {
+    float ret;
+    uint32_t retbits;
+    } conv;
+  npy_halfbits_to_floatbits(h,&conv.retbits);
+  *f = conv.ret;
+  }
+//
+// Compute quaternions
+// from: https://github.com/stm32duino/LSM6DSV16X/blob/main/src/LSM6DSV16XSensor.cpp
+//
+void sflp2q(float *qx,float *qy,float *qz,float *qw,uint16_t x,uint16_t y,uint16_t z) {
+  float sumsq = 0;
+  npy_half_to_float(x,qx);
+  npy_half_to_float(y,qy);
+  npy_half_to_float(z,qz);
+  sumsq = (*qx)*(*qx)+(*qy)*(*qy)+(*qz)*(*qz);
+  if (sumsq > 1.0f) {
+    float n = sqrtf(sumsq);
+    *qx /= n;
+    *qy /= n;
+    *qz /= n;
+    sumsq = 1.0f;
+    }
+  *qw = sqrtf(1.0f-sumsq);
+  }
+//
+// IMU integer read
+//
+void IMU_read_int(uint8_t *tag,int16_t *x,int16_t *y,int16_t *z) {
+   uint8_t xlo,xhi,ylo,yhi,zlo,zhi;
+   *tag = I2C_read_reg(address,0x78) >> 3; // FIFO_DATA_OUT_TAG
+   xlo = I2C_read_reg(address,0x79); // FIFO_DATA_OUT_X_L
+   xhi = I2C_read_reg(address,0x7A); // FIFO_DATA_OUT_X_H
+   *x = (xhi << 8) | xlo;
+   ylo = I2C_read_reg(address,0x7B); // FIFO_DATA_OUT_Y_L
+   yhi = I2C_read_reg(address,0x7C); // FIFO_DATA_OUT_Y_H
+   *y = (yhi << 8) | ylo;
+   zlo = I2C_read_reg(address,0x7D); // FIFO_DATA_OUT_Z_L
+   zhi = I2C_read_reg(address,0x7E); // FIFO_DATA_OUT_Z_H
+   *z = (zhi << 8) | zlo;
+   }
+//
+// IMU float read
+//
+void IMU_read_quaternion(uint8_t *tag,float *qx,float *qy,float *qz,float *qw) {
+   uint8_t xlo,xhi,ylo,yhi,zlo,zhi;
+   uint16_t ix,iy,iz;
+   *tag = I2C_read_reg(address,0x78) >> 3; // FIFO_DATA_OUT_TAG
+   xlo = I2C_read_reg(address,0x79); // FIFO_DATA_OUT_X_L
+   xhi = I2C_read_reg(address,0x7A); // FIFO_DATA_OUT_X_H
+   ix = (xhi << 8) | xlo;
+   ylo = I2C_read_reg(address,0x7B); // FIFO_DATA_OUT_Y_L
+   yhi = I2C_read_reg(address,0x7C); // FIFO_DATA_OUT_Y_H
+   iy = (yhi << 8) | ylo;
+   zlo = I2C_read_reg(address,0x7D); // FIFO_DATA_OUT_Z_L
+   zhi = I2C_read_reg(address,0x7E); // FIFO_DATA_OUT_Z_H
+   iz = (zhi << 8) | zlo;
+   sflp2q(qx,qy,qz,qw,ix,iy,iz);
+   }
+//
+// setup
+//
+void setup() {
+   //
+   // start serial
+   //
+   Serial.begin(115200);
+   delay(1000);
+   //
+   // start I2C
+   //
+   Wire.begin();
+   Wire.setClock(1000000);
+   // 
+   // initialize gyro and accelerometer
+   //
+   I2C_write_reg(address,0x10,0x06); // CTRL1: accel on, 120 Hz, high performance
+   I2C_write_reg(address,0x11,0x06); // CTRL2: gyro on, 120 Hz, high performance
+   I2C_write_reg(address,0x12,0x44); // CTRL3: block data update, auto inc addresses
+   I2C_write_reg(address,0x13,0x00); // CTRL4: for interrupts
+   I2C_write_reg(address,0x14,0x00); // CTRL5: for interrupts
+   I2C_write_reg(address,0x15,0x04); // CTRL6: gyro 2000 dps
+   I2C_write_reg(address,0x16,0x00); // CTRL7: analog hub
+   I2C_write_reg(address,0x17,0x01); // CTRL8: accel 4g
+   I2C_write_reg(address,0x18,0x00); // CTRL9: accel filter
+   I2C_write_reg(address,0x19,0x00); // CTRL10: self test
+   //
+   // initialize FIFO
+   //
+   I2C_write_reg(address,0x01,0x80);  // FUNC_CFG_ACCESS: enable embedded access
+   I2C_write_reg(address,0x04,0b00000010); // EMB_FUNC_EN_A: SFLP_GAME_EN
+   I2C_write_reg(address,0x44,0b00010010); // EMB_FUNC_FIFO_EN_A: 0b00010000 gravity 0b00000010 game
+   I2C_write_reg(address,0x01,0x00);  // FUNC_CFG_ACCESS: exit embedded access
+   I2C_write_reg(address,0x05,0); // EMB_FUNC_EN_B: embedded functions
+   I2C_write_reg(address,0x07,0); // FIFO_CTRL1: watermark
+   I2C_write_reg(address,0x08,0); // FIFO_CTRL2: modes
+   I2C_write_reg(address,0x09,0b01100110); // FIFO_CTRL3:  0 accel gyro not batched 0b01100110 120 Hz gyro and accel batch
+   I2C_write_reg(address,0x0A,0b00000110); // FIFO_CTRL4: continuous mode
+   I2C_write_reg(address,0x5E,0b01011011); // SFLP_ODR: 120 Hz sensor fusion output data rate
+   }
+//
+// main loop
+//
+void loop() {
+   uint8_t tag;
+   int16_t x,y,z;
+   float qx,qy,qz,qw;
+   //
+   // read accelerometer
+   //
+   while (true) {
+      IMU_read_int(&tag,&x,&y,&z);
+      if (tag == 2) {
+         Serial.print("ax ay az: ");
+         Serial.print(x);
+         Serial.print(",");
+         Serial.print(y);
+         Serial.print(",");
+         Serial.print(z);
+         Serial.println("");
+         break;
+         }
+      }
+   //
+   // read gyro
+   //
+   while (true) {
+      IMU_read_int(&tag,&x,&y,&z);
+      if (tag == 1) {
+         Serial.print("rx ry rz: ");
+         Serial.print(x);
+         Serial.print(",");
+         Serial.print(y);
+         Serial.print(",");
+         Serial.print(z);
+         Serial.println("");
+         break;
+         }
+      }
+   //
+   // read gravity
+   //
+   while (true) {
+      IMU_read_int(&tag,&x,&y,&z);
+      if (tag == 0x17) {
+         Serial.print("gx gy gz: ");
+         Serial.print(x);
+         Serial.print(",");
+         Serial.print(y);
+         Serial.print(",");
+         Serial.print(z);
+         Serial.println("");
+         break;
+         }
+      }
+   //
+   // read quaternions
+   //
+   while (true) {
+      IMU_read_quaternion(&tag,&qx,&qy,&qz,&qw);
+      if (tag == 0x13) {
+         Serial.print("qw qx qy qz: ");
+         Serial.print(qw);
+         Serial.print(",");
+         Serial.print(qx);
+         Serial.print(",");
+         Serial.print(qy);
+         Serial.print(",");
+         Serial.print(qz);
+         Serial.println("");
+         break;
+         }
+      }
+   }
diff --git a/input_devices/imu/LSM6DSV16X/hello.LSM6DSV16X.fusion.mp4 b/input_devices/imu/LSM6DSV16X/hello.LSM6DSV16X.fusion.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..b3e74462f86954630f6a4e40658544276f6ae500
Binary files /dev/null and b/input_devices/imu/LSM6DSV16X/hello.LSM6DSV16X.fusion.mp4 differ
diff --git a/input_devices/imu/LSM6DSV16X/hello.LSM6DSV16X.fusion.py b/input_devices/imu/LSM6DSV16X/hello.LSM6DSV16X.fusion.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea567b2607399c7c79b069836e9a9f640a966c9a
--- /dev/null
+++ b/input_devices/imu/LSM6DSV16X/hello.LSM6DSV16X.fusion.py
@@ -0,0 +1,153 @@
+#
+# hello.LSM6DSV16X.fusion.py
+#    LSM6DSV16X IMU hello-world
+#
+# Neil Gershenfeld 5/19/25
+#
+# This work may be reproduced, modified, distributed,
+# performed, and displayed for any purpose, but must
+# acknowledge this project. Copyright is retained and
+# must be preserved. The work is provided as is; no
+# warranty is provided, and users accept all liability.
+#
+from tkinter import *
+import serial,sys,math
+#
+WIDTH = 750 # window width
+NBARS = 12 # number of bar graphs
+BARHEIGHT = 70 # bar graph height
+BORDER = 10 # bar graph border
+HEIGHT = NBARS*BARHEIGHT # window height
+FONTSIZE = 28 # font size
+EPS = 0.2 # smoothing filter
+filter = {}
+#
+def idle(parent,canvas):
+   #
+   # read a line
+   #
+   line = serialport.readline().decode('utf-8').rstrip()
+   #
+   # parse and plot
+   #
+   line = line.split(':')
+   match line[0]:
+      case "ax ay az":
+         try:
+            line = line[1].split(',')
+            line = list(map(int,line))
+            update_bar_graph("ax",line[0],-10000,10000)
+            update_bar_graph("ay",line[1],-10000,10000)
+            update_bar_graph("az",line[2],-10000,10000)
+         except:
+            pass
+      case "gx gy gz":
+         try:
+            line = line[1].split(',')
+            line = list(map(int,line))
+            gx = line[0]
+            gy = line[1]
+            gz = line[2]
+            norm = math.sqrt(gx*gx+gy*gy+gz*gz)
+            gx /= norm
+            gy /= norm
+            gz /= norm
+            update_bar_graph("gx",gx,-1,1)
+            update_bar_graph("gy",gy,-1,1)
+            update_bar_graph("gz",gz,-1,1)
+         except:
+            pass
+      case "rx ry rz":
+         try:
+            line = line[1].split(',')
+            line = list(map(int,line))
+            update_bar_graph("rx",line[0],-10000,10000)
+            update_bar_graph("ry",line[1],-10000,10000)
+            update_bar_graph("rz",line[2],-10000,10000)
+         except:
+            pass
+      case "qw qx qy qz":
+         try:
+            line = line[1].split(',')
+            line = list(map(float,line))
+            qw = line[0]
+            qx = line[1]
+            qy = line[2]
+            qz = line[3]
+            roll = math.atan2(
+               2*qy*qw-2*qx*qz,
+               1-2*qy*qy-2*qz*qz)
+            yaw = math.asin(
+               2*qx*qy+2*qz*qw)
+            pitch = math.atan2(
+               2*qx*qw-2*qy*qz,
+               1-2*qx*qx-2*qz*qz)
+            yaw = 180*yaw/math.pi
+            pitch = 180*pitch/math.pi
+            roll = 180*roll/math.pi
+            update_bar_graph("yaw",yaw,-90,90)
+            update_bar_graph("pitch",pitch,-180,180)
+            update_bar_graph("roll",roll,-180,180)
+         except:
+            pass
+   #
+   # update canvas
+   #
+   canvas.update()
+   parent.after_idle(idle,parent,canvas)
+#
+def update_bar_graph(label,value,vmin,vmax):
+   filt = EPS*value+(1-EPS)*filter[label]
+   filter[label] = filt
+   canvas.itemconfigure(label,text=f"{label}: {filt:.1f}")
+   tuple = canvas.coords(label+'r')
+   tuple[2] = BORDER+(WIDTH-2*BORDER)*(filt-vmin)/(vmax-vmin)
+   canvas.coords(label+'r',tuple)
+#
+def create_bar_graph(label,n):
+   canvas.create_rectangle(BORDER,n*BARHEIGHT+BORDER,WIDTH-BORDER,(n+1)*BARHEIGHT-BORDER,tags=label+'b', fill='#0000b0')
+   canvas.create_rectangle(BORDER,n*BARHEIGHT+BORDER,BORDER,(n+1)*BARHEIGHT-BORDER,tags=label+'r', fill='#b00000')
+   canvas.create_text(WIDTH/2,(n+0.5)*BARHEIGHT,text=label,font=("Helvetica",FONTSIZE),tags=label,fill="#FFFFFF",anchor=CENTER)
+   filter[label] = 0
+#
+#  check command line arguments
+#
+if (len(sys.argv) != 2):
+   print("command line: hello.LSM6DSV16X.fusion.py serial_port")
+   sys.exit()
+port = sys.argv[1]
+#
+# open serial port
+#
+serialport = serial.Serial(port,115200)
+#
+# set up GUI
+#
+root = Tk()
+root.title('hello.LSM6DSV16X.fusion.py (q to exit)')
+root.bind('q','exit')
+canvas = Canvas(root,width=WIDTH,height=HEIGHT,background='white')
+#
+create_bar_graph("ax",0)
+create_bar_graph("ay",1)
+create_bar_graph("az",2)
+#
+create_bar_graph("gx",3)
+create_bar_graph("gy",4)
+create_bar_graph("gz",5)
+#
+create_bar_graph("rx",6)
+create_bar_graph("ry",7)
+create_bar_graph("rz",8)
+#
+create_bar_graph("roll",9)
+create_bar_graph("pitch",10)
+create_bar_graph("yaw",11)
+#
+canvas.pack()
+#
+# start idle loop
+#
+root.after(100,idle,root,canvas)
+root.mainloop()
+
diff --git a/input_devices/imu/LSM6DSV16X/hello.LSM6DSV16X.raw.ino b/input_devices/imu/LSM6DSV16X/hello.LSM6DSV16X.raw.ino
new file mode 100644
index 0000000000000000000000000000000000000000..14c875a8d98e47e0d6ef2eb87c618d553d2cba54
--- /dev/null
+++ b/input_devices/imu/LSM6DSV16X/hello.LSM6DSV16X.raw.ino
@@ -0,0 +1,116 @@
+//
+// hello.LSM6DSV16X.raw.ino
+//
+// LSM6DSV16X IMU raw data hello-world
+//
+// Neil Gershenfeld 5/18/25
+//
+// This work may be reproduced, modified, distributed,
+// performed, and displayed for any purpose, but must
+// acknowledge this project. Copyright is retained and
+// must be preserved. The work is provided as is; no
+// warranty is provided, and users accept all liability.
+//
+
+#include <Wire.h>
+
+#define address 0x6B
+
+void I2C_write_reg(uint8_t addr,uint8_t reg,uint8_t value) {
+   Wire.beginTransmission(addr);
+   Wire.write(reg);
+   Wire.write(value);
+   if (Wire.endTransmission() != 0)
+      Serial.println("I2C_write_reg failure");
+   }
+
+uint8_t I2C_read_reg(uint8_t addr,uint8_t reg) {
+   Wire.beginTransmission(addr);
+   Wire.write(reg);
+   if (Wire.endTransmission() != 0)
+      Serial.println("I2C_read_reg failure");
+   Wire.requestFrom(0x6B,1);
+   return Wire.read();
+   }
+
+void setup() {
+   //
+   // start serial
+   //
+   Serial.begin(115200);
+   delay(1000);
+   //
+   // start I2C
+   //
+   Wire.begin();
+   Wire.setClock(1000000);
+   //
+   // initialize LSM6DSV16X
+   //
+   Serial.println("initialize LSM6DSV16X");
+   I2C_write_reg(address,0x10,0x07); // CTRL1 accel on, 240 Hz, high performance
+   I2C_write_reg(address,0x11,0x07); // CTRL2 gyro on, 240 Hz, high performance
+   I2C_write_reg(address,0x12,0x44); // CTRL3 block data update, auto inc addresses
+   I2C_write_reg(address,0x13,0x00); // CTRL4 for interrupts
+   I2C_write_reg(address,0x14,0x00); // CTRL5 for interrupts
+   I2C_write_reg(address,0x15,0x04); // CTRL6 gyro 2000 dps
+   I2C_write_reg(address,0x16,0x00); // CTRL7 analog hub
+   I2C_write_reg(address,0x17,0x01); // CTRL8 accel 4g
+   I2C_write_reg(address,0x18,0x00); // CTRL9 accel filter
+   I2C_write_reg(address,0x19,0x00); // CTRL10 self test
+   }
+
+void loop() {
+   uint8_t ret,lo,hi;
+   int16_t ax,ay,az,rx,ry,rz;
+   //
+   // check status register
+   //
+   ret = I2C_read_reg(address,0x1E); // STATUS_REG
+   //
+   // gyro data?
+   //
+   if (ret & 2) { // GDA
+      Serial.print("rx ry rz: ");
+      lo = I2C_read_reg(address,0x22); //OUTX_L_G
+      hi = I2C_read_reg(address,0x23); //OUTX_H_G
+      rx = (hi << 8) | lo;
+      Serial.print(rx);
+      Serial.print(" ");
+      lo = I2C_read_reg(address,0x24); //OUTY_L_G
+      hi = I2C_read_reg(address,0x25); //OUTY_H_G
+      ry = (hi << 8) | lo;
+      Serial.print(ry);
+      Serial.print(" ");
+      lo = I2C_read_reg(address,0x26); //OUTZ_L_G
+      hi = I2C_read_reg(address,0x27); //OUTZ_H_G
+      rz = (hi << 8) | lo;
+      Serial.print(rz);
+      Serial.println(" ");
+      }
+   //
+   // accelerometer data?
+   //
+   if (ret & 1) { // XLDA
+      Serial.print("ax ay az: ");
+      lo = I2C_read_reg(address,0x28); //OUTX_L_A
+      hi = I2C_read_reg(address,0x29); //OUTX_H_A
+      ax = (hi << 8) | lo;
+      Serial.print(ax);
+      Serial.print(" ");
+      lo = I2C_read_reg(address,0x2A); //OUTY_L_A
+      hi = I2C_read_reg(address,0x2B); //OUTY_H_A
+      ay = (hi << 8) | lo;
+      Serial.print(ay);
+      Serial.print(" ");
+      lo = I2C_read_reg(address,0x2C); //OUTZ_L_A
+      hi = I2C_read_reg(address,0x2D); //OUTZ_H_A
+      az = (hi << 8) | lo;
+      Serial.print(az);
+      Serial.println("");
+      }
+   //
+   // delay
+   //
+   delay(100);
+   }
diff --git a/input_devices/index.html b/input_devices/index.html
index 4393162240d0416cbed3302fec5ef2e8ffc6440a..81a3abfd9c55a9e29ddbe21eae2ec0e81b1c5512 100755
--- a/input_devices/index.html
+++ b/input_devices/index.html
@@ -143,6 +143,7 @@
       <a href=https://www.st.com/en/mems-and-sensors/lsm6dsv16x.html>LSM6DSV16X</a>
          hello.LSM6DSV16X.RP2040 <a href=imu/LSM6DSV16X/hello.LSM6DSV16X.RP2040.kicad_pro>pro</a> <a href=imu/LSM6DSV16X/hello.LSM6DSV16X.RP2040.kicad_sch>sch</a> <a href=imu/LSM6DSV16X/hello.LSM6DSV16X.RP2040.kicad_pcb>pcb</a> <a href=imu/LSM6DSV16X/hello.LSM6DSV16X.RP2040.png>board</a> <a href=imu/LSM6DSV16X/hello.LSM6DSV16X.RP2040.jpg>components</a> <a href=imu/LSM6DSV16X/production/traces.png>traces</a> <a href=imu/LSM6DSV16X/production/interior.png>interior</a>
             <a href=https://github.com/stm32duino/LSM6DSV16X>library</a> <a href=imu/LSM6DSV16X/hello.LSM6DSV16X.RP2040.mp4>video</a>
+            <a href=imu/LSM6DSV16X/hello.LSM6DSV16X.fusion.ino>hello.LSM6DSV16X.fusion.ino</a> <a href=imu/LSM6DSV16X/hello.LSM6DSV16X.fusion.mp4>video</a>
    9 axis accelerometer+gyroscope+magnetometer
       <a href=https://www.digikey.com/en/products/detail/ceva-technologies-inc/BNO085/9445940>BNO085</a> <a href=https://www.adafruit.com/product/4754>module</a> <a href=https://www.digikey.com/en/products/detail/ceva-technologies-inc/BNO086/14114190>BNO086</a>
          <a href=https://learn.adafruit.com/adafruit-9-dof-orientation-imu-fusion-breakout-bno085?view=all>RVC serial</a>