スキル一覧に戻る
njfdev

sensor-drivers

by njfdev

The code for the entire payload system onboard the 2025 NCSSM HPR rocket named C.A.V.E.R.N.

1🍴 0📅 2026年1月18日
GitHubで見るManusで実行

SKILL.md


name: sensor-drivers description: Guidance for embedded sensor drivers used in the payload. Use when working with BMP390, ENS160, LSM6DSOX, LIS3MDL, GPS, IMU, barometer, or any sensor integration. keywords: [bmp390, ens160, lsm6dsox, lis3mdl, gps, imu, barometer, magnetometer, accelerometer, gyroscope, pressure, temperature, altitude, i2c, sensor, embedded-hal]

Payload Sensor Drivers

Hardware Configuration

SensorTypeBusSDASCLI2C Address
BMP390BarometerI2C1GP2GP30x77 (SDO high)
LSM6DSOX6-axis IMUI2C0GP28GP290x6A (SA0 low)
LIS3MDLMagnetometerI2C0GP28GP290x1C (SA1 low)

BMP390 - Barometric Pressure Sensor

Datasheet: Bosch BMP390

Specifications

  • Pressure range: 300-1250 hPa
  • Relative accuracy: ±3 Pa (~0.25m altitude)
  • Absolute accuracy: ±0.40 hPa
  • Temperature: -40 to +85°C (±0.5°C accuracy)
  • Power: 3.2 µA @ 1Hz

I2C Address

  • 0x76: SDO pin connected to GND
  • 0x77: SDO pin connected to VDDIO (3.3V) ← Our configuration

Key Registers

RegisterAddressDescription
CHIP_ID0x00Chip ID (reads 0x60)
ERR_REG0x02Error register
STATUS0x03Sensor status
DATA_0-50x04-0x09Pressure and temperature data
INT_STATUS0x11Interrupt status
PWR_CTRL0x1BPower control (enable press/temp)
OSR0x1COversampling settings
ODR0x1DOutput data rate
CONFIG0x1FIIR filter config
CALIB_DATA0x31-0x45Calibration coefficients (21 bytes)
CMD0x7ECommand register

Power Modes

  • Sleep: No measurements (default after reset)
  • Forced: Single measurement, then sleep
  • Normal: Continuous measurements with standby

Initialization Sequence

const BMP390_ADDR: u8 = 0x77;
const CHIP_ID: u8 = 0x00;
const PWR_CTRL: u8 = 0x1B;
const OSR: u8 = 0x1C;
const ODR: u8 = 0x1D;

// 1. Read and verify chip ID (should be 0x60)
let mut id = [0u8];
i2c.write_read(BMP390_ADDR, &[CHIP_ID], &mut id).await?;
assert_eq!(id[0], 0x60);

// 2. Read calibration data (21 bytes from 0x31)
let mut calib = [0u8; 21];
i2c.write_read(BMP390_ADDR, &[0x31], &mut calib).await?;

// 3. Configure oversampling (x8 pressure, x1 temp)
i2c.write(BMP390_ADDR, &[OSR, 0b00000011]).await?;

// 4. Configure ODR (50 Hz = 0x02)
i2c.write(BMP390_ADDR, &[ODR, 0x02]).await?;

// 5. Enable pressure and temp, normal mode
// PWR_CTRL: press_en=1, temp_en=1, mode=normal(11)
i2c.write(BMP390_ADDR, &[PWR_CTRL, 0b00110011]).await?;

Reading Data

const DATA_0: u8 = 0x04;

// Read 6 bytes: pressure (3) + temperature (3)
let mut data = [0u8; 6];
i2c.write_read(BMP390_ADDR, &[DATA_0], &mut data).await?;

// Pressure: 24-bit unsigned (XLSB, LSB, MSB)
let press_raw = (data[2] as u32) << 16 | (data[1] as u32) << 8 | data[0] as u32;

// Temperature: 24-bit unsigned (XLSB, LSB, MSB)
let temp_raw = (data[5] as u32) << 16 | (data[4] as u32) << 8 | data[3] as u32;

// Apply compensation algorithm from datasheet using calibration data

LSM6DSOX - 6-Axis IMU (Accelerometer + Gyroscope)

Datasheet: ST LSM6DSOX

