From 8c27c432eba915cf05b1317776e4c58766998586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josip=20=C5=A0imun=20Ku=C4=8Di?= Date: Thu, 16 Apr 2026 08:22:48 +0200 Subject: [PATCH 1/6] Add BHI385 micropython module --- Sensors/BHI385/BHI385/bhi385.py | 1223 +++++++++++++++++++++++++++++++ 1 file changed, 1223 insertions(+) create mode 100644 Sensors/BHI385/BHI385/bhi385.py diff --git a/Sensors/BHI385/BHI385/bhi385.py b/Sensors/BHI385/BHI385/bhi385.py new file mode 100644 index 0000000..eb8d376 --- /dev/null +++ b/Sensors/BHI385/BHI385/bhi385.py @@ -0,0 +1,1223 @@ +# FILE: bhi385.py +# AUTHOR: Josip Šimun Kuči @ Soldered +# BRIEF: A MicroPython module for the Bosch BHI385 Smart IMU sensor +# LAST UPDATED: 2026-04-15 + +import struct +import time +from machine import I2C, Pin +from os import uname + +# --------------------------------------------------------------------------- +# I2C addresses (selected by HSDO pin) +# --------------------------------------------------------------------------- +BHI385_I2C_ADDR_LOW = 0x28 # HSDO = GND +BHI385_I2C_ADDR_HIGH = 0x29 # HSDO = VDDIO + +# --------------------------------------------------------------------------- +# Channel registers (DMA channels for bulk transfer) +# --------------------------------------------------------------------------- +BHI385_CH_CMD = 0x00 # Host command input (write-only) +BHI385_CH_FIFO_WU = 0x01 # Wake-up FIFO output (read-only) +BHI385_CH_FIFO_NW = 0x02 # Non-wake-up FIFO output (read-only) +BHI385_CH_STATUS = 0x03 # Status and debug FIFO output (read-only) + +# --------------------------------------------------------------------------- +# Configuration registers +# --------------------------------------------------------------------------- +BHI385_REG_CHIP_CTRL = 0x05 +BHI385_REG_HOST_INTF_CTRL = 0x06 +BHI385_REG_HOST_IRQ_CTRL = 0x07 +BHI385_REG_RESET_REQ = 0x14 +BHI385_REG_HOST_CTRL = 0x16 +BHI385_REG_HOST_STATUS = 0x17 + +# --------------------------------------------------------------------------- +# Identity and status registers +# --------------------------------------------------------------------------- +BHI385_REG_FUSER2_ID = 0x1C # Expected value: 0x89 +BHI385_REG_FUSER2_REV = 0x1D # 0x02 before firmware, 0x03 after +BHI385_REG_ROM_VER_L = 0x1E # ROM version low byte (0x2E) +BHI385_REG_ROM_VER_H = 0x1F # ROM version high byte (0x14) +BHI385_REG_BOOT_STATUS = 0x25 +BHI385_REG_CHIP_ID = 0x2B # Expected value: 0x7C +BHI385_REG_INT_STATUS = 0x2D +BHI385_REG_ERROR_VAL = 0x2E + +# --------------------------------------------------------------------------- +# Expected chip identity values +# --------------------------------------------------------------------------- +BHI385_CHIP_ID_VAL = 0x7C +BHI385_FUSER2_ID_VAL = 0x89 + +# --------------------------------------------------------------------------- +# Boot status register (0x25) bit masks +# --------------------------------------------------------------------------- +BHI385_BOOT_FLASH_DET = 0x01 +BHI385_BOOT_FLASH_VER_DONE = 0x02 +BHI385_BOOT_FLASH_VER_ERR = 0x04 +BHI385_BOOT_NO_FLASH = 0x08 +BHI385_BOOT_HOST_IFACE_RDY = 0x10 # Bootloader or firmware ready for host +BHI385_BOOT_FW_VER_DONE = 0x20 # RAM firmware CRC check passed +BHI385_BOOT_FW_VER_ERR = 0x40 # RAM firmware CRC check failed +BHI385_BOOT_FW_IDLE = 0x80 + +# --------------------------------------------------------------------------- +# Interrupt status register (0x2D) bit masks +# --------------------------------------------------------------------------- +BHI385_INT_ASSERTED = 0x01 # Interrupt is asserted +BHI385_INT_FIFO_WU = 0x02 # Wake-up FIFO data ready +BHI385_INT_FIFO_WU_LAT = 0x04 # Wake-up FIFO latency threshold reached +BHI385_INT_FIFO_NW = 0x08 # Non-wake-up FIFO data ready +BHI385_INT_FIFO_NW_LAT = 0x10 # Non-wake-up FIFO latency threshold reached +BHI385_INT_STATUS_DBG = 0x20 # Status / debug FIFO has data + +# --------------------------------------------------------------------------- +# Host command IDs (written to channel 0x00) +# --------------------------------------------------------------------------- +BHI385_CMD_UPLOAD_FW = 0x0002 +BHI385_CMD_BOOT_FW = 0x0003 +BHI385_CMD_CONFIGURE_SENSOR = 0x000D +BHI385_CMD_CHANGE_RANGE = 0x000E + +# --------------------------------------------------------------------------- +# Virtual sensor IDs +# --------------------------------------------------------------------------- +BHI385_SENSOR_ACCEL_PASSTHROUGH = 1 +BHI385_SENSOR_ACCEL_RAW = 3 # Raw accelerometer (non-wake-up) +BHI385_SENSOR_ACCEL_CORRECTED = 4 # Corrected accelerometer (non-wake-up) +BHI385_SENSOR_GYRO_PASSTHROUGH = 10 +BHI385_SENSOR_GYRO_RAW = 12 # Raw gyroscope (non-wake-up) +BHI385_SENSOR_GYRO_CORRECTED = 13 # Corrected gyroscope (non-wake-up) +BHI385_SENSOR_GAMERV = 37 # Game rotation vector (non-wake-up) +BHI385_SENSOR_STC = 52 # Step counter (non-wake-up) +BHI385_SENSOR_STC_LP = 136 # Step counter Low Power — used by bsxsam_lite firmware +BHI385_SENSOR_MULTI_TAP = 153 # Multi-tap detect (wake-up) +BHI385_SENSOR_WRIST_GEST = 156 # Wrist gesture detect Low Power (wake-up) +BHI385_SENSOR_WRIST_WEAR = 158 # Wrist wear wakeup (wake-up) + +# Parameter page command ID for multi-tap enable configuration +BHI385_PARAM_MULTI_TAP_ENABLE = 0x0D01 + +# Physical sensor control parameter page for wrist gesture detect +# = 0xE00 | physical_sensor_id (56) +BHI385_PARAM_WRIST_GEST_PHY = 0x0E38 + +# HOST_INTERFACE_CTRL register (0x06) bit masks +BHI385_HIF_CTRL_ASYNC_STATUS = 0x80 + +# --------------------------------------------------------------------------- +# FIFO system event IDs +# Total size = 1 (ID byte) + payload bytes shown in comment +# --------------------------------------------------------------------------- +BHI385_FIFO_PAD = 0x00 # Padding (1 byte total) +BHI385_FIFO_TS_SMALL_DLT_WU = 0xF5 # WU small delta timestamp (2 bytes total) +BHI385_FIFO_TS_LARGE_DLT_WU = 0xF6 # WU large delta timestamp (3 bytes total) +BHI385_FIFO_TS_FULL_WU = 0xF7 # WU full timestamp (6 bytes total) +BHI385_FIFO_META_WU = 0xF8 # WU meta event (4 bytes total) +BHI385_FIFO_DEBUG_MSG = 0xFA # Debug message (18 bytes total) +BHI385_FIFO_TS_SMALL_DLT = 0xFB # NW small delta timestamp (2 bytes total) +BHI385_FIFO_TS_LARGE_DLT = 0xFC # NW large delta timestamp (3 bytes total) +BHI385_FIFO_TS_FULL = 0xFD # NW full timestamp (6 bytes total) +BHI385_FIFO_META = 0xFE # NW meta event (4 bytes total) +BHI385_FIFO_FILLER = 0xFF # Filler byte (1 byte total) + +# --------------------------------------------------------------------------- +# Accelerometer sensitivity in LSB/g per dynamic range setting +# --------------------------------------------------------------------------- +BHI385_ACCEL_SENS_4G = 8192.0 +BHI385_ACCEL_SENS_8G = 4096.0 +BHI385_ACCEL_SENS_16G = 2048.0 +BHI385_ACCEL_SENS_32G = 1024.0 + +# --------------------------------------------------------------------------- +# Gyroscope sensitivity in LSB/(deg/s) per full-scale range setting +# --------------------------------------------------------------------------- +BHI385_GYRO_SENS_125DPS = 262.144 +BHI385_GYRO_SENS_250DPS = 131.072 +BHI385_GYRO_SENS_500DPS = 65.536 +BHI385_GYRO_SENS_1000DPS = 32.768 +BHI385_GYRO_SENS_2000DPS = 16.384 + +# --------------------------------------------------------------------------- +# Timing constants (from datasheet) +# --------------------------------------------------------------------------- +BHI385_T_BOOT_BL_MS = 5 # Bootloader ready timeout (max 1.3 ms, use 5 ms) +BHI385_T_BOOT_FW_MS = 500 # Firmware boot timeout (typical 81 ms) +BHI385_T_FW_VER_MS = 5000 # Firmware CRC verify timeout (conservative) + +# --------------------------------------------------------------------------- +# Buffer and chunk sizes +# --------------------------------------------------------------------------- +BHI385_FIFO_BUF_SIZE = 256 # Maximum FIFO read buffer +BHI385_I2C_CHUNK_SIZE = 28 # Max data bytes per I2C write (Wire buffer safe limit) + +# --------------------------------------------------------------------------- +# Accelerometer dynamic range options (in g) +# --------------------------------------------------------------------------- +BHI385_ACCEL_4G = 4 +BHI385_ACCEL_8G = 8 +BHI385_ACCEL_16G = 16 +BHI385_ACCEL_32G = 32 + +# --------------------------------------------------------------------------- +# Gyroscope full-scale range options (in deg/s) +# --------------------------------------------------------------------------- +BHI385_GYRO_125DPS = 125 +BHI385_GYRO_250DPS = 250 +BHI385_GYRO_500DPS = 500 +BHI385_GYRO_1000DPS = 1000 +BHI385_GYRO_2000DPS = 2000 + +# --------------------------------------------------------------------------- +# Wrist gesture identifiers +# --------------------------------------------------------------------------- +BHI385_WRIST_GEST_NONE = 0 # No gesture / unknown +BHI385_WRIST_GEST_SHAKE_JIGGLE = 3 # Wrist shake / jiggle +BHI385_WRIST_GEST_FLICK_IN = 4 # Arm flick in +BHI385_WRIST_GEST_FLICK_OUT = 5 # Arm flick out + +# --------------------------------------------------------------------------- +# Tap type bitmask values +# As an event value: which tap was detected (one bit set at a time). +# As a config mask: OR together tap types to detect. +# --------------------------------------------------------------------------- +BHI385_TAP_NONE = 0 # No tap +BHI385_TAP_SINGLE = 1 # Single tap +BHI385_TAP_DOUBLE = 2 # Double tap +BHI385_TAP_DOUBLE_SINGLE = 3 # Double and single tap +BHI385_TAP_TRIPLE = 4 # Triple tap +BHI385_TAP_TRIPLE_SINGLE = 5 # Triple and single tap +BHI385_TAP_TRIPLE_DOUBLE = 6 # Triple and double tap +BHI385_TAP_ALL = 7 # All tap types enabled + +# --------------------------------------------------------------------------- +# Wrist hand options +# --------------------------------------------------------------------------- +BHI385_WRIST_LEFT = 0 # Device worn on the left wrist (firmware default) +BHI385_WRIST_RIGHT = 1 # Device worn on the right wrist + + +class BHI385: + """ + MicroPython class for the Bosch BHI385 Smart IMU sensor. + Supports accelerometer, gyroscope, game rotation vector, + step counter, wrist gesture detection, and multi-tap detection. + + The BHI385 requires a firmware binary to be uploaded from the host + on every power-on. Call loadFirmware() after begin() before enabling + any virtual sensors. Firmware must be obtained from Bosch Sensortec. + """ + + def __init__(self, i2c=None, address=BHI385_I2C_ADDR_LOW): + """ + Initialize the BHI385 sensor. + + :param i2c: Initialized I2C object + :param address: I2C address of the sensor (default BHI385_I2C_ADDR_LOW = 0x28) + """ + if i2c is not None: + self._i2c = i2c + else: + if uname().sysname in ("esp32", "esp8266", "Soldered Dasduino CONNECTPLUS"): + self._i2c = I2C(0, scl=Pin(22), sda=Pin(21)) + else: + raise Exception("Board not recognized, enter I2C pins manually") + + self._addr = address + self._dbg = False + self._initialized = False + + # Sensor data: accelerometer [x, y, z] in g + self._accel = [0.0, 0.0, 0.0] + # Sensor data: gyroscope [x, y, z] in deg/s + self._gyro = [0.0, 0.0, 0.0] + # Sensor data: quaternion [x, y, z, w, accuracyDeg] + self._quat = [0.0, 0.0, 0.0, 1.0, 0.0] + self._step_count = 0 + self._wrist_gesture = 0 + self._tap_type = 0 + + # Updated flags — set by update(), cleared by clearUpdatedFlags() + self._accel_updated = False + self._gyro_updated = False + self._quat_updated = False + self._step_updated = False + self._wrist_gesture_updated = False + self._tap_updated = False + + # Active virtual sensor IDs + self._accel_sensor_id = BHI385_SENSOR_ACCEL_CORRECTED + self._gyro_sensor_id = BHI385_SENSOR_GYRO_CORRECTED + self._quat_sensor_id = BHI385_SENSOR_GAMERV + self._stc_sensor_id = BHI385_SENSOR_STC_LP + self._wrist_gest_sensor_id = BHI385_SENSOR_WRIST_GEST + self._tap_sensor_id = BHI385_SENSOR_MULTI_TAP + + # Sensitivity divisors (set when enabling each sensor) + self._accel_sens = BHI385_ACCEL_SENS_8G + self._gyro_sens = BHI385_GYRO_SENS_2000DPS + + def begin(self, address=None): + """ + Initialize the BHI385 host interface. + Issues a soft reset, waits for the bootloader, and verifies the chip ID. + Call loadFirmware() after this. + + :param address: Optional I2C address override + :return: True if host interface is ready and chip identity matches + """ + if address is not None: + self._addr = address + + # Issue a host software reset equivalent to power-on reset + self._i2c_write_reg(BHI385_REG_RESET_REQ, 0x01) + time.sleep_ms(10) + + # Configure host interface control: clear all bits including AP_SUSPENDED + self._i2c_write_reg(BHI385_REG_HOST_INTF_CTRL, 0x00) + + # Configure interrupt: active-high, push-pull, level-triggered (polling mode) + self._i2c_write_reg(BHI385_REG_HOST_IRQ_CTRL, 0x00) + + # Poll for bootloader ready (bit 4 of boot status, max hardware time 1.3 ms) + if not self._poll_boot_status(BHI385_BOOT_HOST_IFACE_RDY, BHI385_T_BOOT_BL_MS): + if self._dbg: + print("[BHI385] begin: FAILED: bootloader not ready") + return False + + if self._dbg: + fuser2_id = self._i2c_read_reg(BHI385_REG_FUSER2_ID, 1) + fuser2_rev = self._i2c_read_reg(BHI385_REG_FUSER2_REV, 1) + rom_ver_l = self._i2c_read_reg(BHI385_REG_ROM_VER_L, 1) + rom_ver_h = self._i2c_read_reg(BHI385_REG_ROM_VER_H, 1) + print("[BHI385] Chip ID: 0x{:02X}".format(self.getChipId())) + if fuser2_id: + print("[BHI385] Fuser2 ID: 0x{:02X}".format(fuser2_id[0])) + if fuser2_rev: + print("[BHI385] Fuser2 Rev: 0x{:02X}".format(fuser2_rev[0])) + if rom_ver_h and rom_ver_l: + print("[BHI385] ROM version: 0x{:02X}{:02X}".format(rom_ver_h[0], rom_ver_l[0])) + + if self.getChipId() != BHI385_CHIP_ID_VAL: + if self._dbg: + print("[BHI385] begin: FAILED: chip ID mismatch (got 0x{:02X}, expected 0x{:02X})".format( + self.getChipId(), BHI385_CHIP_ID_VAL)) + return False + + self._initialized = True + return True + + def loadFirmware(self, firmware): + """ + Upload firmware to the BHI385 program RAM and boot it. + Must be called after begin() and before enabling any sensors. + The firmware binary must be obtained from Bosch Sensortec. + + :param firmware: Firmware binary as bytes or bytearray + :return: True if firmware loaded and booted successfully + """ + if not self._initialized: + if self._dbg: + print("[BHI385] loadFirmware: FAILED: begin() was not successful") + return False + + if not firmware or len(firmware) == 0: + if self._dbg: + print("[BHI385] loadFirmware: FAILED: empty firmware") + return False + + fw_len = len(firmware) + + if self._dbg: + print("[BHI385] loadFirmware: firmware size = {} bytes".format(fw_len)) + print("[BHI385] loadFirmware: boot status before upload = 0x{:02X}".format( + self.getBootStatus())) + + # Step 1: send the 4-byte "Upload to Program RAM" command header. + # Format: [CMD_ID_L][CMD_ID_H][WORD_COUNT_L][WORD_COUNT_H] + # WORD_COUNT = ceil(fw_len / 4) — firmware size in 32-bit words, not bytes. + fw_len_rounded = (fw_len + 3) & ~3 + word_count = fw_len_rounded // 4 + header = bytes([ + BHI385_CMD_UPLOAD_FW & 0xFF, + (BHI385_CMD_UPLOAD_FW >> 8) & 0xFF, + word_count & 0xFF, + (word_count >> 8) & 0xFF, + ]) + + if self._dbg: + fw_preview = " ".join("{:02X}".format(b) for b in firmware[:32]) + print("[BHI385] loadFirmware: firmware first 32 bytes: {}".format(fw_preview)) + print("[BHI385] loadFirmware: word count = {} ({} bytes rounded)".format( + word_count, fw_len_rounded)) + print("[BHI385] loadFirmware: [1/5] writing upload command header... ", end="") + + if not self._channel_write(BHI385_CH_CMD, header): + if self._dbg: + print("FAILED") + return False + + if self._dbg: + print("OK") + + # Step 2: upload firmware data in BHI385_I2C_CHUNK_SIZE-byte chunks. + # Each chunk is a separate I2C transaction: [CH_CMD][data...]. + # A 500 µs pause between chunks lets the BHI385 internal DMA drain its + # receive buffer before the next transaction arrives. + if self._dbg: + num_chunks = fw_len // BHI385_I2C_CHUNK_SIZE + 1 + print("[BHI385] loadFirmware: [2/5] uploading {} chunks @ {} bytes each...".format( + num_chunks, BHI385_I2C_CHUNK_SIZE)) + + offset = 0 + report_step = max(fw_len // 10, 1) + next_report = report_step + + while offset < fw_len: + chunk = min(fw_len - offset, BHI385_I2C_CHUNK_SIZE) + try: + self._i2c.writeto_mem(self._addr, BHI385_CH_CMD, + firmware[offset:offset + chunk]) + except OSError as e: + if self._dbg: + print("[BHI385] loadFirmware: [2/5] FAILED at offset {}/{} ({})".format( + offset, fw_len, e)) + return False + + time.sleep_us(500) + offset += chunk + + if self._dbg and offset >= next_report: + print("[BHI385] loadFirmware: [2/5] {}% ({}/{} bytes)".format( + (offset * 100) // fw_len, offset, fw_len)) + next_report += report_step + + if self._dbg: + print("[BHI385] loadFirmware: [2/5] upload complete") + err_now = self._i2c_read_reg(BHI385_REG_ERROR_VAL, 1) + print("[BHI385] loadFirmware: [2/5] post-upload boot status = 0x{:02X}, " + "error = 0x{:02X}".format( + self.getBootStatus(), err_now[0] if err_now else 0)) + + # Step 3: wait for CRC verify result. + # Bit 5 (FW_VER_DONE) or bit 6 (FW_VER_ERR) will be set when the ROM + # bootloader finishes checking the uploaded image. + if self._dbg: + print("[BHI385] loadFirmware: [3/5] waiting for CRC verify " + "(timeout {} ms)...".format(BHI385_T_FW_VER_MS)) + + if not self._poll_boot_status( + BHI385_BOOT_FW_VER_DONE | BHI385_BOOT_FW_VER_ERR, BHI385_T_FW_VER_MS): + if self._dbg: + print("[BHI385] loadFirmware: [3/5] TIMEOUT, " + "boot status = 0x{:02X}".format(self.getBootStatus())) + return False + + boot_st = self.getBootStatus() + if self._dbg: + print("[BHI385] loadFirmware: [3/5] boot status after verify = " + "0x{:02X}".format(boot_st)) + + if boot_st & BHI385_BOOT_FW_VER_ERR: + if self._dbg: + err_val = self._i2c_read_reg(BHI385_REG_ERROR_VAL, 1) + print("[BHI385] loadFirmware: [3/5] FAILED: CRC mismatch, " + "error = 0x{:02X}".format(err_val[0] if err_val else 0)) + return False + + if self._dbg: + print("[BHI385] loadFirmware: [3/5] CRC OK") + + # Step 4: send the "Boot Program RAM" command. + boot_cmd = bytes([ + BHI385_CMD_BOOT_FW & 0xFF, + (BHI385_CMD_BOOT_FW >> 8) & 0xFF, + 0x00, + 0x00, + ]) + + if self._dbg: + print("[BHI385] loadFirmware: [4/5] sending boot command... ", end="") + + if not self._channel_write(BHI385_CH_CMD, boot_cmd): + if self._dbg: + print("FAILED") + return False + + if self._dbg: + print("OK") + + # Step 5: wait for firmware to boot and host interface to become ready. + if self._dbg: + print("[BHI385] loadFirmware: [5/5] waiting for firmware boot " + "(timeout {} ms)...".format(BHI385_T_BOOT_FW_MS)) + + time.sleep_ms(85) # Typical firmware boot time is 81 ms + + if not self._poll_boot_status(BHI385_BOOT_HOST_IFACE_RDY, BHI385_T_BOOT_FW_MS): + if self._dbg: + print("[BHI385] loadFirmware: [5/5] TIMEOUT, " + "boot status = 0x{:02X}".format(self.getBootStatus())) + return False + + if self._dbg: + print("[BHI385] loadFirmware: [5/5] firmware booted. " + "Boot status = 0x{:02X}".format(self.getBootStatus())) + + # Clear HOST_INTERFACE_CTRL (0x06) to release AP_SUSPENDED. While + # AP_SUSPENDED is set the firmware buffers all sensor events internally + # and does not flush them to the FIFO channels. + self._i2c_write_reg(BHI385_REG_HOST_INTF_CTRL, 0x00) + + # Flush FIFOs to discard the initial boot interrupt + self._channel_read(BHI385_CH_FIFO_WU) + self._channel_read(BHI385_CH_FIFO_NW) + self._channel_read(BHI385_CH_STATUS) + + if self._dbg: + print("[BHI385] loadFirmware: SUCCESS") + return True + + def enableAccelerometer(self, rateHz=100.0, range=BHI385_ACCEL_8G): + """ + Enable the accelerometer virtual sensor. + + :param rateHz: Output data rate in Hz (default 100.0) + :param range: Dynamic range — BHI385_ACCEL_4G / 8G / 16G / 32G (default BHI385_ACCEL_8G) + :return: True on success + """ + self._accel_sensor_id = BHI385_SENSOR_ACCEL_CORRECTED + self._accel_sens = self._accel_range_to_sensitivity(range) + return self._configure_sensor(self._accel_sensor_id, rateHz) + + def enableGyroscope(self, rateHz=100.0, range=BHI385_GYRO_2000DPS): + """ + Enable the gyroscope virtual sensor. + + :param rateHz: Output data rate in Hz (default 100.0) + :param range: Full-scale range — BHI385_GYRO_125DPS through 2000DPS + (default BHI385_GYRO_2000DPS) + :return: True on success + """ + self._gyro_sensor_id = BHI385_SENSOR_GYRO_CORRECTED + self._gyro_sens = self._gyro_range_to_sensitivity(range) + return self._configure_sensor(self._gyro_sensor_id, rateHz) + + def enableGameRotationVector(self, rateHz=100.0): + """ + Enable the Game Rotation Vector virtual sensor. + Outputs a normalized quaternion fused from accel + gyro. + Does not use a magnetometer so yaw is relative to power-on orientation. + + :param rateHz: Output data rate in Hz (default 100.0) + :return: True on success + """ + self._quat_sensor_id = BHI385_SENSOR_GAMERV + return self._configure_sensor(self._quat_sensor_id, rateHz) + + def enableStepCounter(self, rateHz=100.0): + """ + Enable the step counter virtual sensor. + Reports the cumulative step count since the sensor was last reset. + + :param rateHz: Update rate in Hz (default 100.0; 1.0 Hz is typical for step counting) + :return: True on success + """ + self._stc_sensor_id = BHI385_SENSOR_STC_LP + return self._configure_sensor(self._stc_sensor_id, rateHz) + + def enableWristGestureDetect(self, rateHz=100.0, hand=BHI385_WRIST_LEFT): + """ + Enable the wrist gesture detect sensor (wake-up). + Detects wrist shake/jiggle and arm flick in/out. + Requires the bsxsam_lite_Klio_cyclic firmware variant. + + :param rateHz: Output rate in Hz (default 100.0) + :param hand: Which wrist — BHI385_WRIST_LEFT (default) or BHI385_WRIST_RIGHT + :return: True on success + """ + self._wrist_gest_sensor_id = BHI385_SENSOR_WRIST_GEST + + if hand != BHI385_WRIST_LEFT: + if not self._set_wrist_gesture_phys_param(hand): + if self._dbg: + print("[BHI385] enableWristGestureDetect: hand config failed") + return False + + return self._configure_sensor(self._wrist_gest_sensor_id, rateHz) + + def enableMultiTapDetect(self, tapMask=BHI385_TAP_ALL, rateHz=100.0): + """ + Enable the multi-tap detect virtual sensor (wake-up). + Reports single, double, and/or triple taps depending on tapMask. + + :param tapMask: Bitmask of tap types to detect — OR together BHI385_TAP_* values + (default BHI385_TAP_ALL) + :param rateHz: Sensor update rate in Hz (default 100.0) + :return: True on success + """ + self._tap_sensor_id = BHI385_SENSOR_MULTI_TAP + + # Write tap-enable configuration to the multi-tap parameter page. + # Payload is 4 bytes (padded from 1): [tapMask][0][0][0] + cfg = bytes([tapMask, 0, 0, 0]) + if not self._send_command(BHI385_PARAM_MULTI_TAP_ENABLE, cfg): + return False + + return self._configure_sensor(self._tap_sensor_id, rateHz) + + def disableAccelerometer(self): + """ + Disable the accelerometer virtual sensor. + + :return: True on success + """ + return self._configure_sensor(self._accel_sensor_id, 0.0) + + def disableGyroscope(self): + """ + Disable the gyroscope virtual sensor. + + :return: True on success + """ + return self._configure_sensor(self._gyro_sensor_id, 0.0) + + def disableGameRotationVector(self): + """ + Disable the Game Rotation Vector sensor. + + :return: True on success + """ + return self._configure_sensor(self._quat_sensor_id, 0.0) + + def disableStepCounter(self): + """ + Disable the step counter sensor. + + :return: True on success + """ + return self._configure_sensor(self._stc_sensor_id, 0.0) + + def disableWristGestureDetect(self): + """ + Disable the wrist gesture detect sensor. + + :return: True on success + """ + return self._configure_sensor(self._wrist_gest_sensor_id, 0.0) + + def disableMultiTapDetect(self): + """ + Disable the multi-tap detect sensor. + + :return: True on success + """ + return self._configure_sensor(self._tap_sensor_id, 0.0) + + def update(self): + """ + Read and parse all pending FIFO data. + Call this regularly in your main loop or from an interrupt handler. + After calling, check accelUpdated() / gyroUpdated() etc. and read the data. + + :return: True (always; indicates the function ran) + """ + self._accel_updated = False + self._gyro_updated = False + self._quat_updated = False + self._step_updated = False + self._wrist_gesture_updated = False + self._tap_updated = False + + self._read_and_parse_fifo(BHI385_CH_FIFO_WU) + self._read_and_parse_fifo(BHI385_CH_FIFO_NW) + + # Drain STATUS FIFO to prevent it from filling up + self._channel_read(BHI385_CH_STATUS) + + return True + + # --------------------------------------------------------------------------- + # Data accessors + # --------------------------------------------------------------------------- + + def getAccelX(self): + """Return last accelerometer X reading in g.""" + return self._accel[0] + + def getAccelY(self): + """Return last accelerometer Y reading in g.""" + return self._accel[1] + + def getAccelZ(self): + """Return last accelerometer Z reading in g.""" + return self._accel[2] + + def getAccelData(self): + """Return last accelerometer reading as (x, y, z) tuple in g.""" + return (self._accel[0], self._accel[1], self._accel[2]) + + def getGyroX(self): + """Return last gyroscope X reading in deg/s.""" + return self._gyro[0] + + def getGyroY(self): + """Return last gyroscope Y reading in deg/s.""" + return self._gyro[1] + + def getGyroZ(self): + """Return last gyroscope Z reading in deg/s.""" + return self._gyro[2] + + def getGyroData(self): + """Return last gyroscope reading as (x, y, z) tuple in deg/s.""" + return (self._gyro[0], self._gyro[1], self._gyro[2]) + + def getQuatX(self): + """Return last quaternion X component (range -1 to +1).""" + return self._quat[0] + + def getQuatY(self): + """Return last quaternion Y component (range -1 to +1).""" + return self._quat[1] + + def getQuatZ(self): + """Return last quaternion Z component (range -1 to +1).""" + return self._quat[2] + + def getQuatW(self): + """Return last quaternion W component (range -1 to +1).""" + return self._quat[3] + + def getQuatAccuracyDeg(self): + """Return estimated heading accuracy in degrees.""" + return self._quat[4] + + def getQuatData(self): + """Return last quaternion reading as (x, y, z, w, accuracyDeg) tuple.""" + return (self._quat[0], self._quat[1], self._quat[2], + self._quat[3], self._quat[4]) + + def getStepCount(self): + """Return cumulative step count.""" + return self._step_count + + def getWristGesture(self): + """Return last detected wrist gesture (see BHI385_WRIST_GEST_* constants).""" + return self._wrist_gesture + + def getTapType(self): + """Return last detected tap type (see BHI385_TAP_* constants).""" + return self._tap_type + + # --------------------------------------------------------------------------- + # Updated flags + # --------------------------------------------------------------------------- + + def accelUpdated(self): + """Returns True if accelerometer data was refreshed in the last update() call.""" + return self._accel_updated + + def gyroUpdated(self): + """Returns True if gyroscope data was refreshed in the last update() call.""" + return self._gyro_updated + + def quatUpdated(self): + """Returns True if quaternion data was refreshed in the last update() call.""" + return self._quat_updated + + def stepUpdated(self): + """Returns True if step count was updated in the last update() call.""" + return self._step_updated + + def wristGestureUpdated(self): + """Returns True if a new wrist gesture was detected in the last update() call.""" + return self._wrist_gesture_updated + + def tapUpdated(self): + """Returns True if a tap was detected in the last update() call.""" + return self._tap_updated + + def clearUpdatedFlags(self): + """Clear all updated flags. Call after processing data from update().""" + self._accel_updated = False + self._gyro_updated = False + self._quat_updated = False + self._step_updated = False + self._wrist_gesture_updated = False + self._tap_updated = False + + # --------------------------------------------------------------------------- + # Status + # --------------------------------------------------------------------------- + + def getBootStatus(self): + """ + Read the boot status register (0x25). + + :return: Raw boot status byte + """ + data = self._i2c_read_reg(BHI385_REG_BOOT_STATUS, 1) + return data[0] if data else 0 + + def getChipId(self): + """ + Read the chip ID register (0x2B). Should return 0x7C. + + :return: Chip ID byte + """ + data = self._i2c_read_reg(BHI385_REG_CHIP_ID, 1) + return data[0] if data else 0 + + def isReady(self): + """Returns True if begin() succeeded and chip identity was verified.""" + return self._initialized + + def enableDebug(self): + """Enable verbose debug output via print(). Call before loadFirmware() to see + step-by-step progress.""" + self._dbg = True + + def disableDebug(self): + """Disable verbose debug output.""" + self._dbg = False + + # --------------------------------------------------------------------------- + # Private: Low-level I2C + # --------------------------------------------------------------------------- + + def _i2c_write_reg(self, reg, val): + """Write a single byte to a configuration register.""" + try: + self._i2c.writeto_mem(self._addr, reg, bytes([val])) + return True + except OSError as e: + if self._dbg: + print("[BHI385] I2C write failed reg 0x{:02X}: {}".format(reg, e)) + return False + + def _i2c_read_reg(self, reg, length=1): + """Read one or more bytes from a configuration register.""" + try: + return self._i2c.readfrom_mem(self._addr, reg, length) + except OSError as e: + if self._dbg: + print("[BHI385] I2C read failed reg 0x{:02X}: {}".format(reg, e)) + return None + + def _channel_write(self, channel, data): + """ + Write data to a BHI385 channel register in BHI385_I2C_CHUNK_SIZE-byte chunks. + Each I2C transaction: START + [channel][data_chunk] + STOP. + The BHI385 channel maintains its write pointer across separate transactions. + + :param channel: Channel register (e.g. BHI385_CH_CMD) + :param data: bytes or bytearray to write + :return: True on success + """ + offset = 0 + length = len(data) + while offset < length: + chunk = min(length - offset, BHI385_I2C_CHUNK_SIZE) + try: + self._i2c.writeto_mem(self._addr, channel, data[offset:offset + chunk]) + except OSError as e: + if self._dbg: + print("[BHI385] channel write failed ch 0x{:02X}: {}".format(channel, e)) + return False + offset += chunk + return True + + def _channel_read(self, channel, max_len=BHI385_FIFO_BUF_SIZE): + """ + Read data from a BHI385 channel register. + + Uses a repeated START (no STOP between the address write and the data read) + as required by the BHI385. Sending a STOP first causes the device to reset + its internal read pointer, making the 2-byte length header come back as 0x0000. + + Protocol: + 1. Write [channel] without STOP + 2. Read 2-byte length header (little-endian, includes STOP) + 3. For each 32-byte chunk: write [channel] without STOP, then read chunk + + :param channel: Channel register (e.g. BHI385_CH_FIFO_NW) + :param max_len: Maximum bytes to return (capped at BHI385_FIFO_BUF_SIZE) + :return: bytes of FIFO payload (length header stripped), or b'' on failure + """ + try: + # Write channel address without STOP → repeated START before the read + self._i2c.writeto(self._addr, bytes([channel]), False) + # Read 2-byte transfer-length header + header = self._i2c.readfrom(self._addr, 2) + except OSError: + return b"" + + data_len = header[0] | (header[1] << 8) + if data_len == 0: + return b"" + + to_read = min(data_len, max_len) + result = bytearray() + offset = 0 + + while offset < to_read: + chunk = min(to_read - offset, 32) + try: + self._i2c.writeto(self._addr, bytes([channel]), False) + chunk_data = self._i2c.readfrom(self._addr, chunk) + result.extend(chunk_data) + except OSError: + break + offset += chunk + + return bytes(result) + + # --------------------------------------------------------------------------- + # Private: Command helpers + # --------------------------------------------------------------------------- + + def _send_command(self, cmd_id, params=None): + """ + Write a generic command packet to channel 0 (BHI385_CH_CMD). + Format: [CMD_ID_L][CMD_ID_H][PADDED_LEN_L][PADDED_LEN_H][PARAMS...] + padded to a 4-byte boundary with 0x00 bytes. + + :param cmd_id: Command identifier (uint16) + :param params: Parameter bytes or bytearray (optional) + :return: True on success + """ + if params is None: + params = b"" + + param_len = len(params) + padded_param_len = (param_len + 3) & ~3 + total_len = 4 + padded_param_len + + pkt = bytearray(total_len) + pkt[0] = cmd_id & 0xFF + pkt[1] = (cmd_id >> 8) & 0xFF + pkt[2] = padded_param_len & 0xFF + pkt[3] = (padded_param_len >> 8) & 0xFF + pkt[4:4 + param_len] = params + + return self._channel_write(BHI385_CH_CMD, pkt) + + def _configure_sensor(self, sensor_id, rate_hz, latency_ms=0): + """ + Send a Configure Sensor command (0x000D). + 8 parameter bytes: [sensor_id (1)] [rate_hz IEEE-754 LE (4)] [latency_ms 24-bit LE (3)] + + :param sensor_id: Virtual sensor ID + :param rate_hz: Desired output rate in Hz (0.0 = disable) + :param latency_ms: Maximum report latency in milliseconds (default 0) + :return: True on success + """ + rate_bytes = struct.pack("> 8) & 0xFF, + (latency_ms >> 16) & 0xFF, + ])) + return self._send_command(BHI385_CMD_CONFIGURE_SENSOR, params) + + def _change_sensor_range(self, sensor_id, range_val): + """ + Send a Change Sensor Dynamic Range command (0x000E). + For accelerometers: range_val in g (4, 8, 16, 32). + For gyroscopes: range_val in deg/s (125, 250, 500, 1000, 2000). + + :param sensor_id: Virtual sensor ID + :param range_val: Full-scale range value + :return: True on success + """ + params = bytes([sensor_id, range_val & 0xFF, (range_val >> 8) & 0xFF]) + return self._send_command(BHI385_CMD_CHANGE_RANGE, params) + + # --------------------------------------------------------------------------- + # Private: Physical sensor parameter access + # --------------------------------------------------------------------------- + + def _set_wrist_gesture_phys_param(self, hand): + """ + Read the wrist gesture physical sensor control parameters, update the + device_pos (hand) field, and write them back. + + Protocol (per Bosch SensorAPI bhi385_phy_sensor_ctrl_param.c): + 1. Switch STATUS channel to sync mode (clear ASYNC_STATUS_CHANNEL bit). + 2. Send read-request command 0x0E38 with payload [0x87, 0, 0, 0]. + 3. Send get-parameter command 0x1E38 (= 0x0E38 | 0x1000), no payload. + 4. Poll INT_STATUS bit 5 for STATUS FIFO ready. + 5. Read 25 bytes from STATUS channel: + [code_L][code_H][remain_L][remain_H][ctrl=0x07][config[0..19]] + 6. Set config[18] (device_pos) to the requested hand value. + 7. Write back via command 0x0E38 with 21-byte payload. + 8. Restore HOST_INTF_CTRL. + + :param hand: 0 = left arm, 1 = right arm + :return: True on success + """ + # Save HOST_INTF_CTRL and clear ASYNC_STATUS_CHANNEL (bit 7) so the STATUS + # channel operates in synchronous parameter-response mode + hif_ctrl_data = self._i2c_read_reg(BHI385_REG_HOST_INTF_CTRL, 1) + hif_ctrl_saved = hif_ctrl_data[0] if hif_ctrl_data else 0 + self._i2c_write_reg(BHI385_REG_HOST_INTF_CTRL, + hif_ctrl_saved & ~BHI385_HIF_CTRL_ASYNC_STATUS) + + # Send read-request: cmd=0x0E38, 4-byte payload = [ctrl_read=0x87, 0, 0, 0] + read_req = bytes([0x38, 0x0E, 0x04, 0x00, 0x87, 0x00, 0x00, 0x00]) + self._channel_write(BHI385_CH_CMD, read_req) + + # Send get-parameter: cmd = 0x0E38 | 0x1000 = 0x1E38, no payload + get_param = bytes([0x38, 0x1E, 0x00, 0x00]) + self._channel_write(BHI385_CH_CMD, get_param) + + # Wait for STATUS FIFO ready (INT_STATUS bit 5) + status_ready = False + t_start = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), t_start) < 200: + int_st = self._i2c_read_reg(BHI385_REG_INT_STATUS, 1) + if int_st and (int_st[0] & BHI385_INT_STATUS_DBG): + status_ready = True + break + time.sleep_ms(1) + + success = False + if status_ready: + # Read 25-byte sync response from STATUS channel with repeated START + try: + self._i2c.writeto(self._addr, bytes([BHI385_CH_STATUS]), False) + resp = bytearray(self._i2c.readfrom(self._addr, 25)) + except OSError: + resp = bytearray(25) + + code = resp[0] | (resp[1] << 8) + remain = resp[2] | (resp[3] << 8) + ctrl_code = resp[4] + + if code == BHI385_PARAM_WRIST_GEST_PHY and remain == 21 and ctrl_code == 0x07: + # resp[5..24] = config[0..19]; device_pos is at config[18] = resp[23] + resp[23] = hand + # Write back: cmd=0x0E38, payload=[ctrl_code=0x07][config[0..19]] = 21 bytes + success = self._send_command(BHI385_PARAM_WRIST_GEST_PHY, + bytes(resp[4:25])) + elif self._dbg: + print("[BHI385] _set_wrist_gesture_phys_param: unexpected response " + "code=0x{:04X} remain={} ctrlCode=0x{:02X}".format( + code, remain, ctrl_code)) + elif self._dbg: + print("[BHI385] _set_wrist_gesture_phys_param: timeout waiting for STATUS FIFO") + + # Restore HOST_INTF_CTRL + self._i2c_write_reg(BHI385_REG_HOST_INTF_CTRL, hif_ctrl_saved) + return success + + # --------------------------------------------------------------------------- + # Private: FIFO parsing + # --------------------------------------------------------------------------- + + def _read_and_parse_fifo(self, channel): + """ + Read one FIFO channel and parse all events it contains. + + :param channel: Channel register to read from + :return: True on a successful channel read (even if 0 bytes) + """ + data = self._channel_read(channel) + if data: + self._parse_fifo_data(data) + return True + + def _parse_fifo_data(self, buf): + """ + Walk the FIFO byte stream and dispatch each event. + + :param buf: bytes containing raw FIFO data + """ + i = 0 + length = len(buf) + while i < length: + consumed = self._parse_event(buf, length, i) + if consumed == 0: + break # Prevent infinite loop on unexpected data + i += consumed + + def _parse_event(self, buf, length, offset): + """ + Parse a single FIFO event starting at offset and return how many bytes + were consumed (including the ID byte). + + :param buf: FIFO data buffer (bytes) + :param length: Total valid length of buf + :param offset: Byte offset of the event ID within buf + :return: Number of bytes consumed by this event + """ + if offset >= length: + return 1 + + event_id = buf[offset] + + # --- Padding / filler bytes --- + if event_id == BHI385_FIFO_PAD or event_id == BHI385_FIFO_FILLER: + return 1 + + # --- WU FIFO system events (0xF5-0xF8) --- + if event_id == BHI385_FIFO_TS_SMALL_DLT_WU: + return 2 # 1 ID + 1 delta byte + if event_id == BHI385_FIFO_TS_LARGE_DLT_WU: + return 3 # 1 ID + 2 delta bytes + if event_id == BHI385_FIFO_TS_FULL_WU: + return 6 # 1 ID + 5 timestamp bytes + if event_id == BHI385_FIFO_META_WU: + return 4 # 1 ID + 3 meta bytes + if event_id == 0xF9: + return 1 # Invalid — skip to avoid loop + if event_id == BHI385_FIFO_DEBUG_MSG: + return 18 # 1 ID + 17 debug bytes + + # --- NW FIFO system events (0xFB-0xFE) --- + if event_id == BHI385_FIFO_TS_SMALL_DLT: + return 2 + if event_id == BHI385_FIFO_TS_LARGE_DLT: + return 3 + if event_id == BHI385_FIFO_TS_FULL: + return 6 + if event_id == BHI385_FIFO_META: + return 4 + + # --- Accelerometer event (7 bytes: ID + X + Y + Z as int16 LE) --- + if event_id == self._accel_sensor_id: + if offset + 7 <= length: + raw_x = buf[offset + 1] | (buf[offset + 2] << 8) + raw_y = buf[offset + 3] | (buf[offset + 4] << 8) + raw_z = buf[offset + 5] | (buf[offset + 6] << 8) + if raw_x >= 0x8000: + raw_x -= 0x10000 + if raw_y >= 0x8000: + raw_y -= 0x10000 + if raw_z >= 0x8000: + raw_z -= 0x10000 + self._accel[0] = raw_x / self._accel_sens + self._accel[1] = raw_y / self._accel_sens + self._accel[2] = raw_z / self._accel_sens + self._accel_updated = True + return 7 + + # --- Gyroscope event (7 bytes: ID + X + Y + Z as int16 LE) --- + if event_id == self._gyro_sensor_id: + if offset + 7 <= length: + raw_x = buf[offset + 1] | (buf[offset + 2] << 8) + raw_y = buf[offset + 3] | (buf[offset + 4] << 8) + raw_z = buf[offset + 5] | (buf[offset + 6] << 8) + if raw_x >= 0x8000: + raw_x -= 0x10000 + if raw_y >= 0x8000: + raw_y -= 0x10000 + if raw_z >= 0x8000: + raw_z -= 0x10000 + self._gyro[0] = raw_x / self._gyro_sens + self._gyro[1] = raw_y / self._gyro_sens + self._gyro[2] = raw_z / self._gyro_sens + self._gyro_updated = True + return 7 + + # --- Game Rotation Vector event (11 bytes: ID + x,y,z,w,accuracy as int16 LE) --- + # Scale: x/y/z/w = raw / 16384.0 (normalized quaternion, range -1 to +1) + # accuracy = raw / 16384.0 * (180 / PI) (radians → degrees) + if event_id == self._quat_sensor_id: + if offset + 11 <= length: + rx = buf[offset + 1] | (buf[offset + 2] << 8) + ry = buf[offset + 3] | (buf[offset + 4] << 8) + rz = buf[offset + 5] | (buf[offset + 6] << 8) + rw = buf[offset + 7] | (buf[offset + 8] << 8) + ra = buf[offset + 9] | (buf[offset + 10] << 8) + if rx >= 0x8000: + rx -= 0x10000 + if ry >= 0x8000: + ry -= 0x10000 + if rz >= 0x8000: + rz -= 0x10000 + if rw >= 0x8000: + rw -= 0x10000 + if ra >= 0x8000: + ra -= 0x10000 + self._quat[0] = rx / 16384.0 + self._quat[1] = ry / 16384.0 + self._quat[2] = rz / 16384.0 + self._quat[3] = rw / 16384.0 + self._quat[4] = ra / 16384.0 * (180.0 / 3.14159265) + self._quat_updated = True + return 11 + + # --- Step counter event (5 bytes: ID + uint32 LE step count) --- + if event_id == self._stc_sensor_id: + if offset + 5 <= length: + self._step_count = (buf[offset + 1] + | (buf[offset + 2] << 8) + | (buf[offset + 3] << 16) + | (buf[offset + 4] << 24)) + self._step_updated = True + return 5 + + # --- Wrist gesture event (2 bytes: ID + 1-byte gesture enum) --- + if event_id == self._wrist_gest_sensor_id: + if offset + 2 <= length: + self._wrist_gesture = buf[offset + 1] + self._wrist_gesture_updated = True + return 2 + + # --- Multi-tap event (2 bytes: ID + 1-byte tap type bitmask) --- + if event_id == self._tap_sensor_id: + if offset + 2 <= length: + self._tap_type = buf[offset + 1] + self._tap_updated = True + return 2 + + # --- Unknown sensor event: assume 3-axis format (7 bytes) as fallback --- + return 7 + + # --------------------------------------------------------------------------- + # Private: Utilities + # --------------------------------------------------------------------------- + + def _poll_boot_status(self, mask, timeout_ms): + """ + Poll boot status register until the given bits are set or timeout expires. + + :param mask: One or more BHI385_BOOT_* bits to wait for + :param timeout_ms: Timeout in milliseconds + :return: True if mask bits were set within the timeout + """ + t_start = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), t_start) < timeout_ms: + if self.getBootStatus() & mask: + return True + time.sleep_ms(1) + return False + + def _accel_range_to_sensitivity(self, range): + """Convert accelerometer range constant to LSB/g divisor.""" + if range == BHI385_ACCEL_4G: + return BHI385_ACCEL_SENS_4G + elif range == BHI385_ACCEL_16G: + return BHI385_ACCEL_SENS_16G + elif range == BHI385_ACCEL_32G: + return BHI385_ACCEL_SENS_32G + else: + return BHI385_ACCEL_SENS_8G # Default: ±8g + + def _gyro_range_to_sensitivity(self, range): + """Convert gyroscope range constant to LSB/(deg/s) divisor.""" + if range == BHI385_GYRO_125DPS: + return BHI385_GYRO_SENS_125DPS + elif range == BHI385_GYRO_250DPS: + return BHI385_GYRO_SENS_250DPS + elif range == BHI385_GYRO_500DPS: + return BHI385_GYRO_SENS_500DPS + elif range == BHI385_GYRO_1000DPS: + return BHI385_GYRO_SENS_1000DPS + else: + return BHI385_GYRO_SENS_2000DPS # Default: ±2000 deg/s From bc63b074f5e41eb78af0176c3355c27134328751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josip=20=C5=A0imun=20Ku=C4=8Di?= Date: Thu, 16 Apr 2026 08:23:19 +0200 Subject: [PATCH 2/6] Add examples and firmware binary files --- .../BHI385/Examples/bhi385-doubleTap.py | 60 +++++++++++ .../Examples/bhi385-doubleTapInterrupt.py | 76 +++++++++++++ .../Examples/bhi385-gameRotationVector.py | 59 +++++++++++ .../BHI385/Examples/bhi385-readAccelGyro.py | 84 +++++++++++++++ .../Examples/bhi385-readAccelGyroInterrupt.py | 91 ++++++++++++++++ .../BHI385/Examples/bhi385-singleTap.py | 61 +++++++++++ .../Examples/bhi385-singleTapInterrupt.py | 77 ++++++++++++++ .../BHI385/Examples/bhi385-stepCounter.py | 56 ++++++++++ .../Examples/bhi385-wristGestureDetect.py | 77 ++++++++++++++ .../bhi385-wristGestureDetectInterrupt.py | 100 ++++++++++++++++++ Sensors/BHI385/BHI385/bhi385_firmware.bin | Bin 0 -> 80776 bytes .../BHI385/BHI385/bhi385_firmware_klio.bin | Bin 0 -> 111088 bytes 12 files changed, 741 insertions(+) create mode 100644 Sensors/BHI385/BHI385/Examples/bhi385-doubleTap.py create mode 100644 Sensors/BHI385/BHI385/Examples/bhi385-doubleTapInterrupt.py create mode 100644 Sensors/BHI385/BHI385/Examples/bhi385-gameRotationVector.py create mode 100644 Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyro.py create mode 100644 Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyroInterrupt.py create mode 100644 Sensors/BHI385/BHI385/Examples/bhi385-singleTap.py create mode 100644 Sensors/BHI385/BHI385/Examples/bhi385-singleTapInterrupt.py create mode 100644 Sensors/BHI385/BHI385/Examples/bhi385-stepCounter.py create mode 100644 Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetect.py create mode 100644 Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetectInterrupt.py create mode 100644 Sensors/BHI385/BHI385/bhi385_firmware.bin create mode 100644 Sensors/BHI385/BHI385/bhi385_firmware_klio.bin diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-doubleTap.py b/Sensors/BHI385/BHI385/Examples/bhi385-doubleTap.py new file mode 100644 index 0000000..9841d96 --- /dev/null +++ b/Sensors/BHI385/BHI385/Examples/bhi385-doubleTap.py @@ -0,0 +1,60 @@ +# FILE: bhi385-doubleTap.py +# AUTHOR: Josip Šimun Kuči @ Soldered +# BRIEF: Detect double taps using the Bosch BHI385 Smart IMU multi-tap virtual +# sensor (sensor ID 153). Tap the board/sensor twice in quick succession +# to trigger a detection. The firmware's double-tap algorithm requires +# two sharp acceleration impulses within a short time window. +# WORKS WITH: Bosch BHI385 Smart IMU breakout: www.solde.red/333232 +# LAST UPDATED: 2026-04-15 + +from machine import Pin, I2C +from bhi385 import BHI385, BHI385_I2C_ADDR_HIGH, BHI385_TAP_DOUBLE +import time + +SCL_PIN = 22 +SDA_PIN = 21 + +i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000) +imu = BHI385(i2c, BHI385_I2C_ADDR_HIGH) + +print("BHI385 Double Tap Detection example") + +if not imu.begin(): + print("ERROR: BHI385 not found! Check wiring.") + raise SystemExit + +imu.enableDebug() + +print("Loading firmware...") +with open("bhi385_firmware.bin", "rb") as f: + firmware = f.read() + +if not imu.loadFirmware(firmware): + print("Firmware load failed.") + raise SystemExit +print("Firmware loaded successfully.") + +# Enable multi-tap sensor configured for double-tap-only detection at 100 Hz +if not imu.enableMultiTapDetect(BHI385_TAP_DOUBLE, 100.0): + print("ERROR: Failed to enable multi-tap sensor.") + raise SystemExit +print("Double tap detection enabled.") + +imu.disableDebug() + +print() +print("Tap the sensor twice quickly to detect a double tap.") +print("Count Type") + +tap_count = 0 + +# Poll without delay — tap events are instantaneous and the FIFO should be +# drained as quickly as possible after the interrupt fires. +while True: + imu.update() + + if imu.tapUpdated() and imu.getTapType() == BHI385_TAP_DOUBLE: + tap_count += 1 + print("{} Double tap".format(tap_count)) + + imu.clearUpdatedFlags() diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-doubleTapInterrupt.py b/Sensors/BHI385/BHI385/Examples/bhi385-doubleTapInterrupt.py new file mode 100644 index 0000000..269f382 --- /dev/null +++ b/Sensors/BHI385/BHI385/Examples/bhi385-doubleTapInterrupt.py @@ -0,0 +1,76 @@ +# FILE: bhi385-doubleTapInterrupt.py +# AUTHOR: Josip Šimun Kuči @ Soldered +# BRIEF: Detect double taps using the BHI385 multi-tap sensor driven by a +# hardware interrupt for immediate response. The tap sensor is a wake-up +# sensor — its events appear in the wake-up FIFO and assert the INT pin. +# Note: double-tap detection requires the firmware to observe two impulses +# within a short window, so the interrupt fires slightly later than for a +# single tap — this is expected behaviour. +# WORKS WITH: Bosch BHI385 Smart IMU breakout: www.solde.red/333232 +# LAST UPDATED: 2026-04-15 + +from machine import Pin, I2C +from bhi385 import BHI385, BHI385_I2C_ADDR_HIGH, BHI385_TAP_DOUBLE +import time + +# GPIO connected to the BHI385 INT pin — change as needed +INT_PIN = 4 + +SCL_PIN = 22 +SDA_PIN = 21 + +i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000) +imu = BHI385(i2c, BHI385_I2C_ADDR_HIGH) + +print("BHI385 Double Tap (interrupt mode)") + +if not imu.begin(): + print("ERROR: BHI385 not found! Check wiring.") + raise SystemExit + +imu.enableDebug() + +print("Loading firmware...") +with open("bhi385_firmware.bin", "rb") as f: + firmware = f.read() + +if not imu.loadFirmware(firmware): + print("Firmware load failed.") + raise SystemExit +print("Firmware loaded successfully.") + +if not imu.enableMultiTapDetect(BHI385_TAP_DOUBLE, 100.0): + print("ERROR: Failed to enable multi-tap sensor.") + raise SystemExit +print("Double tap detection enabled.") + +imu.disableDebug() + +# Attach interrupt AFTER firmware is loaded and sensors are configured +int_fired = False + +def on_bhi385_int(pin): + global int_fired + int_fired = True + +int_pin = Pin(INT_PIN, Pin.IN) +int_pin.irq(trigger=Pin.IRQ_RISING, handler=on_bhi385_int) + +print() +print("Tap the sensor twice quickly to trigger a double tap.") +print("Count Type") + +tap_count = 0 + +while True: + if not int_fired: + continue + + int_fired = False + imu.update() + + if imu.tapUpdated() and imu.getTapType() == BHI385_TAP_DOUBLE: + tap_count += 1 + print("{} Double tap".format(tap_count)) + + imu.clearUpdatedFlags() diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-gameRotationVector.py b/Sensors/BHI385/BHI385/Examples/bhi385-gameRotationVector.py new file mode 100644 index 0000000..3ba8c6c --- /dev/null +++ b/Sensors/BHI385/BHI385/Examples/bhi385-gameRotationVector.py @@ -0,0 +1,59 @@ +# FILE: bhi385-gameRotationVector.py +# AUTHOR: Josip Šimun Kuči @ Soldered +# BRIEF: Read a normalized quaternion from the Bosch BHI385 Smart IMU using the +# Game Rotation Vector virtual sensor (sensor ID 37). +# The Game Rotation Vector fuses accelerometer and gyroscope data to +# produce a quaternion (x, y, z, w). It does NOT use a magnetometer, so +# yaw is relative to the device orientation at power-on. Pitch and roll +# are absolute (gravity-referenced). +# WORKS WITH: Bosch BHI385 Smart IMU breakout: www.solde.red/333232 +# LAST UPDATED: 2026-04-15 + +from machine import Pin, I2C +from bhi385 import BHI385, BHI385_I2C_ADDR_HIGH +import time + +SCL_PIN = 22 +SDA_PIN = 21 + +i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000) +imu = BHI385(i2c, BHI385_I2C_ADDR_HIGH) + +print("BHI385 Game Rotation Vector example") + +if not imu.begin(): + print("ERROR: BHI385 not found! Check wiring.") + raise SystemExit + +imu.enableDebug() + +print("Loading firmware...") +with open("bhi385_firmware.bin", "rb") as f: + firmware = f.read() + +if not imu.loadFirmware(firmware): + print("Firmware load failed.") + raise SystemExit +print("Firmware loaded successfully.") + +# Enable Game Rotation Vector at 100 Hz +if not imu.enableGameRotationVector(100.0): + print("ERROR: Failed to enable Game Rotation Vector.") + raise SystemExit +print("Game Rotation Vector enabled: 100 Hz") + +imu.disableDebug() + +print() +print("Quat X Quat Y Quat Z Quat W") + +while True: + imu.update() + + if imu.quatUpdated(): + print("{:.4f} {:.4f} {:.4f} {:.4f}".format( + imu.getQuatX(), imu.getQuatY(), + imu.getQuatZ(), imu.getQuatW())) + + imu.clearUpdatedFlags() + time.sleep_ms(20) diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyro.py b/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyro.py new file mode 100644 index 0000000..baf60d4 --- /dev/null +++ b/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyro.py @@ -0,0 +1,84 @@ +# FILE: bhi385-readAccelGyro.py +# AUTHOR: Josip Šimun Kuči @ Soldered +# BRIEF: Read accelerometer and gyroscope data from the Bosch BHI385 Smart IMU +# WORKS WITH: Bosch BHI385 Smart IMU breakout: www.solde.red/333232 +# LAST UPDATED: 2026-04-15 + +from machine import Pin, I2C +from bhi385 import BHI385, BHI385_I2C_ADDR_HIGH, BHI385_ACCEL_8G, BHI385_GYRO_2000DPS +import time + +# Initialize I2C at 400 kHz for faster firmware upload (~1.3 s vs ~5 s) +SCL_PIN = 22 +SDA_PIN = 21 + +i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000) + +# Initialize sensor. Use BHI385_I2C_ADDR_HIGH (0x29) if HSDO is tied to VDDIO. +imu = BHI385(i2c, BHI385_I2C_ADDR_HIGH) + +print("BHI385 Accelerometer and Gyroscope example") + +if not imu.begin(): + print("ERROR: BHI385 not found! Check wiring, I2C address,.") + print(" Chip ID read: 0x{:02X}".format(imu.getChipId())) + print(" Expected: 0x7C") + raise SystemExit + +print("BHI385 found. Boot status: 0x{:02X}".format(imu.getBootStatus())) + +# Enable verbose debug output so every step of firmware loading is printed +imu.enableDebug() + +# Load firmware from a binary file. Obtain the firmware from Bosch Sensortec +# and place it on the filesystem as "bhi385_firmware.bin". +print("Loading firmware...") +with open("bhi385_firmware.bin", "rb") as f: + firmware = f.read() + +if not imu.loadFirmware(firmware): + print("Firmware load failed.") + raise SystemExit + +print("Firmware loaded successfully.") + +# Enable accelerometer: 100 Hz, +-8 g +if not imu.enableAccelerometer(100.0, BHI385_ACCEL_8G): + print("ERROR: Failed to enable accelerometer.") + raise SystemExit +print("Accelerometer enabled: 100 Hz, +-8 g") + +# Enable gyroscope: 100 Hz, +-2000 deg/s +if not imu.enableGyroscope(100.0, BHI385_GYRO_2000DPS): + print("ERROR: Failed to enable gyroscope.") + raise SystemExit +print("Gyroscope enabled: 100 Hz, +-2000 deg/s") + +imu.disableDebug() + +print() +print("AccelX(g) AccelY(g) AccelZ(g) GyroX(dps) GyroY(dps) GyroZ(dps)") + +# Main loop — poll at ~50 Hz. At 100 Hz sensor rate this typically returns +# 2 samples per call. +while True: + imu.update() + + accel_str = "" + if imu.accelUpdated(): + accel_str = "{:.4f} {:.4f} {:.4f}".format( + imu.getAccelX(), imu.getAccelY(), imu.getAccelZ()) + else: + accel_str = "N/A N/A N/A" + + gyro_str = "" + if imu.gyroUpdated(): + gyro_str = "{:.4f} {:.4f} {:.4f}".format( + imu.getGyroX(), imu.getGyroY(), imu.getGyroZ()) + else: + gyro_str = "N/A N/A N/A" + + print("{} {}".format(accel_str, gyro_str)) + + imu.clearUpdatedFlags() + time.sleep_ms(20) diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyroInterrupt.py b/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyroInterrupt.py new file mode 100644 index 0000000..e6fbca6 --- /dev/null +++ b/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyroInterrupt.py @@ -0,0 +1,91 @@ +# FILE: bhi385-readAccelGyroInterrupt.py +# AUTHOR: Josip Šimun Kuči @ Soldered +# BRIEF: Read accelerometer and gyroscope data from the Bosch BHI385 Smart IMU +# using a hardware interrupt for lowest-latency FIFO draining. +# The BHI385 INT pin is pulled HIGH whenever new data is waiting in the +# non-wake-up FIFO. A hardware interrupt on the host MCU detects this +# rising edge and sets a flag; the main loop then calls update() +# immediately instead of polling on a fixed timer. +# WORKS WITH: Bosch BHI385 Smart IMU breakout: www.solde.red/333232 +# LAST UPDATED: 2026-04-15 + +from machine import Pin, I2C +from bhi385 import BHI385, BHI385_I2C_ADDR_HIGH +import time + +# GPIO connected to the BHI385 INT pin — change as needed +INT_PIN = 4 + +SCL_PIN = 22 +SDA_PIN = 21 + +i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000) +imu = BHI385(i2c, BHI385_I2C_ADDR_HIGH) + +print("BHI385 Accel and Gyro (interrupt mode)") + +if not imu.begin(): + print("ERROR: BHI385 not found! Check wiring.") + raise SystemExit + +imu.enableDebug() + +print("Loading firmware...") +with open("bhi385_firmware.bin", "rb") as f: + firmware = f.read() + +if not imu.loadFirmware(firmware): + print("Firmware load failed.") + raise SystemExit +print("Firmware loaded successfully.") + +if not imu.enableAccelerometer(100.0): + print("ERROR: Failed to enable accelerometer.") + raise SystemExit + +if not imu.enableGyroscope(100.0): + print("ERROR: Failed to enable gyroscope.") + raise SystemExit + +imu.disableDebug() + +# Attach interrupt AFTER firmware is loaded and sensors are configured so that +# the initial boot interrupt does not trigger the handler early. +int_fired = False + +def on_bhi385_int(pin): + global int_fired + int_fired = True + +int_pin = Pin(INT_PIN, Pin.IN) +int_pin.irq(trigger=Pin.IRQ_RISING, handler=on_bhi385_int) + +print("Sensors enabled at 100 Hz. Waiting for data...") +print() +print("Accel X (g) Accel Y (g) Accel Z (g) Gyro X (dps) Gyro Y (dps) Gyro Z (dps)") + +while True: + if not int_fired: + continue + + int_fired = False + imu.update() + + printed = False + + if imu.accelUpdated(): + print("{:.3f} {:.3f} {:.3f}".format( + imu.getAccelX(), imu.getAccelY(), imu.getAccelZ()), end="") + printed = True + + if imu.gyroUpdated(): + if not printed: + # Pad accel columns if accel was not in this packet + print("N/A N/A N/A", end="") + print(" {:.3f} {:.3f} {:.3f}".format( + imu.getGyroX(), imu.getGyroY(), imu.getGyroZ())) + printed = True + elif printed: + print() + + imu.clearUpdatedFlags() diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-singleTap.py b/Sensors/BHI385/BHI385/Examples/bhi385-singleTap.py new file mode 100644 index 0000000..cf216cd --- /dev/null +++ b/Sensors/BHI385/BHI385/Examples/bhi385-singleTap.py @@ -0,0 +1,61 @@ +# FILE: bhi385-singleTap.py +# AUTHOR: Josip Šimun Kuči @ Soldered +# BRIEF: Detect single taps using the Bosch BHI385 Smart IMU multi-tap virtual +# sensor (sensor ID 153). Tap the board/sensor firmly once with your +# finger to trigger a detection. The firmware's tap algorithm looks for a +# sharp acceleration impulse followed by a quiet period. +# WORKS WITH: Bosch BHI385 Smart IMU breakout: www.solde.red/333232 +# LAST UPDATED: 2026-04-15 + +from machine import Pin, I2C +from bhi385 import BHI385, BHI385_I2C_ADDR_HIGH, BHI385_TAP_SINGLE +import time + +SCL_PIN = 22 +SDA_PIN = 21 + +i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000) +imu = BHI385(i2c, BHI385_I2C_ADDR_HIGH) + +print("BHI385 Single Tap Detection example") + +if not imu.begin(): + print("ERROR: BHI385 not found! Check wiring.") + raise SystemExit + +imu.enableDebug() + +print("Loading firmware...") +with open("bhi385_firmware.bin", "rb") as f: + firmware = f.read() + +if not imu.loadFirmware(firmware): + print("Firmware load failed.") + raise SystemExit +print("Firmware loaded successfully.") + +# Enable multi-tap sensor configured for single-tap-only detection at 100 Hz. +# The library writes the tap mask to the firmware parameter page before enabling. +if not imu.enableMultiTapDetect(BHI385_TAP_SINGLE, 100.0): + print("ERROR: Failed to enable multi-tap sensor.") + raise SystemExit +print("Single tap detection enabled.") + +imu.disableDebug() + +print() +print("Tap the sensor once to detect a single tap.") +print("Count Type") + +tap_count = 0 + +# Poll without delay — tap events are instantaneous and the FIFO should be +# drained as quickly as possible after the interrupt fires. +while True: + imu.update() + + if imu.tapUpdated() and imu.getTapType() == BHI385_TAP_SINGLE: + tap_count += 1 + print("{} Single tap".format(tap_count)) + + imu.clearUpdatedFlags() diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-singleTapInterrupt.py b/Sensors/BHI385/BHI385/Examples/bhi385-singleTapInterrupt.py new file mode 100644 index 0000000..bef0a74 --- /dev/null +++ b/Sensors/BHI385/BHI385/Examples/bhi385-singleTapInterrupt.py @@ -0,0 +1,77 @@ +# FILE: bhi385-singleTapInterrupt.py +# AUTHOR: Josip Šimun Kuči @ Soldered +# BRIEF: Detect single taps using the BHI385 multi-tap sensor driven by a +# hardware interrupt for immediate response. The tap sensor is a wake-up +# sensor — its events appear in the wake-up FIFO and assert the INT pin. +# The ISR sets a flag and the main loop calls update() immediately, +# keeping end-to-end latency as low as the firmware detection time +# (~10-20 ms) rather than the polling interval. +# WORKS WITH: Bosch BHI385 Smart IMU breakout: www.solde.red/333232 +# LAST UPDATED: 2026-04-15 + +from machine import Pin, I2C +from bhi385 import BHI385, BHI385_I2C_ADDR_HIGH, BHI385_TAP_SINGLE +import time + +# GPIO connected to the BHI385 INT pin — change as needed +INT_PIN = 4 + +SCL_PIN = 22 +SDA_PIN = 21 + +i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000) +imu = BHI385(i2c, BHI385_I2C_ADDR_HIGH) + +print("BHI385 Single Tap (interrupt mode)") + +if not imu.begin(): + print("ERROR: BHI385 not found! Check wiring.") + raise SystemExit + +imu.enableDebug() + +print("Loading firmware...") +with open("bhi385_firmware.bin", "rb") as f: + firmware = f.read() + +if not imu.loadFirmware(firmware): + print("Firmware load failed.") + raise SystemExit +print("Firmware loaded successfully.") + +if not imu.enableMultiTapDetect(BHI385_TAP_SINGLE, 100.0): + print("ERROR: Failed to enable multi-tap sensor.") + raise SystemExit +print("Single tap detection enabled.") + +imu.disableDebug() + +# Attach interrupt AFTER firmware is loaded and sensors are configured so that +# the initial boot interrupt does not trigger the handler early. +int_fired = False + +def on_bhi385_int(pin): + global int_fired + int_fired = True + +int_pin = Pin(INT_PIN, Pin.IN) +int_pin.irq(trigger=Pin.IRQ_RISING, handler=on_bhi385_int) + +print() +print("Tap the sensor once to trigger a single tap.") +print("Count Type") + +tap_count = 0 + +while True: + if not int_fired: + continue + + int_fired = False + imu.update() + + if imu.tapUpdated() and imu.getTapType() == BHI385_TAP_SINGLE: + tap_count += 1 + print("{} Single tap".format(tap_count)) + + imu.clearUpdatedFlags() diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-stepCounter.py b/Sensors/BHI385/BHI385/Examples/bhi385-stepCounter.py new file mode 100644 index 0000000..440a791 --- /dev/null +++ b/Sensors/BHI385/BHI385/Examples/bhi385-stepCounter.py @@ -0,0 +1,56 @@ +# FILE: bhi385-stepCounter.py +# AUTHOR: Josip Šimun Kuči @ Soldered +# BRIEF: Count footsteps using the Bosch BHI385 Smart IMU step counter virtual +# sensor (sensor ID 52). The step counter outputs a cumulative step count +# that increments as the sensor detects walking/running. The count is +# retained across sensor enable/disable cycles but resets on power-off. +# WORKS WITH: Bosch BHI385 Smart IMU breakout: www.solde.red/333232 +# LAST UPDATED: 2026-04-15 + +from machine import Pin, I2C +from bhi385 import BHI385, BHI385_I2C_ADDR_HIGH +import time + +SCL_PIN = 22 +SDA_PIN = 21 + +i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000) +imu = BHI385(i2c, BHI385_I2C_ADDR_HIGH) + +print("BHI385 Step Counter example") + +if not imu.begin(): + print("ERROR: BHI385 not found! Check wiring.") + raise SystemExit + +imu.enableDebug() + +print("Loading firmware...") +with open("bhi385_firmware.bin", "rb") as f: + firmware = f.read() + +if not imu.loadFirmware(firmware): + print("Firmware load failed.") + raise SystemExit +print("Firmware loaded successfully.") + +# Enable step counter at 100 Hz +if not imu.enableStepCounter(100.0): + print("ERROR: Failed to enable step counter.") + raise SystemExit +print("Step counter enabled: 100 Hz") + +imu.disableDebug() + +print() +print("Walk around to count steps.") +print("Steps") + +while True: + imu.update() + + if imu.stepUpdated(): + print(imu.getStepCount()) + + imu.clearUpdatedFlags() + time.sleep_ms(100) diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetect.py b/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetect.py new file mode 100644 index 0000000..3a789a1 --- /dev/null +++ b/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetect.py @@ -0,0 +1,77 @@ +# FILE: bhi385-wristGestureDetect.py +# AUTHOR: Josip Šimun Kuči @ Soldered +# BRIEF: Detect wrist gestures using the Bosch BHI385 Smart IMU wrist gesture +# detect virtual sensor (sensor ID 156). +# IMPORTANT: this sensor requires the "bsxsam_lite_Klio_cyclic" firmware +# variant from Bosch Sensortec. The plain "bsxsam_lite" firmware does NOT +# expose sensor ID 156. +# WORKS WITH: Bosch BHI385 Smart IMU breakout: www.solde.red/333232 +# LAST UPDATED: 2026-04-15 + +from machine import Pin, I2C +from bhi385 import (BHI385, BHI385_I2C_ADDR_HIGH, BHI385_WRIST_LEFT, + BHI385_WRIST_GEST_SHAKE_JIGGLE, + BHI385_WRIST_GEST_FLICK_IN, + BHI385_WRIST_GEST_FLICK_OUT) +import time + + +def gesture_name(gesture): + if gesture == BHI385_WRIST_GEST_SHAKE_JIGGLE: + return "Wrist shake/jiggle" + elif gesture == BHI385_WRIST_GEST_FLICK_IN: + return "Arm flick in" + elif gesture == BHI385_WRIST_GEST_FLICK_OUT: + return "Arm flick out" + else: + return "No gesture" + + +SCL_PIN = 22 +SDA_PIN = 21 + +i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000) +imu = BHI385(i2c, BHI385_I2C_ADDR_HIGH) + +print("BHI385 Wrist Gesture Detect example") + +if not imu.begin(): + print("ERROR: BHI385 not found! Check wiring.") + raise SystemExit + +imu.enableDebug() + +print("Loading firmware...") +with open("bhi385_firmware_klio.bin", "rb") as f: + firmware = f.read() + +if not imu.loadFirmware(firmware): + print("Firmware load failed.") + raise SystemExit +print("Firmware loaded successfully.") + +# Enable wrist gesture detect. Pass BHI385_WRIST_RIGHT if the device is on +# the right wrist. +if not imu.enableWristGestureDetect(100.0, BHI385_WRIST_LEFT): + print("ERROR: Failed to enable wrist gesture detect.") + raise SystemExit +print("Wrist gesture detect enabled.") + +imu.disableDebug() + +print() +print("Perform a wrist gesture to see it detected.") +print(" Wrist shake/jiggle : shake the wrist rapidly side to side") +print(" Arm flick in : quick inward flick of the forearm") +print(" Arm flick out : quick outward flick of the forearm") +print() +print("Gesture ID Gesture Name") + +while True: + imu.update() + + if imu.wristGestureUpdated(): + gesture = imu.getWristGesture() + print("{} {}".format(gesture, gesture_name(gesture))) + + imu.clearUpdatedFlags() diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetectInterrupt.py b/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetectInterrupt.py new file mode 100644 index 0000000..097bff3 --- /dev/null +++ b/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetectInterrupt.py @@ -0,0 +1,100 @@ +# FILE: bhi385-wristGestureDetectInterrupt.py +# AUTHOR: Josip Šimun Kuči @ Soldered +# BRIEF: Detect wrist gestures using the BHI385 wrist gesture detect sensor +# driven by a hardware interrupt for immediate response. +# The wrist gesture sensor is a wake-up sensor — its events appear in +# the wake-up FIFO and assert the INT pin. The ISR sets a flag and the +# main loop calls update() immediately. +# IMPORTANT: requires the "bsxsam_lite_Klio_cyclic" firmware variant +# from Bosch Sensortec. The plain "bsxsam_lite" firmware does NOT expose +# sensor ID 156. +# WORKS WITH: Bosch BHI385 Smart IMU breakout: www.solde.red/333232 +# LAST UPDATED: 2026-04-15 + +from machine import Pin, I2C +from bhi385 import (BHI385, BHI385_I2C_ADDR_HIGH, BHI385_WRIST_LEFT, + BHI385_WRIST_GEST_NONE, + BHI385_WRIST_GEST_SHAKE_JIGGLE, + BHI385_WRIST_GEST_FLICK_IN, + BHI385_WRIST_GEST_FLICK_OUT) +import time + +# GPIO connected to the BHI385 INT pin — change as needed +INT_PIN = 4 + + +def gesture_name(gesture): + if gesture == BHI385_WRIST_GEST_SHAKE_JIGGLE: + return "Wrist shake/jiggle" + elif gesture == BHI385_WRIST_GEST_FLICK_IN: + return "Arm flick in" + elif gesture == BHI385_WRIST_GEST_FLICK_OUT: + return "Arm flick out" + else: + return "Unknown" + + +SCL_PIN = 22 +SDA_PIN = 21 + +i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000) +imu = BHI385(i2c, BHI385_I2C_ADDR_HIGH) + +print("BHI385 Wrist Gesture Detect (interrupt mode)") + +if not imu.begin(): + print("ERROR: BHI385 not found! Check wiring.") + raise SystemExit + +imu.enableDebug() + +print("Loading firmware...") +with open("bhi385_firmware_klio.bin", "rb") as f: + firmware = f.read() + +if not imu.loadFirmware(firmware): + print("Firmware load failed.") + raise SystemExit +print("Firmware loaded successfully.") + +# Enable wrist gesture detect. Pass BHI385_WRIST_RIGHT if the device is on +# the right wrist. +if not imu.enableWristGestureDetect(100.0, BHI385_WRIST_LEFT): + print("ERROR: Failed to enable wrist gesture detect.") + raise SystemExit +print("Wrist gesture detect enabled.") + +imu.disableDebug() + +# Attach interrupt AFTER firmware is loaded and sensors are configured +int_fired = False + +def on_bhi385_int(pin): + global int_fired + int_fired = True + +int_pin = Pin(INT_PIN, Pin.IN) +int_pin.irq(trigger=Pin.IRQ_RISING, handler=on_bhi385_int) + +print() +print("Perform a wrist gesture to see it detected.") +print(" Wrist shake/jiggle : shake the wrist rapidly side to side") +print(" Arm flick in : quick inward flick of the forearm") +print(" Arm flick out : quick outward flick of the forearm") +print() +print("Gesture ID Gesture Name") + +while True: + if not int_fired: + continue + + int_fired = False + imu.update() + + if imu.wristGestureUpdated(): + gesture = imu.getWristGesture() + # Filter out "No Gesture" (value 0) — reported at startup and between gestures + if gesture != BHI385_WRIST_GEST_NONE: + print("{} {}".format(gesture, gesture_name(gesture))) + + imu.clearUpdatedFlags() diff --git a/Sensors/BHI385/BHI385/bhi385_firmware.bin b/Sensors/BHI385/BHI385/bhi385_firmware.bin new file mode 100644 index 0000000000000000000000000000000000000000..c874997f549b7f07cfc1b5a7933c79bf2d7f58a3 GIT binary patch literal 80776 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 literal 0 HcmV?d00001 diff --git a/Sensors/BHI385/BHI385/bhi385_firmware_klio.bin b/Sensors/BHI385/BHI385/bhi385_firmware_klio.bin new file mode 100644 index 0000000000000000000000000000000000000000..cb3a7d77e3d17df3a823f787d7163ff296e38123 GIT binary patch literal 111088 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 literal 0 HcmV?d00001 From 5a26e6fcf3990faad628d3555e37706afb55c63c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josip=20=C5=A0imun=20Ku=C4=8Di?= Date: Thu, 16 Apr 2026 08:23:40 +0200 Subject: [PATCH 3/6] Create a README and package.json --- Sensors/BHI385/README.md | 14 +++++++++ Sensors/BHI385/package.json | 58 +++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 Sensors/BHI385/README.md create mode 100644 Sensors/BHI385/package.json diff --git a/Sensors/BHI385/README.md b/Sensors/BHI385/README.md new file mode 100644 index 0000000..c2cc394 --- /dev/null +++ b/Sensors/BHI385/README.md @@ -0,0 +1,14 @@ +# How to install + +--- + +After [**installing the mpremote package**](https://docs.micropython.org/en/latest/reference/mpremote.html), flash a module to the board using the following command: + +```sh + mpremote mip install github:SolderedElectronics/Soldered-Micropython-modules/Sensors/BHI385 +``` +Or, if you're running a Windows OS: + +```sh + python -m mpremote mip install github:SolderedElectronics/Soldered-Micropython-modules/Sensors/BHI385 +``` diff --git a/Sensors/BHI385/package.json b/Sensors/BHI385/package.json new file mode 100644 index 0000000..18754e4 --- /dev/null +++ b/Sensors/BHI385/package.json @@ -0,0 +1,58 @@ +{ + "urls": [ + [ + "bhi385.py", + "github:SolderedElectronics/Soldered-MicroPython-Modules/Sensors/BHI385/BHI385/bhi385.py" + ], + [ + "bhi385_firmware.bin", + "github:SolderedElectronics/Soldered-MicroPython-Modules/Sensors/BHI385/BHI385/bhi385_firmware.bin" + ], + [ + "bhi385_firmware_klio.bin", + "github:SolderedElectronics/Soldered-MicroPython-Modules/Sensors/BHI385/BHI385/bhi385_firmware_klio.bin" + ], + [ + "Examples/bhi385-readAccelGyro.py", + "github:SolderedElectronics/Soldered-MicroPython-Modules/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyro.py" + ], + [ + "Examples/bhi385-readAccelGyroInterrupt.py", + "github:SolderedElectronics/Soldered-MicroPython-Modules/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyroInterrupt.py" + ], + [ + "Examples/bhi385-gameRotationVector.py", + "github:SolderedElectronics/Soldered-MicroPython-Modules/Sensors/BHI385/BHI385/Examples/bhi385-gameRotationVector.py" + ], + [ + "Examples/bhi385-stepCounter.py", + "github:SolderedElectronics/Soldered-MicroPython-Modules/Sensors/BHI385/BHI385/Examples/bhi385-stepCounter.py" + ], + [ + "Examples/bhi385-singleTap.py", + "github:SolderedElectronics/Soldered-MicroPython-Modules/Sensors/BHI385/BHI385/Examples/bhi385-singleTap.py" + ], + [ + "Examples/bhi385-singleTapInterrupt.py", + "github:SolderedElectronics/Soldered-MicroPython-Modules/Sensors/BHI385/BHI385/Examples/bhi385-singleTapInterrupt.py" + ], + [ + "Examples/bhi385-doubleTap.py", + "github:SolderedElectronics/Soldered-MicroPython-Modules/Sensors/BHI385/BHI385/Examples/bhi385-doubleTap.py" + ], + [ + "Examples/bhi385-doubleTapInterrupt.py", + "github:SolderedElectronics/Soldered-MicroPython-Modules/Sensors/BHI385/BHI385/Examples/bhi385-doubleTapInterrupt.py" + ], + [ + "Examples/bhi385-wristGestureDetect.py", + "github:SolderedElectronics/Soldered-MicroPython-Modules/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetect.py" + ], + [ + "Examples/bhi385-wristGestureDetectInterrupt.py", + "github:SolderedElectronics/Soldered-MicroPython-Modules/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetectInterrupt.py" + ] + ], + "deps": [], + "version": "1.0" +} From 9f8d9ca2822b7f58c9d8db98fbc5f120ab498c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josip=20=C5=A0imun=20Ku=C4=8Di?= Date: Thu, 16 Apr 2026 08:24:00 +0200 Subject: [PATCH 4/6] Add BHI385 sensor to supported module list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 98ebd30..9f8ad95 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Each module in the library is designed to be lightweight, readable, and compatib ### Sensors - [AD8495](Sensors/AD8495/) - [APDS9960](Sensors/APDS9960/) +- [BHI385](Sensors/BHI385/) - [BME280](Sensors/BME280/) - [BME680](Sensors/BME680/) - [BME688](Sensors/BME688/) From ce41434ee5d51d58a62cfedd6aa838747ffd7d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josip=20=C5=A0imun=20Ku=C4=8Di?= Date: Thu, 16 Apr 2026 08:24:05 +0200 Subject: [PATCH 5/6] Create .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..407e9e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode/ +build/ +Build/ +examples/.DS_Store +.DS_Store \ No newline at end of file From 5a345cb53f223412a0a423798cd1486c0d85c4e8 Mon Sep 17 00:00:00 2001 From: JosipKuci Date: Thu, 16 Apr 2026 06:28:39 +0000 Subject: [PATCH 6/6] Apply Ruff formatting (MicroPython-safe) --- .../Examples/bhi385-doubleTapInterrupt.py | 2 + .../Examples/bhi385-gameRotationVector.py | 8 +- .../BHI385/Examples/bhi385-readAccelGyro.py | 6 +- .../Examples/bhi385-readAccelGyroInterrupt.py | 17 +- .../Examples/bhi385-singleTapInterrupt.py | 2 + .../Examples/bhi385-wristGestureDetect.py | 12 +- .../bhi385-wristGestureDetectInterrupt.py | 16 +- Sensors/BHI385/BHI385/bhi385.py | 377 +++++++++++------- 8 files changed, 272 insertions(+), 168 deletions(-) diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-doubleTapInterrupt.py b/Sensors/BHI385/BHI385/Examples/bhi385-doubleTapInterrupt.py index 269f382..b2d8ae6 100644 --- a/Sensors/BHI385/BHI385/Examples/bhi385-doubleTapInterrupt.py +++ b/Sensors/BHI385/BHI385/Examples/bhi385-doubleTapInterrupt.py @@ -49,10 +49,12 @@ # Attach interrupt AFTER firmware is loaded and sensors are configured int_fired = False + def on_bhi385_int(pin): global int_fired int_fired = True + int_pin = Pin(INT_PIN, Pin.IN) int_pin.irq(trigger=Pin.IRQ_RISING, handler=on_bhi385_int) diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-gameRotationVector.py b/Sensors/BHI385/BHI385/Examples/bhi385-gameRotationVector.py index 3ba8c6c..fc7ebc6 100644 --- a/Sensors/BHI385/BHI385/Examples/bhi385-gameRotationVector.py +++ b/Sensors/BHI385/BHI385/Examples/bhi385-gameRotationVector.py @@ -51,9 +51,11 @@ imu.update() if imu.quatUpdated(): - print("{:.4f} {:.4f} {:.4f} {:.4f}".format( - imu.getQuatX(), imu.getQuatY(), - imu.getQuatZ(), imu.getQuatW())) + print( + "{:.4f} {:.4f} {:.4f} {:.4f}".format( + imu.getQuatX(), imu.getQuatY(), imu.getQuatZ(), imu.getQuatW() + ) + ) imu.clearUpdatedFlags() time.sleep_ms(20) diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyro.py b/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyro.py index baf60d4..f2495b9 100644 --- a/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyro.py +++ b/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyro.py @@ -67,14 +67,16 @@ accel_str = "" if imu.accelUpdated(): accel_str = "{:.4f} {:.4f} {:.4f}".format( - imu.getAccelX(), imu.getAccelY(), imu.getAccelZ()) + imu.getAccelX(), imu.getAccelY(), imu.getAccelZ() + ) else: accel_str = "N/A N/A N/A" gyro_str = "" if imu.gyroUpdated(): gyro_str = "{:.4f} {:.4f} {:.4f}".format( - imu.getGyroX(), imu.getGyroY(), imu.getGyroZ()) + imu.getGyroX(), imu.getGyroY(), imu.getGyroZ() + ) else: gyro_str = "N/A N/A N/A" diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyroInterrupt.py b/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyroInterrupt.py index e6fbca6..4f2f9fd 100644 --- a/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyroInterrupt.py +++ b/Sensors/BHI385/BHI385/Examples/bhi385-readAccelGyroInterrupt.py @@ -53,10 +53,12 @@ # the initial boot interrupt does not trigger the handler early. int_fired = False + def on_bhi385_int(pin): global int_fired int_fired = True + int_pin = Pin(INT_PIN, Pin.IN) int_pin.irq(trigger=Pin.IRQ_RISING, handler=on_bhi385_int) @@ -74,16 +76,23 @@ def on_bhi385_int(pin): printed = False if imu.accelUpdated(): - print("{:.3f} {:.3f} {:.3f}".format( - imu.getAccelX(), imu.getAccelY(), imu.getAccelZ()), end="") + print( + "{:.3f} {:.3f} {:.3f}".format( + imu.getAccelX(), imu.getAccelY(), imu.getAccelZ() + ), + end="", + ) printed = True if imu.gyroUpdated(): if not printed: # Pad accel columns if accel was not in this packet print("N/A N/A N/A", end="") - print(" {:.3f} {:.3f} {:.3f}".format( - imu.getGyroX(), imu.getGyroY(), imu.getGyroZ())) + print( + " {:.3f} {:.3f} {:.3f}".format( + imu.getGyroX(), imu.getGyroY(), imu.getGyroZ() + ) + ) printed = True elif printed: print() diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-singleTapInterrupt.py b/Sensors/BHI385/BHI385/Examples/bhi385-singleTapInterrupt.py index bef0a74..fbc271e 100644 --- a/Sensors/BHI385/BHI385/Examples/bhi385-singleTapInterrupt.py +++ b/Sensors/BHI385/BHI385/Examples/bhi385-singleTapInterrupt.py @@ -50,10 +50,12 @@ # the initial boot interrupt does not trigger the handler early. int_fired = False + def on_bhi385_int(pin): global int_fired int_fired = True + int_pin = Pin(INT_PIN, Pin.IN) int_pin.irq(trigger=Pin.IRQ_RISING, handler=on_bhi385_int) diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetect.py b/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetect.py index 3a789a1..82d69e3 100644 --- a/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetect.py +++ b/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetect.py @@ -9,10 +9,14 @@ # LAST UPDATED: 2026-04-15 from machine import Pin, I2C -from bhi385 import (BHI385, BHI385_I2C_ADDR_HIGH, BHI385_WRIST_LEFT, - BHI385_WRIST_GEST_SHAKE_JIGGLE, - BHI385_WRIST_GEST_FLICK_IN, - BHI385_WRIST_GEST_FLICK_OUT) +from bhi385 import ( + BHI385, + BHI385_I2C_ADDR_HIGH, + BHI385_WRIST_LEFT, + BHI385_WRIST_GEST_SHAKE_JIGGLE, + BHI385_WRIST_GEST_FLICK_IN, + BHI385_WRIST_GEST_FLICK_OUT, +) import time diff --git a/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetectInterrupt.py b/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetectInterrupt.py index 097bff3..010f8ab 100644 --- a/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetectInterrupt.py +++ b/Sensors/BHI385/BHI385/Examples/bhi385-wristGestureDetectInterrupt.py @@ -12,11 +12,15 @@ # LAST UPDATED: 2026-04-15 from machine import Pin, I2C -from bhi385 import (BHI385, BHI385_I2C_ADDR_HIGH, BHI385_WRIST_LEFT, - BHI385_WRIST_GEST_NONE, - BHI385_WRIST_GEST_SHAKE_JIGGLE, - BHI385_WRIST_GEST_FLICK_IN, - BHI385_WRIST_GEST_FLICK_OUT) +from bhi385 import ( + BHI385, + BHI385_I2C_ADDR_HIGH, + BHI385_WRIST_LEFT, + BHI385_WRIST_GEST_NONE, + BHI385_WRIST_GEST_SHAKE_JIGGLE, + BHI385_WRIST_GEST_FLICK_IN, + BHI385_WRIST_GEST_FLICK_OUT, +) import time # GPIO connected to the BHI385 INT pin — change as needed @@ -69,10 +73,12 @@ def gesture_name(gesture): # Attach interrupt AFTER firmware is loaded and sensors are configured int_fired = False + def on_bhi385_int(pin): global int_fired int_fired = True + int_pin = Pin(INT_PIN, Pin.IN) int_pin.irq(trigger=Pin.IRQ_RISING, handler=on_bhi385_int) diff --git a/Sensors/BHI385/BHI385/bhi385.py b/Sensors/BHI385/BHI385/bhi385.py index eb8d376..0b12475 100644 --- a/Sensors/BHI385/BHI385/bhi385.py +++ b/Sensors/BHI385/BHI385/bhi385.py @@ -11,90 +11,90 @@ # --------------------------------------------------------------------------- # I2C addresses (selected by HSDO pin) # --------------------------------------------------------------------------- -BHI385_I2C_ADDR_LOW = 0x28 # HSDO = GND +BHI385_I2C_ADDR_LOW = 0x28 # HSDO = GND BHI385_I2C_ADDR_HIGH = 0x29 # HSDO = VDDIO # --------------------------------------------------------------------------- # Channel registers (DMA channels for bulk transfer) # --------------------------------------------------------------------------- -BHI385_CH_CMD = 0x00 # Host command input (write-only) +BHI385_CH_CMD = 0x00 # Host command input (write-only) BHI385_CH_FIFO_WU = 0x01 # Wake-up FIFO output (read-only) BHI385_CH_FIFO_NW = 0x02 # Non-wake-up FIFO output (read-only) -BHI385_CH_STATUS = 0x03 # Status and debug FIFO output (read-only) +BHI385_CH_STATUS = 0x03 # Status and debug FIFO output (read-only) # --------------------------------------------------------------------------- # Configuration registers # --------------------------------------------------------------------------- -BHI385_REG_CHIP_CTRL = 0x05 +BHI385_REG_CHIP_CTRL = 0x05 BHI385_REG_HOST_INTF_CTRL = 0x06 -BHI385_REG_HOST_IRQ_CTRL = 0x07 -BHI385_REG_RESET_REQ = 0x14 -BHI385_REG_HOST_CTRL = 0x16 -BHI385_REG_HOST_STATUS = 0x17 +BHI385_REG_HOST_IRQ_CTRL = 0x07 +BHI385_REG_RESET_REQ = 0x14 +BHI385_REG_HOST_CTRL = 0x16 +BHI385_REG_HOST_STATUS = 0x17 # --------------------------------------------------------------------------- # Identity and status registers # --------------------------------------------------------------------------- -BHI385_REG_FUSER2_ID = 0x1C # Expected value: 0x89 -BHI385_REG_FUSER2_REV = 0x1D # 0x02 before firmware, 0x03 after -BHI385_REG_ROM_VER_L = 0x1E # ROM version low byte (0x2E) -BHI385_REG_ROM_VER_H = 0x1F # ROM version high byte (0x14) +BHI385_REG_FUSER2_ID = 0x1C # Expected value: 0x89 +BHI385_REG_FUSER2_REV = 0x1D # 0x02 before firmware, 0x03 after +BHI385_REG_ROM_VER_L = 0x1E # ROM version low byte (0x2E) +BHI385_REG_ROM_VER_H = 0x1F # ROM version high byte (0x14) BHI385_REG_BOOT_STATUS = 0x25 -BHI385_REG_CHIP_ID = 0x2B # Expected value: 0x7C -BHI385_REG_INT_STATUS = 0x2D -BHI385_REG_ERROR_VAL = 0x2E +BHI385_REG_CHIP_ID = 0x2B # Expected value: 0x7C +BHI385_REG_INT_STATUS = 0x2D +BHI385_REG_ERROR_VAL = 0x2E # --------------------------------------------------------------------------- # Expected chip identity values # --------------------------------------------------------------------------- -BHI385_CHIP_ID_VAL = 0x7C +BHI385_CHIP_ID_VAL = 0x7C BHI385_FUSER2_ID_VAL = 0x89 # --------------------------------------------------------------------------- # Boot status register (0x25) bit masks # --------------------------------------------------------------------------- -BHI385_BOOT_FLASH_DET = 0x01 +BHI385_BOOT_FLASH_DET = 0x01 BHI385_BOOT_FLASH_VER_DONE = 0x02 -BHI385_BOOT_FLASH_VER_ERR = 0x04 -BHI385_BOOT_NO_FLASH = 0x08 +BHI385_BOOT_FLASH_VER_ERR = 0x04 +BHI385_BOOT_NO_FLASH = 0x08 BHI385_BOOT_HOST_IFACE_RDY = 0x10 # Bootloader or firmware ready for host -BHI385_BOOT_FW_VER_DONE = 0x20 # RAM firmware CRC check passed -BHI385_BOOT_FW_VER_ERR = 0x40 # RAM firmware CRC check failed -BHI385_BOOT_FW_IDLE = 0x80 +BHI385_BOOT_FW_VER_DONE = 0x20 # RAM firmware CRC check passed +BHI385_BOOT_FW_VER_ERR = 0x40 # RAM firmware CRC check failed +BHI385_BOOT_FW_IDLE = 0x80 # --------------------------------------------------------------------------- # Interrupt status register (0x2D) bit masks # --------------------------------------------------------------------------- -BHI385_INT_ASSERTED = 0x01 # Interrupt is asserted -BHI385_INT_FIFO_WU = 0x02 # Wake-up FIFO data ready +BHI385_INT_ASSERTED = 0x01 # Interrupt is asserted +BHI385_INT_FIFO_WU = 0x02 # Wake-up FIFO data ready BHI385_INT_FIFO_WU_LAT = 0x04 # Wake-up FIFO latency threshold reached -BHI385_INT_FIFO_NW = 0x08 # Non-wake-up FIFO data ready +BHI385_INT_FIFO_NW = 0x08 # Non-wake-up FIFO data ready BHI385_INT_FIFO_NW_LAT = 0x10 # Non-wake-up FIFO latency threshold reached -BHI385_INT_STATUS_DBG = 0x20 # Status / debug FIFO has data +BHI385_INT_STATUS_DBG = 0x20 # Status / debug FIFO has data # --------------------------------------------------------------------------- # Host command IDs (written to channel 0x00) # --------------------------------------------------------------------------- -BHI385_CMD_UPLOAD_FW = 0x0002 -BHI385_CMD_BOOT_FW = 0x0003 +BHI385_CMD_UPLOAD_FW = 0x0002 +BHI385_CMD_BOOT_FW = 0x0003 BHI385_CMD_CONFIGURE_SENSOR = 0x000D -BHI385_CMD_CHANGE_RANGE = 0x000E +BHI385_CMD_CHANGE_RANGE = 0x000E # --------------------------------------------------------------------------- # Virtual sensor IDs # --------------------------------------------------------------------------- BHI385_SENSOR_ACCEL_PASSTHROUGH = 1 -BHI385_SENSOR_ACCEL_RAW = 3 # Raw accelerometer (non-wake-up) -BHI385_SENSOR_ACCEL_CORRECTED = 4 # Corrected accelerometer (non-wake-up) -BHI385_SENSOR_GYRO_PASSTHROUGH = 10 -BHI385_SENSOR_GYRO_RAW = 12 # Raw gyroscope (non-wake-up) -BHI385_SENSOR_GYRO_CORRECTED = 13 # Corrected gyroscope (non-wake-up) -BHI385_SENSOR_GAMERV = 37 # Game rotation vector (non-wake-up) -BHI385_SENSOR_STC = 52 # Step counter (non-wake-up) -BHI385_SENSOR_STC_LP = 136 # Step counter Low Power — used by bsxsam_lite firmware -BHI385_SENSOR_MULTI_TAP = 153 # Multi-tap detect (wake-up) -BHI385_SENSOR_WRIST_GEST = 156 # Wrist gesture detect Low Power (wake-up) -BHI385_SENSOR_WRIST_WEAR = 158 # Wrist wear wakeup (wake-up) +BHI385_SENSOR_ACCEL_RAW = 3 # Raw accelerometer (non-wake-up) +BHI385_SENSOR_ACCEL_CORRECTED = 4 # Corrected accelerometer (non-wake-up) +BHI385_SENSOR_GYRO_PASSTHROUGH = 10 +BHI385_SENSOR_GYRO_RAW = 12 # Raw gyroscope (non-wake-up) +BHI385_SENSOR_GYRO_CORRECTED = 13 # Corrected gyroscope (non-wake-up) +BHI385_SENSOR_GAMERV = 37 # Game rotation vector (non-wake-up) +BHI385_SENSOR_STC = 52 # Step counter (non-wake-up) +BHI385_SENSOR_STC_LP = 136 # Step counter Low Power — used by bsxsam_lite firmware +BHI385_SENSOR_MULTI_TAP = 153 # Multi-tap detect (wake-up) +BHI385_SENSOR_WRIST_GEST = 156 # Wrist gesture detect Low Power (wake-up) +BHI385_SENSOR_WRIST_WEAR = 158 # Wrist wear wakeup (wake-up) # Parameter page command ID for multi-tap enable configuration BHI385_PARAM_MULTI_TAP_ENABLE = 0x0D01 @@ -110,91 +110,91 @@ # FIFO system event IDs # Total size = 1 (ID byte) + payload bytes shown in comment # --------------------------------------------------------------------------- -BHI385_FIFO_PAD = 0x00 # Padding (1 byte total) +BHI385_FIFO_PAD = 0x00 # Padding (1 byte total) BHI385_FIFO_TS_SMALL_DLT_WU = 0xF5 # WU small delta timestamp (2 bytes total) BHI385_FIFO_TS_LARGE_DLT_WU = 0xF6 # WU large delta timestamp (3 bytes total) -BHI385_FIFO_TS_FULL_WU = 0xF7 # WU full timestamp (6 bytes total) -BHI385_FIFO_META_WU = 0xF8 # WU meta event (4 bytes total) -BHI385_FIFO_DEBUG_MSG = 0xFA # Debug message (18 bytes total) -BHI385_FIFO_TS_SMALL_DLT = 0xFB # NW small delta timestamp (2 bytes total) -BHI385_FIFO_TS_LARGE_DLT = 0xFC # NW large delta timestamp (3 bytes total) -BHI385_FIFO_TS_FULL = 0xFD # NW full timestamp (6 bytes total) -BHI385_FIFO_META = 0xFE # NW meta event (4 bytes total) -BHI385_FIFO_FILLER = 0xFF # Filler byte (1 byte total) +BHI385_FIFO_TS_FULL_WU = 0xF7 # WU full timestamp (6 bytes total) +BHI385_FIFO_META_WU = 0xF8 # WU meta event (4 bytes total) +BHI385_FIFO_DEBUG_MSG = 0xFA # Debug message (18 bytes total) +BHI385_FIFO_TS_SMALL_DLT = 0xFB # NW small delta timestamp (2 bytes total) +BHI385_FIFO_TS_LARGE_DLT = 0xFC # NW large delta timestamp (3 bytes total) +BHI385_FIFO_TS_FULL = 0xFD # NW full timestamp (6 bytes total) +BHI385_FIFO_META = 0xFE # NW meta event (4 bytes total) +BHI385_FIFO_FILLER = 0xFF # Filler byte (1 byte total) # --------------------------------------------------------------------------- # Accelerometer sensitivity in LSB/g per dynamic range setting # --------------------------------------------------------------------------- -BHI385_ACCEL_SENS_4G = 8192.0 -BHI385_ACCEL_SENS_8G = 4096.0 +BHI385_ACCEL_SENS_4G = 8192.0 +BHI385_ACCEL_SENS_8G = 4096.0 BHI385_ACCEL_SENS_16G = 2048.0 BHI385_ACCEL_SENS_32G = 1024.0 # --------------------------------------------------------------------------- # Gyroscope sensitivity in LSB/(deg/s) per full-scale range setting # --------------------------------------------------------------------------- -BHI385_GYRO_SENS_125DPS = 262.144 -BHI385_GYRO_SENS_250DPS = 131.072 -BHI385_GYRO_SENS_500DPS = 65.536 +BHI385_GYRO_SENS_125DPS = 262.144 +BHI385_GYRO_SENS_250DPS = 131.072 +BHI385_GYRO_SENS_500DPS = 65.536 BHI385_GYRO_SENS_1000DPS = 32.768 BHI385_GYRO_SENS_2000DPS = 16.384 # --------------------------------------------------------------------------- # Timing constants (from datasheet) # --------------------------------------------------------------------------- -BHI385_T_BOOT_BL_MS = 5 # Bootloader ready timeout (max 1.3 ms, use 5 ms) -BHI385_T_BOOT_FW_MS = 500 # Firmware boot timeout (typical 81 ms) -BHI385_T_FW_VER_MS = 5000 # Firmware CRC verify timeout (conservative) +BHI385_T_BOOT_BL_MS = 5 # Bootloader ready timeout (max 1.3 ms, use 5 ms) +BHI385_T_BOOT_FW_MS = 500 # Firmware boot timeout (typical 81 ms) +BHI385_T_FW_VER_MS = 5000 # Firmware CRC verify timeout (conservative) # --------------------------------------------------------------------------- # Buffer and chunk sizes # --------------------------------------------------------------------------- -BHI385_FIFO_BUF_SIZE = 256 # Maximum FIFO read buffer -BHI385_I2C_CHUNK_SIZE = 28 # Max data bytes per I2C write (Wire buffer safe limit) +BHI385_FIFO_BUF_SIZE = 256 # Maximum FIFO read buffer +BHI385_I2C_CHUNK_SIZE = 28 # Max data bytes per I2C write (Wire buffer safe limit) # --------------------------------------------------------------------------- # Accelerometer dynamic range options (in g) # --------------------------------------------------------------------------- -BHI385_ACCEL_4G = 4 -BHI385_ACCEL_8G = 8 +BHI385_ACCEL_4G = 4 +BHI385_ACCEL_8G = 8 BHI385_ACCEL_16G = 16 BHI385_ACCEL_32G = 32 # --------------------------------------------------------------------------- # Gyroscope full-scale range options (in deg/s) # --------------------------------------------------------------------------- -BHI385_GYRO_125DPS = 125 -BHI385_GYRO_250DPS = 250 -BHI385_GYRO_500DPS = 500 +BHI385_GYRO_125DPS = 125 +BHI385_GYRO_250DPS = 250 +BHI385_GYRO_500DPS = 500 BHI385_GYRO_1000DPS = 1000 BHI385_GYRO_2000DPS = 2000 # --------------------------------------------------------------------------- # Wrist gesture identifiers # --------------------------------------------------------------------------- -BHI385_WRIST_GEST_NONE = 0 # No gesture / unknown +BHI385_WRIST_GEST_NONE = 0 # No gesture / unknown BHI385_WRIST_GEST_SHAKE_JIGGLE = 3 # Wrist shake / jiggle -BHI385_WRIST_GEST_FLICK_IN = 4 # Arm flick in -BHI385_WRIST_GEST_FLICK_OUT = 5 # Arm flick out +BHI385_WRIST_GEST_FLICK_IN = 4 # Arm flick in +BHI385_WRIST_GEST_FLICK_OUT = 5 # Arm flick out # --------------------------------------------------------------------------- # Tap type bitmask values # As an event value: which tap was detected (one bit set at a time). # As a config mask: OR together tap types to detect. # --------------------------------------------------------------------------- -BHI385_TAP_NONE = 0 # No tap -BHI385_TAP_SINGLE = 1 # Single tap -BHI385_TAP_DOUBLE = 2 # Double tap +BHI385_TAP_NONE = 0 # No tap +BHI385_TAP_SINGLE = 1 # Single tap +BHI385_TAP_DOUBLE = 2 # Double tap BHI385_TAP_DOUBLE_SINGLE = 3 # Double and single tap -BHI385_TAP_TRIPLE = 4 # Triple tap +BHI385_TAP_TRIPLE = 4 # Triple tap BHI385_TAP_TRIPLE_SINGLE = 5 # Triple and single tap BHI385_TAP_TRIPLE_DOUBLE = 6 # Triple and double tap -BHI385_TAP_ALL = 7 # All tap types enabled +BHI385_TAP_ALL = 7 # All tap types enabled # --------------------------------------------------------------------------- # Wrist hand options # --------------------------------------------------------------------------- -BHI385_WRIST_LEFT = 0 # Device worn on the left wrist (firmware default) +BHI385_WRIST_LEFT = 0 # Device worn on the left wrist (firmware default) BHI385_WRIST_RIGHT = 1 # Device worn on the right wrist @@ -297,12 +297,19 @@ def begin(self, address=None): if fuser2_rev: print("[BHI385] Fuser2 Rev: 0x{:02X}".format(fuser2_rev[0])) if rom_ver_h and rom_ver_l: - print("[BHI385] ROM version: 0x{:02X}{:02X}".format(rom_ver_h[0], rom_ver_l[0])) + print( + "[BHI385] ROM version: 0x{:02X}{:02X}".format( + rom_ver_h[0], rom_ver_l[0] + ) + ) if self.getChipId() != BHI385_CHIP_ID_VAL: if self._dbg: - print("[BHI385] begin: FAILED: chip ID mismatch (got 0x{:02X}, expected 0x{:02X})".format( - self.getChipId(), BHI385_CHIP_ID_VAL)) + print( + "[BHI385] begin: FAILED: chip ID mismatch (got 0x{:02X}, expected 0x{:02X})".format( + self.getChipId(), BHI385_CHIP_ID_VAL + ) + ) return False self._initialized = True @@ -331,27 +338,39 @@ def loadFirmware(self, firmware): if self._dbg: print("[BHI385] loadFirmware: firmware size = {} bytes".format(fw_len)) - print("[BHI385] loadFirmware: boot status before upload = 0x{:02X}".format( - self.getBootStatus())) + print( + "[BHI385] loadFirmware: boot status before upload = 0x{:02X}".format( + self.getBootStatus() + ) + ) # Step 1: send the 4-byte "Upload to Program RAM" command header. # Format: [CMD_ID_L][CMD_ID_H][WORD_COUNT_L][WORD_COUNT_H] # WORD_COUNT = ceil(fw_len / 4) — firmware size in 32-bit words, not bytes. fw_len_rounded = (fw_len + 3) & ~3 word_count = fw_len_rounded // 4 - header = bytes([ - BHI385_CMD_UPLOAD_FW & 0xFF, - (BHI385_CMD_UPLOAD_FW >> 8) & 0xFF, - word_count & 0xFF, - (word_count >> 8) & 0xFF, - ]) + header = bytes( + [ + BHI385_CMD_UPLOAD_FW & 0xFF, + (BHI385_CMD_UPLOAD_FW >> 8) & 0xFF, + word_count & 0xFF, + (word_count >> 8) & 0xFF, + ] + ) if self._dbg: fw_preview = " ".join("{:02X}".format(b) for b in firmware[:32]) - print("[BHI385] loadFirmware: firmware first 32 bytes: {}".format(fw_preview)) - print("[BHI385] loadFirmware: word count = {} ({} bytes rounded)".format( - word_count, fw_len_rounded)) - print("[BHI385] loadFirmware: [1/5] writing upload command header... ", end="") + print( + "[BHI385] loadFirmware: firmware first 32 bytes: {}".format(fw_preview) + ) + print( + "[BHI385] loadFirmware: word count = {} ({} bytes rounded)".format( + word_count, fw_len_rounded + ) + ) + print( + "[BHI385] loadFirmware: [1/5] writing upload command header... ", end="" + ) if not self._channel_write(BHI385_CH_CMD, header): if self._dbg: @@ -367,8 +386,11 @@ def loadFirmware(self, firmware): # receive buffer before the next transaction arrives. if self._dbg: num_chunks = fw_len // BHI385_I2C_CHUNK_SIZE + 1 - print("[BHI385] loadFirmware: [2/5] uploading {} chunks @ {} bytes each...".format( - num_chunks, BHI385_I2C_CHUNK_SIZE)) + print( + "[BHI385] loadFirmware: [2/5] uploading {} chunks @ {} bytes each...".format( + num_chunks, BHI385_I2C_CHUNK_SIZE + ) + ) offset = 0 report_step = max(fw_len // 10, 1) @@ -377,65 +399,86 @@ def loadFirmware(self, firmware): while offset < fw_len: chunk = min(fw_len - offset, BHI385_I2C_CHUNK_SIZE) try: - self._i2c.writeto_mem(self._addr, BHI385_CH_CMD, - firmware[offset:offset + chunk]) + self._i2c.writeto_mem( + self._addr, BHI385_CH_CMD, firmware[offset : offset + chunk] + ) except OSError as e: if self._dbg: - print("[BHI385] loadFirmware: [2/5] FAILED at offset {}/{} ({})".format( - offset, fw_len, e)) + print( + "[BHI385] loadFirmware: [2/5] FAILED at offset {}/{} ({})".format( + offset, fw_len, e + ) + ) return False time.sleep_us(500) offset += chunk if self._dbg and offset >= next_report: - print("[BHI385] loadFirmware: [2/5] {}% ({}/{} bytes)".format( - (offset * 100) // fw_len, offset, fw_len)) + print( + "[BHI385] loadFirmware: [2/5] {}% ({}/{} bytes)".format( + (offset * 100) // fw_len, offset, fw_len + ) + ) next_report += report_step if self._dbg: print("[BHI385] loadFirmware: [2/5] upload complete") err_now = self._i2c_read_reg(BHI385_REG_ERROR_VAL, 1) - print("[BHI385] loadFirmware: [2/5] post-upload boot status = 0x{:02X}, " - "error = 0x{:02X}".format( - self.getBootStatus(), err_now[0] if err_now else 0)) + print( + "[BHI385] loadFirmware: [2/5] post-upload boot status = 0x{:02X}, " + "error = 0x{:02X}".format( + self.getBootStatus(), err_now[0] if err_now else 0 + ) + ) # Step 3: wait for CRC verify result. # Bit 5 (FW_VER_DONE) or bit 6 (FW_VER_ERR) will be set when the ROM # bootloader finishes checking the uploaded image. if self._dbg: - print("[BHI385] loadFirmware: [3/5] waiting for CRC verify " - "(timeout {} ms)...".format(BHI385_T_FW_VER_MS)) + print( + "[BHI385] loadFirmware: [3/5] waiting for CRC verify " + "(timeout {} ms)...".format(BHI385_T_FW_VER_MS) + ) if not self._poll_boot_status( - BHI385_BOOT_FW_VER_DONE | BHI385_BOOT_FW_VER_ERR, BHI385_T_FW_VER_MS): + BHI385_BOOT_FW_VER_DONE | BHI385_BOOT_FW_VER_ERR, BHI385_T_FW_VER_MS + ): if self._dbg: - print("[BHI385] loadFirmware: [3/5] TIMEOUT, " - "boot status = 0x{:02X}".format(self.getBootStatus())) + print( + "[BHI385] loadFirmware: [3/5] TIMEOUT, " + "boot status = 0x{:02X}".format(self.getBootStatus()) + ) return False boot_st = self.getBootStatus() if self._dbg: - print("[BHI385] loadFirmware: [3/5] boot status after verify = " - "0x{:02X}".format(boot_st)) + print( + "[BHI385] loadFirmware: [3/5] boot status after verify = " + "0x{:02X}".format(boot_st) + ) if boot_st & BHI385_BOOT_FW_VER_ERR: if self._dbg: err_val = self._i2c_read_reg(BHI385_REG_ERROR_VAL, 1) - print("[BHI385] loadFirmware: [3/5] FAILED: CRC mismatch, " - "error = 0x{:02X}".format(err_val[0] if err_val else 0)) + print( + "[BHI385] loadFirmware: [3/5] FAILED: CRC mismatch, " + "error = 0x{:02X}".format(err_val[0] if err_val else 0) + ) return False if self._dbg: print("[BHI385] loadFirmware: [3/5] CRC OK") # Step 4: send the "Boot Program RAM" command. - boot_cmd = bytes([ - BHI385_CMD_BOOT_FW & 0xFF, - (BHI385_CMD_BOOT_FW >> 8) & 0xFF, - 0x00, - 0x00, - ]) + boot_cmd = bytes( + [ + BHI385_CMD_BOOT_FW & 0xFF, + (BHI385_CMD_BOOT_FW >> 8) & 0xFF, + 0x00, + 0x00, + ] + ) if self._dbg: print("[BHI385] loadFirmware: [4/5] sending boot command... ", end="") @@ -450,20 +493,26 @@ def loadFirmware(self, firmware): # Step 5: wait for firmware to boot and host interface to become ready. if self._dbg: - print("[BHI385] loadFirmware: [5/5] waiting for firmware boot " - "(timeout {} ms)...".format(BHI385_T_BOOT_FW_MS)) + print( + "[BHI385] loadFirmware: [5/5] waiting for firmware boot " + "(timeout {} ms)...".format(BHI385_T_BOOT_FW_MS) + ) time.sleep_ms(85) # Typical firmware boot time is 81 ms if not self._poll_boot_status(BHI385_BOOT_HOST_IFACE_RDY, BHI385_T_BOOT_FW_MS): if self._dbg: - print("[BHI385] loadFirmware: [5/5] TIMEOUT, " - "boot status = 0x{:02X}".format(self.getBootStatus())) + print( + "[BHI385] loadFirmware: [5/5] TIMEOUT, " + "boot status = 0x{:02X}".format(self.getBootStatus()) + ) return False if self._dbg: - print("[BHI385] loadFirmware: [5/5] firmware booted. " - "Boot status = 0x{:02X}".format(self.getBootStatus())) + print( + "[BHI385] loadFirmware: [5/5] firmware booted. " + "Boot status = 0x{:02X}".format(self.getBootStatus()) + ) # Clear HOST_INTERFACE_CTRL (0x06) to release AP_SUSPENDED. While # AP_SUSPENDED is set the firmware buffers all sensor events internally @@ -696,8 +745,13 @@ def getQuatAccuracyDeg(self): def getQuatData(self): """Return last quaternion reading as (x, y, z, w, accuracyDeg) tuple.""" - return (self._quat[0], self._quat[1], self._quat[2], - self._quat[3], self._quat[4]) + return ( + self._quat[0], + self._quat[1], + self._quat[2], + self._quat[3], + self._quat[4], + ) def getStepCount(self): """Return cumulative step count.""" @@ -821,10 +875,16 @@ def _channel_write(self, channel, data): while offset < length: chunk = min(length - offset, BHI385_I2C_CHUNK_SIZE) try: - self._i2c.writeto_mem(self._addr, channel, data[offset:offset + chunk]) + self._i2c.writeto_mem( + self._addr, channel, data[offset : offset + chunk] + ) except OSError as e: if self._dbg: - print("[BHI385] channel write failed ch 0x{:02X}: {}".format(channel, e)) + print( + "[BHI385] channel write failed ch 0x{:02X}: {}".format( + channel, e + ) + ) return False offset += chunk return True @@ -900,7 +960,7 @@ def _send_command(self, cmd_id, params=None): pkt[1] = (cmd_id >> 8) & 0xFF pkt[2] = padded_param_len & 0xFF pkt[3] = (padded_param_len >> 8) & 0xFF - pkt[4:4 + param_len] = params + pkt[4 : 4 + param_len] = params return self._channel_write(BHI385_CH_CMD, pkt) @@ -915,13 +975,17 @@ def _configure_sensor(self, sensor_id, rate_hz, latency_ms=0): :return: True on success """ rate_bytes = struct.pack("> 8) & 0xFF, - (latency_ms >> 16) & 0xFF, - ])) + params = ( + bytes([sensor_id]) + + rate_bytes + + bytes( + [ + latency_ms & 0xFF, + (latency_ms >> 8) & 0xFF, + (latency_ms >> 16) & 0xFF, + ] + ) + ) return self._send_command(BHI385_CMD_CONFIGURE_SENSOR, params) def _change_sensor_range(self, sensor_id, range_val): @@ -964,8 +1028,9 @@ def _set_wrist_gesture_phys_param(self, hand): # channel operates in synchronous parameter-response mode hif_ctrl_data = self._i2c_read_reg(BHI385_REG_HOST_INTF_CTRL, 1) hif_ctrl_saved = hif_ctrl_data[0] if hif_ctrl_data else 0 - self._i2c_write_reg(BHI385_REG_HOST_INTF_CTRL, - hif_ctrl_saved & ~BHI385_HIF_CTRL_ASYNC_STATUS) + self._i2c_write_reg( + BHI385_REG_HOST_INTF_CTRL, hif_ctrl_saved & ~BHI385_HIF_CTRL_ASYNC_STATUS + ) # Send read-request: cmd=0x0E38, 4-byte payload = [ctrl_read=0x87, 0, 0, 0] read_req = bytes([0x38, 0x0E, 0x04, 0x00, 0x87, 0x00, 0x00, 0x00]) @@ -998,18 +1063,28 @@ def _set_wrist_gesture_phys_param(self, hand): remain = resp[2] | (resp[3] << 8) ctrl_code = resp[4] - if code == BHI385_PARAM_WRIST_GEST_PHY and remain == 21 and ctrl_code == 0x07: + if ( + code == BHI385_PARAM_WRIST_GEST_PHY + and remain == 21 + and ctrl_code == 0x07 + ): # resp[5..24] = config[0..19]; device_pos is at config[18] = resp[23] resp[23] = hand # Write back: cmd=0x0E38, payload=[ctrl_code=0x07][config[0..19]] = 21 bytes - success = self._send_command(BHI385_PARAM_WRIST_GEST_PHY, - bytes(resp[4:25])) + success = self._send_command( + BHI385_PARAM_WRIST_GEST_PHY, bytes(resp[4:25]) + ) elif self._dbg: - print("[BHI385] _set_wrist_gesture_phys_param: unexpected response " - "code=0x{:04X} remain={} ctrlCode=0x{:02X}".format( - code, remain, ctrl_code)) + print( + "[BHI385] _set_wrist_gesture_phys_param: unexpected response " + "code=0x{:04X} remain={} ctrlCode=0x{:02X}".format( + code, remain, ctrl_code + ) + ) elif self._dbg: - print("[BHI385] _set_wrist_gesture_phys_param: timeout waiting for STATUS FIFO") + print( + "[BHI385] _set_wrist_gesture_phys_param: timeout waiting for STATUS FIFO" + ) # Restore HOST_INTF_CTRL self._i2c_write_reg(BHI385_REG_HOST_INTF_CTRL, hif_ctrl_saved) @@ -1066,15 +1141,15 @@ def _parse_event(self, buf, length, offset): # --- WU FIFO system events (0xF5-0xF8) --- if event_id == BHI385_FIFO_TS_SMALL_DLT_WU: - return 2 # 1 ID + 1 delta byte + return 2 # 1 ID + 1 delta byte if event_id == BHI385_FIFO_TS_LARGE_DLT_WU: - return 3 # 1 ID + 2 delta bytes + return 3 # 1 ID + 2 delta bytes if event_id == BHI385_FIFO_TS_FULL_WU: - return 6 # 1 ID + 5 timestamp bytes + return 6 # 1 ID + 5 timestamp bytes if event_id == BHI385_FIFO_META_WU: - return 4 # 1 ID + 3 meta bytes + return 4 # 1 ID + 3 meta bytes if event_id == 0xF9: - return 1 # Invalid — skip to avoid loop + return 1 # Invalid — skip to avoid loop if event_id == BHI385_FIFO_DEBUG_MSG: return 18 # 1 ID + 17 debug bytes @@ -1155,10 +1230,12 @@ def _parse_event(self, buf, length, offset): # --- Step counter event (5 bytes: ID + uint32 LE step count) --- if event_id == self._stc_sensor_id: if offset + 5 <= length: - self._step_count = (buf[offset + 1] - | (buf[offset + 2] << 8) - | (buf[offset + 3] << 16) - | (buf[offset + 4] << 24)) + self._step_count = ( + buf[offset + 1] + | (buf[offset + 2] << 8) + | (buf[offset + 3] << 16) + | (buf[offset + 4] << 24) + ) self._step_updated = True return 5