Specifications

  • Accelerometer: ±2/±4/±8/±16 g
  • Gyroscope: ±125/±250/±500/±1000/±2000 dps
  • ODR: up to 6.66 kHz
  • Power: 0.55 mA in high-performance mode

I2C Address

  • 0x6A: SA0/SDO pin connected to GND ← Our configuration
  • 0x6B: SA0/SDO pin connected to VDDIO

Key Registers

RegisterAddressDescription
FUNC_CFG_ACCESS0x01Embedded functions config
WHO_AM_I0x0FDevice ID (reads 0x6C)
CTRL1_XL0x10Accelerometer control
CTRL2_G0x11Gyroscope control
CTRL3_C0x12Control register 3
CTRL6_C0x15Accelerometer power mode
CTRL7_G0x16Gyroscope power mode
STATUS_REG0x1EData ready status
OUTX_L_G0x22Gyro X low byte
OUTX_H_G0x23Gyro X high byte
OUTY_L_G0x24Gyro Y low byte
OUTY_H_G0x25Gyro Y high byte
OUTZ_L_G0x26Gyro Z low byte
OUTZ_H_G0x27Gyro Z high byte
OUTX_L_A0x28Accel X low byte
OUTX_H_A0x29Accel X high byte
OUTY_L_A0x2AAccel Y low byte
OUTY_H_A0x2BAccel Y high byte
OUTZ_L_A0x2CAccel Z low byte
OUTZ_H_A0x2DAccel Z high byte

CTRL1_XL (Accelerometer Control)

[7:4] ODR_XL: Output data rate
      0000 = Power-down
      0001 = 12.5 Hz
      0010 = 26 Hz
      0011 = 52 Hz
      0100 = 104 Hz
      0101 = 208 Hz
      0110 = 416 Hz
      0111 = 833 Hz
      1000 = 1.66 kHz
      1001 = 3.33 kHz
      1010 = 6.66 kHz

[3:2] FS_XL: Full-scale selection
      00 = ±2 g
      01 = ±16 g
      10 = ±4 g
      11 = ±8 g

[1] LPF2_XL_EN: Low-pass filter enable
[0] (unused)

CTRL2_G (Gyroscope Control)

[7:4] ODR_G: Output data rate (same as accelerometer)

[3:2] FS_G: Full-scale selection
      00 = ±250 dps
      01 = ±500 dps
      10 = ±1000 dps
      11 = ±2000 dps

[1] FS_125: 125 dps full-scale (overrides FS_G if set)
[0] (unused)

Initialization Sequence

const LSM6DSOX_ADDR: u8 = 0x6A;
const WHO_AM_I: u8 = 0x0F;
const CTRL1_XL: u8 = 0x10;
const CTRL2_G: u8 = 0x11;
const CTRL3_C: u8 = 0x12;

// 1. Read and verify WHO_AM_I (should be 0x6C)
let mut who = [0u8];
i2c.write_read(LSM6DSOX_ADDR, &[WHO_AM_I], &mut who).await?;
assert_eq!(who[0], 0x6C);

// 2. Software reset
i2c.write(LSM6DSOX_ADDR, &[CTRL3_C, 0x01]).await?;
Timer::after_millis(10).await;

// 3. Configure accelerometer: 104 Hz, ±16g
// ODR=0100, FS=01 (±16g) -> 0b0100_0100 = 0x44
i2c.write(LSM6DSOX_ADDR, &[CTRL1_XL, 0x44]).await?;

// 4. Configure gyroscope: 104 Hz, ±2000 dps
// ODR=0100, FS=11 (±2000 dps) -> 0b0100_1100 = 0x4C
i2c.write(LSM6DSOX_ADDR, &[CTRL2_G, 0x4C]).await?;

Reading Data

const OUTX_L_G: u8 = 0x22;

// Read 12 bytes: gyro (6) + accel (6)
let mut data = [0u8; 12];
i2c.write_read(LSM6DSOX_ADDR, &[OUTX_L_G], &mut data).await?;

// Gyroscope (signed 16-bit, dps)
let gx = i16::from_le_bytes([data[0], data[1]]);
let gy = i16::from_le_bytes([data[2], data[3]]);
let gz = i16::from_le_bytes([data[4], data[5]]);

// Accelerometer (signed 16-bit, g)
let ax = i16::from_le_bytes([data[6], data[7]]);
let ay = i16::from_le_bytes([data[8], data[9]]);
let az = i16::from_le_bytes([data[10], data[11]]);

// Convert to physical units
// For ±16g: sensitivity = 0.488 mg/LSB
// For ±2000 dps: sensitivity = 70 mdps/LSB

LIS3MDL - 3-Axis Magnetometer

Datasheet: ST LIS3MDL

Specifications

  • Range: ±4/±8/±12/±16 gauss
  • ODR: up to 1000 Hz (ultra-high performance)
  • Power: 270 µA @ 80 Hz

I2C Address

  • 0x1C: SA1/SDO pin connected to GND ← Our configuration
  • 0x1E: SA1/SDO pin connected to VDDIO

Key Registers

RegisterAddressDescription
WHO_AM_I0x0FDevice ID (reads 0x3D)
CTRL_REG10x20Control register 1 (ODR, mode)
CTRL_REG20x21Control register 2 (full-scale)
CTRL_REG30x22Control register 3 (power mode)
CTRL_REG40x23Control register 4 (Z-axis mode)
CTRL_REG50x24Control register 5 (block update)
STATUS_REG0x27Data status
OUT_X_L0x28X output low byte
OUT_X_H0x29X output high byte
OUT_Y_L0x2AY output low byte
OUT_Y_H0x2BY output high byte
OUT_Z_L0x2CZ output low byte
OUT_Z_H0x2DZ output high byte
TEMP_OUT_L0x2ETemperature low byte
TEMP_OUT_H0x2FTemperature high byte

CTRL_REG1 (ODR and XY Performance)

[7] TEMP_EN: Temperature sensor enable
[6:5] OM: X/Y-axis operative mode
      00 = Low-power
      01 = Medium-performance
      10 = High-performance
      11 = Ultra-high performance

[4:2] DO: Output data rate
      000 = 0.625 Hz
      001 = 1.25 Hz
      010 = 2.5 Hz
      011 = 5 Hz
      100 = 10 Hz
      101 = 20 Hz
      110 = 40 Hz
      111 = 80 Hz

[1] FAST_ODR: Enable rates >80 Hz
[0] ST: Self-test enable

CTRL_REG2 (Full-Scale Selection)

[6:5] FS: Full-scale configuration
      00 = ±4 gauss
      01 = ±8 gauss
      10 = ±12 gauss
      11 = ±16 gauss

CTRL_REG3 (Operating Mode)

[1:0] MD: Operating mode
      00 = Continuous-conversion
      01 = Single-conversion
      10 = Power-down (default)
      11 = Power-down

Initialization Sequence

const LIS3MDL_ADDR: u8 = 0x1C;
const WHO_AM_I: u8 = 0x0F;
const CTRL_REG1: u8 = 0x20;
const CTRL_REG2: u8 = 0x21;
const CTRL_REG3: u8 = 0x22;
const CTRL_REG4: u8 = 0x23;

// 1. Read and verify WHO_AM_I (should be 0x3D)
let mut who = [0u8];
i2c.write_read(LIS3MDL_ADDR, &[WHO_AM_I], &mut who).await?;
assert_eq!(who[0], 0x3D);

// 2. Configure CTRL_REG1: temp enable, ultra-high perf XY, 80 Hz
// TEMP_EN=1, OM=11, DO=111 -> 0b1111_1100 = 0xFC
i2c.write(LIS3MDL_ADDR, &[CTRL_REG1, 0xFC]).await?;

// 3. Configure CTRL_REG2: ±4 gauss (default, 0x00)
i2c.write(LIS3MDL_ADDR, &[CTRL_REG2, 0x00]).await?;

// 4. Configure CTRL_REG3: continuous mode
i2c.write(LIS3MDL_ADDR, &[CTRL_REG3, 0x00]).await?;

// 5. Configure CTRL_REG4: ultra-high performance Z-axis
// OMZ=11 -> 0b0000_1100 = 0x0C
i2c.write(LIS3MDL_ADDR, &[CTRL_REG4, 0x0C]).await?;

Reading Data

const OUT_X_L: u8 = 0x28;

// Read 6 bytes (can also read with auto-increment: 0x28 | 0x80)
let mut data = [0u8; 6];
i2c.write_read(LIS3MDL_ADDR, &[OUT_X_L | 0x80], &mut data).await?;

// Magnetometer (signed 16-bit, gauss)
let mx = i16::from_le_bytes([data[0], data[1]]);
let my = i16::from_le_bytes([data[2], data[3]]);
let mz = i16::from_le_bytes([data[4], data[5]]);

// Convert to physical units
// For ±4 gauss: sensitivity = 6842 LSB/gauss

Embassy I2C Setup for RP2040

use embassy_rp::i2c::{I2c, Config, InterruptHandler};
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::{I2C0, I2C1};

bind_interrupts!(struct Irqs {
    I2C0_IRQ => InterruptHandler<I2C0>;
    I2C1_IRQ => InterruptHandler<I2C1>;
});

// I2C0 for IMU (LSM6DSOX + LIS3MDL): SDA=GP28, SCL=GP29
let mut config = Config::default();
config.frequency = 400_000; // 400 kHz Fast mode
let i2c0 = I2c::new_async(p.I2C0, p.PIN_29, p.PIN_28, Irqs, config);

// I2C1 for Barometer (BMP390): SDA=GP2, SCL=GP3
let i2c1 = I2c::new_async(p.I2C1, p.PIN_3, p.PIN_2, Irqs, config);

Note: Embassy I2C::new_async takes pins as (SCL, SDA) not (SDA, SCL).


Sensitivity and Conversion

BMP390

Requires calibration coefficients from registers 0x31-0x45. See datasheet section 8.4 for compensation formulas.

LSM6DSOX

Accel RangeSensitivity
±2 g0.061 mg/LSB
±4 g0.122 mg/LSB
±8 g0.244 mg/LSB
±16 g0.488 mg/LSB
Gyro RangeSensitivity
±125 dps4.375 mdps/LSB
±250 dps8.75 mdps/LSB
±500 dps17.5 mdps/LSB
±1000 dps35 mdps/LSB
±2000 dps70 mdps/LSB

LIS3MDL

Mag RangeSensitivity
±4 gauss6842 LSB/gauss
±8 gauss3421 LSB/gauss
±12 gauss2281 LSB/gauss
±16 gauss1711 LSB/gauss

Troubleshooting

Sensor Not Responding

  1. Check I2C address (measure SDO/SA0/SA1 pin voltage)
  2. Verify pull-up resistors on SDA/SCL (2.2k-10k to 3.3V)
  3. Check wiring: SDA to SDA, SCL to SCL
  4. Reduce I2C frequency to 100kHz for debugging

Wrong WHO_AM_I Value

  • 0xFF: No device responding (check address, wiring)
  • 0x00: Device in reset or powered off
  • Other: Wrong device or address conflict

No Data Updates

  • Check power mode (devices start in power-down)
  • Verify ODR is set (output data rate > 0)
  • Check STATUS register for data ready flags

I2C Reads Hanging

If sensor reads hang after successful initialization:

  1. Test sensors individually - add debug output between each sensor read to identify which one hangs
  2. Check for I2C bus conflicts - multiple sensors on same bus can cause issues if one holds SDA/SCL low
  3. Add small delays between operations - some sensors need settling time between init and first read
  4. Verify async I2C trait is in scope - must use embedded_hal_async::i2c::I2c as I2cTrait; in each module

Debugging Pattern

When sensor reads aren't working, use this progressive debugging approach:

// Add debug output before/after each operation
let _ = class.write_packet(b"SENSOR_NAME...\r\n").await;
if sensor::read(&mut i2c, &mut data).await.is_err() {
    let _ = class.write_packet(b"SENSOR_NAME ERR\r\n").await;
}
let _ = class.write_packet(b"SENSOR_NAME DONE\r\n").await;

This reveals exactly which operation hangs - invaluable for I2C debugging.


Working Code Reference

The current pico_logger implementation in pico_logger/src/sensors/ provides working drivers for all three sensors. Key patterns:

  1. Each sensor in its own module with init() and read() functions
  2. I2C trait imported in each module: use embedded_hal_async::i2c::I2c as I2cTrait;
  3. Error handling via Result - init returns error if WHO_AM_I doesn't match
  4. Shared SensorData struct passed by mutable reference to reads

スコア

総合スコア

50/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です