Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Hayasen is a lightweight, no_std-friendly Rust crate designed to provide a clean and modular interface for a wide range of sensors. While initial support focuses on the MPU9250 inertial measurement unit (IMU), the architecture is built to easily extend to other sensors and communication protocols beyond I²C.

This crate is ideal for:

  • Embedded systems running on microcontrollers (e.g., STM32, RP2040, ESP32, etc.).
  • Bare-metal Rust applications requiring efficient, hardware-safe abstraction layers.
  • Developers seeking unified error handling, modular sensor APIs, and minimal runtime overhead.

Key highlights:

  • Extensible design for multiple sensor families.
  • Unified error management through a generic Error<E> enum.
  • Clear separation of concerns across modules for maintainability.
  • no_std support for resource-constrained environments.

Features of Hayasen

Overview

Hayasen is designed to be a lightweight, modular sensor driver crate with a focus on embedded systems. It aims to provide a unified interface for interacting with various sensors, starting with I²C-based devices and expanding to support other communication protocols in future releases.


Key Features

1. Unified Error Handling

  • Centralized Error enum for consistent error reporting across all drivers.

  • Distinguishes between:

    • Low-level I²C errors
    • Data integrity issues
    • Configuration and initialization failures
    • Sensor-specific errors
  • Convenience methods (is_i2c_error, is_config_error, into_i2c_error) for error inspection and recovery.

2. MPU9250 Sensor Support

  • Initial implementation supports the MPU9250 IMU sensor.

  • Provides methods for:

    • Initialization and configuration of accelerometer, gyroscope, and magnetometer.
    • Reading raw and processed data from all sensor units.
    • Power management (e.g., wake/sleep modes).
  • Modular driver structure allows easy adaptation to similar IMU devices.

3. Modular Design for Sensor Expansion

  • Core library is structured to support additional sensors without rewriting core logic.

  • Shared abstractions for configuration, data reading, and error handling.

  • Planned future support for:

    • SPI-based sensors
    • Analog sensors (via ADC interfaces)
    • Digital sensors beyond I²C.

4. Lightweight and no_std Compatible

  • Designed for embedded environments with constrained resources.
  • Avoids allocations and unnecessary dependencies.
  • Compatible with no_std for use in bare-metal microcontrollers.

5. Extensible Configuration System

  • Supports runtime configuration of sensor parameters.
  • Designed to allow sensor-specific tuning (e.g., sensitivity ranges, filter settings).
  • Provides error reporting for invalid configurations via Error::ConfigError.

6. Developer-Friendly API

  • Intuitive function naming and structured module layout.
  • Encourages readable, idiomatic Rust code.
  • Provides clear debugging output via Debug trait implementations.

Future Features (Planned)

  • Support for additional sensors (environmental, proximity, etc.).
  • Integration with embedded-hal traits for seamless compatibility with Rust embedded ecosystem.
  • Optional calibration utilities and sensor fusion algorithms.
  • Benchmarking and performance profiling tools for real-time applications.

Installation Guide for Hayasen

1. Prerequisites

  • Rust Toolchain: Ensure that Rust (with Cargo) is installed. You can install it using:

    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    
  • Embedded Development Setup:

    • Install the target for your microcontroller (e.g., ARM Cortex-M):

      rustup target add thumbv7em-none-eabihf
      
    • Ensure you have an appropriate linker and build tools installed.

  • Optional: For cross-compilation, tools like probe-rs or cargo-embed can be used.


2. Adding Hayasen to Your Project

Option A: Using Cargo.toml

Add the following line to your project's Cargo.toml under [dependencies]:

[dependencies]
hayasen = { git = "https://github.com/Vaishnav-Sabari-Girish/Hayasen" }

Option B: Using cargo add

You can add Hayasen directly via Cargo command:

cargo add --git https://github.com/Vaishnav-Sabari-Girish/Hayasen

This ensures that your project is always linked to the latest version of the crate.


To view the crate's documentation locally:

cargo doc --open

This command builds and opens the documentation in your browser.


4. Example Environment Setup

A minimal Cargo.toml for a project using Hayasen might look like:

[package]
name = "sensor_app"
version = "0.1.0"
edition = "2021"

[dependencies]
hayasen = { git = "https://github.com/Vaishnav-Sabari-Girish/Hayasen" }
embedded-hal = "0.2"

5. Verifying Installation

Run a test build to ensure everything is set up correctly:

cargo build --target thumbv7em-none-eabihf

If successful, you're ready to start integrating Hayasen with your embedded projects.

MPU9250 Usage Guide

Overview

The Hayasen MPU9250 library provides a comprehensive interface for working with the MPU9250 9-axis motion tracking device. This guide demonstrates how to use the library in various scenarios, from basic sensor reading to advanced configuration.

Please Note

Please note that the I2C instance used here currently is of datatype TWIM which corresponds to the I2C instance of a nRF Nordic microcontroller. So basically hayasen currently only supports Nordic Microcontrollers

Basic Usage

Quick Start with Default Configuration

The simplest way to get started is using the create_default function from the mpu9250_hayasen module:

use hayasen::prelude::*;
use hayasen::mpu9250_hayasen;

fn main() -> Result<(), Error<YourI2cError>> {
    // Assume you have an I2C peripheral instance
    let i2c = setup_i2c(); // Your platform-specific I2C setup
    let mpu_address = 0x68; // Default MPU9250 I2C address
    
    // Create sensor with default configuration (2G accel, 250 DPS gyro)
    let mut sensor = mpu9250_hayasen::create_default(i2c, mpu_address)?;
    
    // Read all sensor data
    let (temperature, acceleration, angular_velocity) = mpu9250_hayasen::read_all(&mut sensor)?;
    
    println!("Temperature: {:.2}°C", temperature);
    println!("Acceleration [X, Y, Z]: [{:.3}, {:.3}, {:.3}] g", 
             acceleration[0], acceleration[1], acceleration[2]);
    println!("Angular Velocity [X, Y, Z]: [{:.3}, {:.3}, {:.3}] dps", 
             angular_velocity[0], angular_velocity[1], angular_velocity[2]);
    
    Ok(())
}

Individual Sensor Readings

You can read each sensor independently:

#![allow(unused)]
fn main() {
use hayasen::prelude::*;
use hayasen::mpu9250_hayasen;

fn read_individual_sensors() -> Result<(), Error<YourI2cError>> {
    let i2c = setup_i2c();
    let mut sensor = mpu9250_hayasen::create_default(i2c, 0x68)?;
    
    // Read acceleration only
    let accel = mpu9250_hayasen::read_acceleration(&mut sensor)?;
    println!("Acceleration: X={:.3}g, Y={:.3}g, Z={:.3}g", accel[0], accel[1], accel[2]);
    
    // Read gyroscope only
    let gyro = mpu9250_hayasen::read_angular_velocity(&mut sensor)?;
    println!("Angular Velocity: X={:.3}°/s, Y={:.3}°/s, Z={:.3}°/s", gyro[0], gyro[1], gyro[2]);
    
    // Read temperature only
    let temp = mpu9250_hayasen::read_temperature(&mut sensor)?;
    println!("Temperature: {:.2}°C", temp);
    
    Ok(())
}
}

Advanced Configuration

Manual Sensor Setup

For more control over sensor configuration, use the direct API:

#![allow(unused)]
fn main() {
use hayasen::prelude::*;

fn advanced_setup() -> Result<(), Error<YourI2cError>> {
    let i2c = setup_i2c();
    let mut sensor = Mpu9250::new(i2c, 0x68);
    
    // Manual initialization with custom ranges
    sensor.initialize_sensor(
        AccelRange::Range8G,      // Higher acceleration range
        GyroRange::Range1000Dps   // Higher angular velocity range
    )?;
    
    // Configure sample rate (divider from 1kHz base rate)
    // Sample rate = 1000Hz / (1 + divider)
    sensor.set_sample_rate(9)?; // 100Hz sample rate
    
    // Configure digital low-pass filter
    sensor.set_dlpf_config(DlpfConfig::Bandwidth184Hz)?;
    
    Ok(())
}
}

Reading Raw Data

For applications requiring raw ADC values:

#![allow(unused)]
fn main() {
use hayasen::prelude::*;

fn read_raw_data() -> Result<(), Error<YourI2cError>> {
    let i2c = setup_i2c();
    let mut sensor = Mpu9250::new(i2c, 0x68);
    sensor.initialize_sensor(AccelRange::Range2G, GyroRange::Range250Dps)?;
    
    // Read raw 16-bit values
    let raw_accel = sensor.read_accel_raw()?;
    let raw_gyro = sensor.read_gyro_raw()?;
    let raw_temp = sensor.read_temp_raw()?;
    
    println!("Raw Accelerometer: X={}, Y={}, Z={}", raw_accel[0], raw_accel[1], raw_accel[2]);
    println!("Raw Gyroscope: X={}, Y={}, Z={}", raw_gyro[0], raw_gyro[1], raw_gyro[2]);
    println!("Raw Temperature: {}", raw_temp);
    
    Ok(())
}
}

Power Management

Sleep Mode Operation

#![allow(unused)]
fn main() {
use hayasen::prelude::*;

fn power_management_example() -> Result<(), Error<YourI2cError>> {
    let i2c = setup_i2c();
    let mut sensor = Mpu9250::new(i2c, 0x68);
    sensor.initialize_sensor(AccelRange::Range2G, GyroRange::Range250Dps)?;
    
    // Normal operation
    let data = sensor.read_acceleration()?;
    println!("Before sleep: {:?}", data);
    
    // Enter sleep mode to save power
    sensor.enter_sleep_mode()?;
    println!("Sensor in sleep mode");
    
    // Wake up and resume operation
    sensor.wake_up()?;
    
    // Read data after waking up
    let data_after_wake = sensor.read_acceleration()?;
    println!("After wake: {:?}", data_after_wake);
    
    Ok(())
}
}

Real-World Applications

Motion Detection Example

#![allow(unused)]
fn main() {
use hayasen::prelude::*;
use hayasen::mpu9250_hayasen;

fn motion_detection_loop() -> Result<(), Error<YourI2cError>> {
    let i2c = setup_i2c();
    let mut sensor = mpu9250_hayasen::create_default(i2c, 0x68)?;
    
    // Motion detection thresholds
    const ACCEL_THRESHOLD: f32 = 0.1; // g
    const GYRO_THRESHOLD: f32 = 5.0;  // degrees per second
    
    loop {
        let (temp, accel, gyro) = mpu9250_hayasen::read_all(&mut sensor)?;
        
        // Calculate total acceleration magnitude (subtract gravity)
        let total_accel = (accel[0].powi(2) + accel[1].powi(2) + accel[2].powi(2)).sqrt();
        let motion_accel = (total_accel - 1.0).abs(); // Subtract 1g gravity
        
        // Calculate total angular velocity
        let total_gyro = (gyro[0].powi(2) + gyro[1].powi(2) + gyro[2].powi(2)).sqrt();
        
        // Detect motion
        if motion_accel > ACCEL_THRESHOLD || total_gyro > GYRO_THRESHOLD {
            println!("Motion detected! Accel: {:.3}g, Gyro: {:.3}°/s", motion_accel, total_gyro);
        }
        
        // Small delay between readings
        delay_ms(50);
    }
}
}

Data Logging Example

#![allow(unused)]
fn main() {
use hayasen::prelude::*;

fn data_logging_example() -> Result<(), Error<YourI2cError>> {
    let i2c = setup_i2c();
    let mut sensor = Mpu9250::new(i2c, 0x68);
    
    // Configure for high-precision data logging
    sensor.initialize_sensor(AccelRange::Range4G, GyroRange::Range500Dps)?;
    sensor.set_sample_rate(19)?; // 50Hz sampling
    sensor.set_dlpf_config(DlpfConfig::Bandwidth184Hz)?;
    
    let mut sample_count = 0;
    const MAX_SAMPLES: usize = 1000;
    
    while sample_count < MAX_SAMPLES {
        let timestamp = get_timestamp(); // Your platform-specific timestamp
        let accel = sensor.read_acceleration()?;
        let gyro = sensor.read_angular_velocity()?;
        let temp = sensor.read_temperature_celsius()?;
        
        // Log data (implement your own logging mechanism)
        log_data(timestamp, accel, gyro, temp);
        
        sample_count += 1;
        delay_ms(20); // 50Hz sampling
    }
    
    Ok(())
}
}

Configuration Options

Accelerometer Ranges

#![allow(unused)]
fn main() {
use hayasen::prelude::*;

// Available accelerometer ranges and their use cases
fn configure_accelerometer_ranges() -> Result<(), Error<YourI2cError>> {
    let i2c = setup_i2c();
    let mut sensor = Mpu9250::new(i2c, 0x68);
    
    // Choose range based on application:
    
    // For precise, low-acceleration measurements (e.g., tilt sensing)
    sensor.setup_accelerometer(AccelRange::Range2G)?;
    
    // For general motion detection
    sensor.setup_accelerometer(AccelRange::Range4G)?;
    
    // For high-impact applications (e.g., crash detection)
    sensor.setup_accelerometer(AccelRange::Range8G)?;
    
    // For extreme acceleration measurements
    sensor.setup_accelerometer(AccelRange::Range16G)?;
    
    Ok(())
}
}

Gyroscope Ranges

#![allow(unused)]
fn main() {
use hayasen::prelude::*;

fn configure_gyroscope_ranges() -> Result<(), Error<YourI2cError>> {
    let i2c = setup_i2c();
    let mut sensor = Mpu9250::new(i2c, 0x68);
    
    // Choose range based on expected rotation rates:
    
    // For slow, precise rotations (e.g., stabilization)
    sensor.setup_gyroscope(GyroRange::Range250Dps)?;
    
    // For moderate rotation rates (e.g., drone control)
    sensor.setup_gyroscope(GyroRange::Range500Dps)?;
    
    // For fast rotations (e.g., sports analysis)
    sensor.setup_gyroscope(GyroRange::Range1000Dps)?;
    
    // For very high rotation rates (e.g., spinning objects)
    sensor.setup_gyroscope(GyroRange::Range2000Dps)?;
    
    Ok(())
}
}

Error Handling

Comprehensive Error Handling

#![allow(unused)]
fn main() {
use hayasen::prelude::*;
use hayasen::mpu9250_hayasen;

fn robust_sensor_operation() {
    let i2c = setup_i2c();
    
    match mpu9250_hayasen::create_default(i2c, 0x68) {
        Ok(mut sensor) => {
            loop {
                match mpu9250_hayasen::read_all(&mut sensor) {
                    Ok((temp, accel, gyro)) => {
                        process_sensor_data(temp, accel, gyro);
                    },
                    Err(e) => {
                        match e {
                            Error::I2c(_) => {
                                println!("I2C communication error, retrying...");
                                delay_ms(100);
                                continue;
                            },
                            Error::NotDetected => {
                                println!("Sensor not detected, check wiring");
                                break;
                            },
                            Error::InvalidData => {
                                println!("Invalid data received, skipping reading");
                                continue;
                            },
                            Error::ConfigError => {
                                println!("Configuration error");
                                break;
                            },
                            Error::SensorSpecific(msg) => {
                                println!("Sensor-specific error: {}", msg);
                                break;
                            },
                        }
                    }
                }
                delay_ms(20);
            }
        },
        Err(e) => {
            println!("Failed to initialize sensor: {:?}", e);
        }
    }
}
}

Error Classification

#![allow(unused)]
fn main() {
use hayasen::prelude::*;

fn classify_errors(error: Error<YourI2cError>) {
    if error.is_i2c_error() {
        println!("Communication problem - check wiring and I2C bus");
    } else if error.is_config_error() {
        println!("Configuration issue - check sensor settings");
    }
    
    // Extract underlying I2C error if needed
    if let Some(i2c_err) = error.into_i2c_error() {
        handle_i2c_specific_error(i2c_err);
    }
}
}

Platform-Specific Examples

ESP32 Example (using esp-hal)

#![no_std]
#![no_main]

use esp_hal::{
    clock::ClockControl,
    i2c::I2C,
    peripherals::Peripherals,
    prelude::*,
    delay::Delay,
};
use hayasen::prelude::*;
use hayasen::mpu9250_hayasen;

#[entry]
fn main() -> ! {
    let peripherals = Peripherals::take();
    let system = peripherals.SYSTEM.split();
    let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
    
    // Setup I2C
    let i2c = I2C::new(
        peripherals.I2C0,
        peripherals.GPIO21, // SDA
        peripherals.GPIO22, // SCL
        100u32.kHz(),
        &clocks,
    );
    
    let mut delay = Delay::new(&clocks);
    
    // Initialize MPU9250
    let mut sensor = match mpu9250_hayasen::create_default(i2c, 0x68) {
        Ok(s) => s,
        Err(_) => {
            println!("Failed to initialize MPU9250");
            loop { delay.delay_ms(1000u32); }
        }
    };
    
    loop {
        match mpu9250_hayasen::read_all(&mut sensor) {
            Ok((temp, accel, gyro)) => {
                println!("T: {:.1}°C | A: [{:.2}, {:.2}, {:.2}]g | G: [{:.1}, {:.1}, {:.1}]°/s",
                        temp, accel[0], accel[1], accel[2], gyro[0], gyro[1], gyro[2]);
            },
            Err(_) => println!("Read error"),
        }
        delay.delay_ms(100u32);
    }
}

STM32 Example (using stm32f4xx-hal)

#![no_std]
#![no_main]

use panic_halt as _;
use cortex_m_rt::entry;
use stm32f4xx_hal::{
    pac,
    prelude::*,
    i2c::I2c,
};
use hayasen::prelude::*;

#[entry]
fn main() -> ! {
    let dp = pac::Peripherals::take().unwrap();
    let cp = cortex_m::peripheral::Peripherals::take().unwrap();
    
    let rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr.freeze();
    
    let gpiob = dp.GPIOB.split();
    let scl = gpiob.pb8.into_alternate_open_drain();
    let sda = gpiob.pb9.into_alternate_open_drain();
    
    let i2c = I2c::new(dp.I2C1, (scl, sda), 400.kHz(), &clocks);
    
    let mut delay = cortex_m::delay::Delay::new(cp.SYST, clocks.hclk().to_Hz());
    let mut sensor = Mpu9250::new(i2c, 0x68);
    
    // Custom initialization
    match sensor.initialize_sensor(AccelRange::Range4G, GyroRange::Range500Dps) {
        Ok(_) => {},
        Err(_) => loop { delay.delay_ms(1000u32); }
    }
    
    loop {
        if let Ok(accel) = sensor.read_acceleration() {
            // Process acceleration data
            process_motion_data(accel);
        }
        delay.delay_ms(50u32);
    }
}

Advanced Use Cases

Calibration and Offset Correction

#![allow(unused)]
fn main() {
use hayasen::prelude::*;

fn calibrate_sensor() -> Result<[f32; 6], Error<YourI2cError>> {
    let i2c = setup_i2c();
    let mut sensor = Mpu9250::new(i2c, 0x68);
    sensor.initialize_sensor(AccelRange::Range2G, GyroRange::Range250Dps)?;
    
    println!("Keep sensor stationary for calibration...");
    delay_ms(2000);
    
    let mut accel_offsets = [0.0f32; 3];
    let mut gyro_offsets = [0.0f32; 3];
    const SAMPLES: usize = 100;
    
    // Collect calibration samples
    for _ in 0..SAMPLES {
        let accel = sensor.read_acceleration()?;
        let gyro = sensor.read_angular_velocity()?;
        
        accel_offsets[0] += accel[0];
        accel_offsets[1] += accel[1];
        accel_offsets[2] += accel[2] - 1.0; // Subtract expected 1g on Z-axis
        
        gyro_offsets[0] += gyro[0];
        gyro_offsets[1] += gyro[1];
        gyro_offsets[2] += gyro[2];
        
        delay_ms(10);
    }
    
    // Calculate averages
    for i in 0..3 {
        accel_offsets[i] /= SAMPLES as f32;
        gyro_offsets[i] /= SAMPLES as f32;
    }
    
    println!("Calibration complete!");
    println!("Accel offsets: [{:.4}, {:.4}, {:.4}]", accel_offsets[0], accel_offsets[1], accel_offsets[2]);
    println!("Gyro offsets: [{:.4}, {:.4}, {:.4}]", gyro_offsets[0], gyro_offsets[1], gyro_offsets[2]);
    
    Ok([accel_offsets[0], accel_offsets[1], accel_offsets[2], 
        gyro_offsets[0], gyro_offsets[1], gyro_offsets[2]])
}
}

Orientation Estimation

#![allow(unused)]
fn main() {
use hayasen::prelude::*;

fn estimate_orientation() -> Result<(), Error<YourI2cError>> {
    let i2c = setup_i2c();
    let mut sensor = Mpu9250::new(i2c, 0x68);
    sensor.initialize_sensor(AccelRange::Range2G, GyroRange::Range250Dps)?;
    
    loop {
        let accel = sensor.read_acceleration()?;
        
        // Calculate roll and pitch from accelerometer (when stationary)
        let roll = accel[1].atan2(accel[2]) * 180.0 / core::f32::consts::PI;
        let pitch = (-accel[0]).atan2((accel[1].powi(2) + accel[2].powi(2)).sqrt()) 
                   * 180.0 / core::f32::consts::PI;
        
        println!("Roll: {:.2}°, Pitch: {:.2}°", roll, pitch);
        
        delay_ms(100);
    }
}
}

Multi-Sensor Setup

#![allow(unused)]
fn main() {
use hayasen::prelude::*;

fn multiple_sensors_example() -> Result<(), Error<YourI2cError>> {
    let i2c = setup_i2c();
    
    // MPU9250 can have two possible I2C addresses
    let mut sensor1 = Mpu9250::new(i2c, 0x68); // AD0 = LOW
    let mut sensor2 = Mpu9250::new(i2c, 0x69); // AD0 = HIGH
    
    // Initialize both sensors
    sensor1.initialize_sensor(AccelRange::Range2G, GyroRange::Range250Dps)?;
    sensor2.initialize_sensor(AccelRange::Range2G, GyroRange::Range250Dps)?;
    
    loop {
        let accel1 = sensor1.read_acceleration()?;
        let accel2 = sensor2.read_acceleration()?;
        
        println!("Sensor 1: [{:.3}, {:.3}, {:.3}]g", accel1[0], accel1[1], accel1[2]);
        println!("Sensor 2: [{:.3}, {:.3}, {:.3}]g", accel2[0], accel2[1], accel2[2]);
        
        delay_ms(100);
    }
}
}

Best Practices

Initialization Checklist

  1. Always verify sensor identity before configuration
  2. Configure power management early in initialization
  3. Set appropriate ranges based on your application requirements
  4. Configure sample rate to match your update frequency needs
  5. Handle errors gracefully with appropriate retry logic

Sample Rate Guidelines

#![allow(unused)]
fn main() {
// Sample rate formula: Sample Rate = 1000Hz / (1 + SMPRT_DIV)
sensor.set_sample_rate(0)?;   // 1000 Hz - High frequency applications
sensor.set_sample_rate(9)?;   // 100 Hz - General motion tracking
sensor.set_sample_rate(19)?;  // 50 Hz - Low power applications
sensor.set_sample_rate(99)?;  // 10 Hz - Very low power monitoring
}

Memory Usage Optimization

#![allow(unused)]
fn main() {
use hayasen::prelude::*;

// For memory-constrained systems, use raw readings when possible
fn memory_efficient_reading() -> Result<(), Error<YourI2cError>> {
    let i2c = setup_i2c();
    let mut sensor = Mpu9250::new(i2c, 0x68);
    sensor.initialize_sensor(AccelRange::Range2G, GyroRange::Range250Dps)?;
    
    // Read raw data to avoid floating-point operations
    let raw_accel = sensor.read_accel_raw()?;
    
    // Manual scaling only when needed
    const SCALE_2G: f32 = 2.0 / 32768.0;
    let scaled_x = raw_accel[0] as f32 * SCALE_2G;
    
    Ok(())
}
}

Troubleshooting

Common Issues and Solutions

Sensor Not Detected:

  • Check I2C wiring (SDA, SCL, VCC, GND)
  • Verify I2C address (0x68 or 0x69 depending on AD0 pin)
  • Ensure proper pull-up resistors on I2C lines

Inconsistent Readings:

  • Check power supply stability
  • Verify sample rate configuration
  • Consider using digital low-pass filter
  • Ensure sensor is properly mounted

High Noise:

  • Lower the digital low-pass filter bandwidth
  • Reduce sample rate if appropriate
  • Check for electromagnetic interference
  • Implement software filtering

Debug Helper Functions

#![allow(unused)]
fn main() {
use hayasen::prelude::*;

fn debug_sensor_status(sensor: &mut Mpu9250<impl I2c>) -> Result<(), Error<impl std::fmt::Debug>> {
    // Verify sensor is responding
    match sensor.verify_identity() {
        Ok(_) => println!("✓ Sensor identity verified"),
        Err(e) => println!("✗ Identity check failed: {:?}", e),
    }
    
    // Test basic readings
    match sensor.read_temp_raw() {
        Ok(temp) => println!("✓ Raw temperature reading: {}", temp),
        Err(e) => println!("✗ Temperature read failed: {:?}", e),
    }
    
    Ok(())
}
}

This documentation provides comprehensive examples for using the MPU9250 library across different scenarios and platforms. The examples progress from simple usage to advanced applications, helping developers implement motion sensing in their embedded projects effectively.

Development Notes - Hayasen Crate

Crate Structure

Module Organization

The crate follows a modular design pattern optimized for embedded systems:

hayasen/
├── src/
│   ├── lib.rs          # Main library entry point
│   ├── error.rs        # Unified error types
│   ├── functions.rs    # Function registry system
│   └── mpu9250.rs      # MPU9250 sensor implementation
├── examples/           # Usage examples
└── tests/              # Integration tests

Key Architectural Decisions

1. Feature-Gated Compilation

#![allow(unused)]
fn main() {
// lib.rs
#[cfg(feature = "mpu9250")]
pub mod mpu9250;

#[cfg(feature = "mpu9250")]
pub use crate::mpu9250::*;
}

Rationale: The crate uses conditional compilation to include only the sensors you need, reducing binary size and compilation time. This is crucial for resource-constrained embedded systems.

Usage:

[dependencies]
hayasen = { version = "x.x.x", features = ["mpu9250"] }

2. Prelude Pattern

#![allow(unused)]
fn main() {
pub mod prelude {
    pub use crate::error::Error;
    #[cfg(feature = "mpu9250")]
    pub use crate::mpu9250::*;
    pub use embedded_hal::i2c::I2c;
}
}

Purpose: Provides a convenient way to import commonly used types and traits. Users can simply use hayasen::prelude::*; to get everything they need.

3. Convenience Layer (mpu9250_hayasen)

#![allow(unused)]
fn main() {
pub mod mpu9250_hayasen {
    // Simplified API for common operations
    pub fn create_default<I2C, E>(i2c: I2C, address: u8) -> Result<mpu9250::Mpu9250<I2C>, Error<E>>
    pub fn read_all<I2C, E>(sensor: &mut mpu9250::Mpu9250<I2C>) -> Result<(f32, [f32; 3], [f32; 3]), Error<E>>
}
}

Benefits:

  • Reduces boilerplate for common operations
  • Provides sensible defaults for new users
  • Maintains access to low-level API for advanced users

Driver Architecture

Core Driver Structure

#![allow(unused)]
fn main() {
pub struct Mpu9250<I2C> {
    i2c: I2C,           // Owned I2C peripheral
    address: u8,        // Device I2C address
    accel_scale: f32,   // Cached scaling factor for acceleration
    gyro_scale: f32,    // Cached scaling factor for angular velocity
}
}

Design Principles:

  • Ownership Model: The driver takes ownership of the I2C peripheral to prevent conflicts
  • Zero-Cost Abstractions: Scale factors are pre-calculated and cached to avoid runtime divisions
  • Type Safety: Generic over I2C type for platform independence

State Management

The driver maintains minimal state to reduce memory footprint:

  • Scale factors are calculated once during configuration
  • No internal buffering or filtering (left to user applications)
  • Stateless register operations for maximum flexibility

no_std Compatibility

Core Requirements

#![allow(unused)]
#![no_std]
#![no_main]  // For applications, not the library itself
fn main() {
}

Memory Management

No Dynamic Allocation

#![allow(unused)]
fn main() {
// ✓ Stack-allocated arrays
let mut buffer = [0u8; 6];

// ✗ Avoid heap allocation
// let mut buffer = vec![0u8; 6];  // This would require std
}

Fixed-Size Buffers

All I2C operations use compile-time sized buffers:

#![allow(unused)]
fn main() {
pub fn read_accel_raw(&mut self) -> Result<[i16; 3], Error<E>> {
    let mut buffer = [0u8; 6];  // Fixed size, stack allocated
    // ...
}
}

Core Library Dependencies

The crate only depends on core and embedded-hal:

#![allow(unused)]
fn main() {
use core::fmt::{Debug, Formatter, Result};  // ✓ Core library
use embedded_hal::i2c::I2c;                  // ✓ Hardware abstraction

// ✗ Avoid std dependencies
// use std::vec::Vec;
// use std::collections::HashMap;
}

Floating Point Considerations

Target Compatibility

#![allow(unused)]
fn main() {
// The crate uses f32 for sensor data conversion
let temperature = (raw as f32) / 340.0 + 36.53;
}

Notes:

  • Uses f32 for better performance on 32-bit ARM Cortex-M
  • All calculations are optimized for embedded floating-point units
  • For targets without FPU, consider using fixed-point arithmetic wrapper

Alternative for No-FPU Targets

#![allow(unused)]
fn main() {
// Example fixed-point implementation (not included in crate)
pub fn read_acceleration_fixed(&mut self) -> Result<[i32; 3], Error<E>> {
    let raw = self.read_accel_raw()?;
    // Scale to milligee (1/1000 g) using integer math
    let scale_factor = (self.accel_scale * 1000.0) as i32;
    Ok([
        (raw[0] as i32 * scale_factor) / 1000,
        (raw[1] as i32 * scale_factor) / 1000,
        (raw[2] as i32 * scale_factor) / 1000,
    ])
}
}

Const Generics and Compile-Time Optimization

#![allow(unused)]
fn main() {
// Register addresses are compile-time constants
const WHO_AM_I: u8 = 0x75;
const ACCEL_XOUT_H: u8 = 0x3B;

// Enums are zero-cost abstractions
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AccelRange {
    Range2G,    // Compiles to simple integer values
    Range4G,
    Range8G,
    Range16G,
}
}

Safety Considerations

I2C Communication Safety

Transaction Atomicity

#![allow(unused)]
fn main() {
pub fn read_accel_raw(&mut self) -> Result<[i16; 3], Error<E>> {
    let mut buffer = [0u8; 6];
    self.i2c.write(self.address, &[ACCEL_XOUT_H])?;  // Register pointer
    self.i2c.read(self.address, &mut buffer)?;       // Read data
    // ... process buffer
}
}

Critical Points:

  • Atomic Operations: Each register read is a complete write-then-read transaction
  • Error Propagation: I2C errors are immediately propagated using ? operator
  • Buffer Safety: Fixed-size buffers prevent overflow issues

Address Validation

#![allow(unused)]
fn main() {
pub fn verify_identity(&mut self) -> Result<(), Error<E>> {
    let mut buffer = [0u8];
    self.i2c.write(self.address, &[WHO_AM_I])?;
    self.i2c.read(self.address, &mut buffer)?;
    if buffer[0] != WHO_AM_I_VALUE {
        return Err(Error::NotDetected);  // Explicit validation
    }
    Ok(())
}
}

Safety Features:

  • Device Verification: Always check WHO_AM_I register before operation
  • Type Safety: Rust's type system prevents incorrect address usage
  • Explicit Error States: Clear error types for different failure modes

Memory Safety

Buffer Bounds Checking

#![allow(unused)]
fn main() {
// Safe array access - Rust prevents buffer overruns at compile time
let x = ((buffer[0] as i16) << 8) | buffer[1] as i16;  // Safe indexing
let y = ((buffer[2] as i16) << 8) | buffer[3] as i16;  // Bounds checked
let z = ((buffer[4] as i16) << 8) | buffer[5] as i16;  // Compile-time verified
}

Integer Overflow Protection

#![allow(unused)]
fn main() {
// Safe conversion with explicit casting
let x = raw[0] as f32 * self.accel_scale;  // Explicit type conversion

// For temperature calculation, use checked arithmetic in critical applications
pub fn read_temperature_celsius_safe(&mut self) -> Result<f32, Error<E>> {
    let raw = self.read_temp_raw()?;
    let temp_f64 = (raw as f64) / 340.0 + 36.53;  // Higher precision intermediate
    Ok(temp_f64 as f32)
}
}

Register Access Safety

Read-Modify-Write Operations

#![allow(unused)]
fn main() {
pub fn wake_up(&mut self) -> Result<(), Error<E>> {
    let mut buffer = [0u8];
    self.i2c.write(self.address, &[PWR_MGMT_1])?;     // Read current value
    self.i2c.read(self.address, &mut buffer)?;
    let new_config = buffer[0] & 0xBF;                // Clear sleep bit safely
    self.i2c.write(self.address, &[PWR_MGMT_1, new_config])?;  // Write back
    Ok(())
}
}

Safety Measures:

  • Atomic RMW: Complete read-modify-write sequence
  • Bit Masking: Safe bit manipulation using explicit masks
  • State Preservation: Only modify intended bits, preserve others

Error Handling Strategy

Comprehensive Error Types

#![allow(unused)]
fn main() {
#[derive(Clone, PartialEq, Eq)]
pub enum Error<E> {
    I2c(E),                    // Underlying I2C errors
    InvalidData,               // Data validation failures
    NotDetected,              // Device not found/responding
    ConfigError,              // Configuration parameter errors
    SensorSpecific(&'static str), // Sensor-specific error messages
}
}

Error Recovery Patterns

#![allow(unused)]
fn main() {
// Recommended error handling pattern
fn safe_sensor_operation() -> Result<(), Error<YourI2cError>> {
    let mut retry_count = 0;
    const MAX_RETRIES: u8 = 3;
    
    loop {
        match sensor.read_acceleration() {
            Ok(data) => return Ok(process_data(data)),
            Err(Error::I2c(_)) if retry_count < MAX_RETRIES => {
                retry_count += 1;
                delay_ms(10);  // Brief delay before retry
                continue;
            },
            Err(e) => return Err(e),  // Propagate non-recoverable errors
        }
    }
}
}

Concurrency Safety

Single-Threaded Design

#![allow(unused)]
fn main() {
// The driver requires &mut self for all operations
impl<I2C, E> Mpu9250<I2C> where I2C: I2c<Error = E> {
    pub fn read_acceleration(&mut self) -> Result<[f32; 3], Error<E>>
    //                        ^^^^ Exclusive access required
}
}

Implications:

  • Thread Safety: Not Send or Sync by default - I2C peripherals are typically not thread-safe
  • Exclusive Access: Prevents concurrent access to I2C bus
  • RAII Pattern: Resource cleanup handled by Rust's ownership system

Interrupt Safety

#![allow(unused)]
fn main() {
// For interrupt-driven applications
fn interrupt_safe_reading() {
    // Disable interrupts during I2C transaction if required by your platform
    critical_section::with(|_cs| {
        let result = sensor.read_acceleration();
        // Process result...
    });
}
}

Performance Considerations

Zero-Copy Design

#![allow(unused)]
fn main() {
// Direct array return - no heap allocation
pub fn read_accel_raw(&mut self) -> Result<[i16; 3], Error<E>> {
    // Returns stack-allocated array directly
}
}

Minimal Register Access

#![allow(unused)]
fn main() {
// Efficient register reading - single transaction for multiple axes
let mut buffer = [0u8; 6];  // Read all 6 bytes (3 axes × 2 bytes) at once
self.i2c.write(self.address, &[ACCEL_XOUT_H])?;
self.i2c.read(self.address, &mut buffer)?;
}

Development Workflow

Testing Strategy

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;
    
    // Mock I2C for unit testing
    struct MockI2c {
        expected_writes: Vec<Vec<u8>>,
        read_responses: Vec<Vec<u8>>,
    }
    
    impl I2c for MockI2c {
        type Error = ();
        
        fn write(&mut self, _addr: u8, data: &[u8]) -> Result<(), Self::Error> {
            // Verify expected write operations
            Ok(())
        }
        
        fn read(&mut self, _addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
            // Return mock data
            Ok(())
        }
    }
    
    #[test]
    fn test_sensor_initialization() {
        let mock_i2c = MockI2c::new();
        let mut sensor = Mpu9250::new(mock_i2c, 0x68);
        // Test initialization logic...
    }
}
}

Documentation Guidelines

#![allow(unused)]
fn main() {
/// Reads accelerometer data in g-force units
/// 
/// # Returns
/// 
/// Array of [X, Y, Z] acceleration values in g-force units.
/// The scaling is automatically applied based on the configured range.
/// 
/// # Errors
/// 
/// Returns `Error::I2c` if communication fails.
/// 
/// # Example
/// 
/// ```no_run
/// use hayasen::prelude::*;
/// 
/// let mut sensor = Mpu9250::new(i2c, 0x68);
/// sensor.initialize_sensor(AccelRange::Range2G, GyroRange::Range250Dps)?;
/// let accel = sensor.read_acceleration()?;
/// println!("Acceleration: {:?}", accel);
/// ```
pub fn read_acceleration(&mut self) -> Result<[f32; 3], Error<E>>
}

Platform Integration Notes

Embedded HAL Compatibility

#![allow(unused)]
fn main() {
// Generic over any I2C implementation
impl<I2C, E> Mpu9250<I2C>
where 
    I2C: I2c<Error = E>  // Uses embedded-hal trait
{
    // Platform-agnostic implementation
}
}

Benefits:

  • Platform Independence: Works with any embedded-hal compliant I2C driver
  • Type Safety: Compile-time verification of I2C compatibility
  • Error Propagation: Preserves underlying platform error types

Memory Layout Considerations

#![allow(unused)]
fn main() {
#[repr(C)]
struct SensorData {
    temperature: f32,    // 4 bytes
    accel: [f32; 3],    // 12 bytes
    gyro: [f32; 3],     // 12 bytes
}  // Total: 28 bytes - predictable layout
}

Safety Patterns

Resource Management

#![allow(unused)]
fn main() {
// RAII pattern ensures I2C is properly released
impl<I2C> Drop for Mpu9250<I2C> {
    fn drop(&mut self) {
        // I2C peripheral is automatically returned when Mpu9250 is dropped
        // No explicit cleanup required due to Rust's ownership system
    }
}
}

Initialization Safety

#![allow(unused)]
fn main() {
pub fn initialize_sensor(&mut self, accel_range: AccelRange, gyro_range: GyroRange) -> Result<(), Error<E>> {
    self.verify_identity()?;        // Always verify device first
    self.configure_power()?;        // Ensure proper power state
    self.setup_accelerometer(accel_range)?;  // Configure before use
    self.setup_gyroscope(gyro_range)?;       // Configure before use
    Ok(())
}
}

Safety Chain:

  1. Device identity verification prevents wrong device communication
  2. Power configuration ensures device is ready
  3. Sensor configuration sets known state before operation

Register Access Patterns

#![allow(unused)]
fn main() {
// Safe register write pattern
fn write_register(&mut self, register: u8, value: u8) -> Result<(), Error<E>> {
    // Always write register address followed by data
    self.i2c.write(self.address, &[register, value])?;
    Ok(())
}

// Safe register read pattern  
fn read_register(&mut self, register: u8) -> Result<u8, Error<E>> {
    let mut buffer = [0u8; 1];
    self.i2c.write(self.address, &[register])?;      // Set register pointer
    self.i2c.read(self.address, &mut buffer)?;       // Read data
    Ok(buffer[0])
}
}

Error Safety Guarantees

Fail-Fast Design

#![allow(unused)]
fn main() {
// Sensor operations fail immediately on error
pub fn read_acceleration(&mut self) -> Result<[f32; 3], Error<E>> {
    let raw = self.read_accel_raw()?;  // Fail fast on I2C error
    // Only proceed if raw read succeeded
    let x = raw[0] as f32 * self.accel_scale;
    // ...
}
}

Error Propagation Chain

#![allow(unused)]
fn main() {
// Error chain: I2C Error -> Driver Error -> Application Error
Hardware I2C Error
    ↓ (From<E> implementation)
Driver Error<E>
    ↓ (? operator)  
Application Error Handling
}

Integration Patterns

Dependency Injection

#![allow(unused)]
fn main() {
// The driver doesn't create its own I2C - it's injected
let i2c = platform_specific_i2c_setup();
let sensor = Mpu9250::new(i2c, address);  // Dependency injection
}

Benefits:

  • Testability with mock I2C implementations
  • Platform flexibility
  • Resource sharing control

Builder Pattern Extension

#![allow(unused)]
fn main() {
// Example extension for complex configurations
impl<I2C, E> Mpu9250<I2C> 
where I2C: I2c<Error = E>
{
    pub fn builder(i2c: I2C, address: u8) -> SensorBuilder<I2C> {
        SensorBuilder::new(i2c, address)
    }
}

pub struct SensorBuilder<I2C> {
    sensor: Mpu9250<I2C>,
    configured: bool,
}

impl<I2C, E> SensorBuilder<I2C> 
where I2C: I2c<Error = E>
{
    pub fn with_accel_range(mut self, range: AccelRange) -> Result<Self, Error<E>> {
        self.sensor.setup_accelerometer(range)?;
        Ok(self)
    }
    
    pub fn build(self) -> Result<Mpu9250<I2C>, Error<E>> {
        if !self.configured {
            return Err(Error::ConfigError);
        }
        Ok(self.sensor)
    }
}
}

Debugging and Development

Register Debug Utilities

#![allow(unused)]
fn main() {
#[cfg(debug_assertions)]
impl<I2C, E> Mpu9250<I2C>
where I2C: I2c<Error = E>
{
    pub fn dump_registers(&mut self) -> Result<(), Error<E>> {
        let registers = [WHO_AM_I, PWR_MGMT_1, ACCEL_CONFIG, GYRO_CONFIG];
        
        for &reg in &registers {
            match self.read_register(reg) {
                Ok(value) => println!("Register 0x{:02X}: 0x{:02X}", reg, value),
                Err(e) => println!("Failed to read register 0x{:02X}: {:?}", reg, e),
            }
        }
        Ok(())
    }
}
}

Compile-Time Configuration Validation

#![allow(unused)]
fn main() {
// Use const assertions for compile-time validation
const _: () = {
    assert!(WHO_AM_I_VALUE == 0x71, "Incorrect WHO_AM_I value for MPU9250");
};
}

Performance Optimization Notes

Batch Operations

#![allow(unused)]
fn main() {
// Reading all axes in single I2C transaction is more efficient
pub fn read_accel_raw(&mut self) -> Result<[i16; 3], Error<E>> {
    let mut buffer = [0u8; 6];  // Read all 3 axes at once
    // More efficient than 3 separate 2-byte reads
}
}

Cache-Friendly Access Patterns

#![allow(unused)]
fn main() {
// Scale factors are cached to avoid repeated calculations
impl<I2C, E> Mpu9250<I2C> {
    pub fn setup_accelerometer(&mut self, range: AccelRange) -> Result<(), Error<E>> {
        let (config_value, scale) = match range {
            AccelRange::Range2G => (0x00, 2.0 / 32768.0),  // Pre-calculated
            // ...
        };
        self.accel_scale = scale;  // Cache for future use
        Ok(())
    }
}
}

Future Development Considerations

Extensibility

  • Modular Design: Easy to add new sensor support via feature flags
  • Trait Abstractions: Common sensor operations could be abstracted into traits
  • Async Support: Could be extended for async I2C operations

Version Compatibility

#![allow(unused)]
fn main() {
// Use semantic versioning for breaking changes
// Major: Breaking API changes
// Minor: New features, backwards compatible  
// Patch: Bug fixes only
}

The crate is designed with embedded systems' constraints in mind, prioritizing safety, performance, and resource efficiency while maintaining a clean, intuitive API.

Contributing to Hayasen

Contributions are welcome for the hayasen project! To ensure a smooth and effective collaboration, please follow these guidelines.

Getting Started

  1. Fork the repository: Start by forking the hayasen repository to your GitHub account.
  2. Clone your fork: Clone your forked repository to your local machine:
    git clone https://github.com/Vaishnav-Sabari-Girish/hayasen.git
    cd hayasen
    
  3. Create a new branch: For each new feature or bug fix, create a new branch:
    git checkout -b feature/your-feature-name # For new features
    git checkout -b bugfix/your-bug-name    # For bug fixes
    

Code Style Guidelines (Rust)

Please adhere to the existing code style and conventions of the project, as enforced by cargo fmt and cargo clippy.

  • Imports: Prefer use crate::module::item; or use super::item; for internal modules. Group related imports.
  • Formatting: Adhere to cargo fmt standards. You can run cargo fmt to automatically format your code.
  • Naming Conventions:
    • snake_case for functions, variables, and modules.
    • PascalCase for types (structs, enums, traits).
    • SCREAMING_SNAKE_CASE for constants.
  • Error Handling: Utilize Result and Option types for explicit error handling. Avoid unwrap() and expect() in production code.
  • Types: Use explicit types where clarity is enhanced, otherwise let type inference work.
  • Comments: Explain why complex logic is implemented, not what it does.

Development Workflow

Building the Project

  • Build: cargo build
  • Build (release): cargo build --release

Testing

  • Run all tests: cargo test
  • Run a single test: cargo test -- <test_name>

Linting and Type Checking

  • Linting: cargo clippy -- -D warnings
  • Check types: cargo check

Submitting Changes

  1. Commit your changes: Write clear, concise commit messages that explain the purpose of your changes.
  2. Push to your fork:
    git push origin feature/your-feature-name
    
  3. Create a Pull Request:
    • Go to the original hayasen repository on GitHub.
    • Click on "New Pull Request".
    • Provide a descriptive title and a detailed explanation of your changes.
    • Ensure all tests pass and linting checks are clear.

Thank you for contributing to Hayasen!

License Information

Dual Licensing

Hayasen is dual-licensed under the MIT License and Apache License 2.0. You may choose to use, modify, and distribute this software under the terms of either license.

Why Dual Licensing?

The dual licensing approach provides maximum flexibility for different use cases:

  • MIT License: Simple, permissive license ideal for proprietary applications and commercial use
  • Apache License 2.0: More comprehensive license with explicit patent grants and trademark protections

You can choose the license that best fits your project's requirements.

License Texts

MIT License

MIT License

Copyright (c) [YEAR] [COPYRIGHT_HOLDER]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Apache License 2.0

Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

   "License" shall mean the terms and conditions for use, reproduction,
   and distribution as defined by Sections 1 through 9 of this document.

   "Licensor" shall mean the copyright owner or entity granting the License.

   "Legal Entity" shall mean the union of the acting entity and all
   other entities that control, are controlled by, or are under common
   control with that entity. For the purposes of this definition,
   "control" means (i) the power, direct or indirect, to cause the
   direction or management of such entity, whether by contract or
   otherwise, or (ii) ownership of fifty percent (50%) or more of the
   outstanding shares, or (iii) beneficial ownership of such entity.

   "You" (or "Your") shall mean an individual or Legal Entity
   exercising permissions granted by this License.

   "Source" form shall mean the preferred form for making modifications,
   including but not limited to software source code, documentation
   source, and configuration files.

   "Object" form shall mean any form resulting from mechanical
   transformation or translation of a Source form, including but
   not limited to compiled object code, generated documentation,
   and conversions to other media types.

   "Work" shall mean the work of authorship, whether in Source or
   Object form, made available under the License, as indicated by a
   copyright notice that is included in or attached to the work
   (which shall not include communications that are clearly marked or
   otherwise designated in writing by the copyright owner as "Not a Contribution").

   "Derivative Works" shall mean any work, whether in Source or Object
   form, that is based upon (or derived from) the Work and for which the
   editorial revisions, annotations, elaborations, or other modifications
   represent, as a whole, an original work of authorship. For the purposes
   of this License, Derivative Works shall not include works that remain
   separable from, or merely link (or bind by name) to the interfaces of,
   the Work and derivative works thereof.

   "Contribution" shall mean any work of authorship, including
   the original version of the Work and any modifications or additions
   to that Work or Derivative Works thereof, that is intentionally
   submitted to Licensor for inclusion in the Work by the copyright
   owner or by an individual or Legal Entity authorized to submit on
   behalf of the copyright owner. For the purposes of this definition,
   "submitted" means any form of electronic, verbal, or written
   communication sent to the Licensor or its representatives, including
   but not limited to communication on electronic mailing lists, source
   code control systems, and issue tracking systems that are managed by,
   or on behalf of, the Licensor for the purpose of discussing and
   improving the Work, but excluding communication that is conspicuously
   marked or otherwise designated in writing by the copyright owner as
   "Not a Contribution."

2. Grant of Copyright License. Subject to the terms and conditions of
   this License, each Contributor hereby grants to You a perpetual,
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
   copyright license to use, reproduce, modify, publicly display,
   publicly perform, sublicense, and distribute the Work and such Derivative
   Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
   this License, each Contributor hereby grants to You a perpetual,
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
   (except as stated in this section) patent license to make, have made,
   use, offer to sell, sell, import, and otherwise transfer the Work,
   where such license applies only to those patent claims licensable
   by such Contributor that are necessarily infringed by their
   Contribution(s) alone or by combination of their Contribution(s)
   with the Work to which such Contribution(s) was submitted. If You
   institute patent litigation against any entity (including a
   cross-claim or counterclaim in a lawsuit) alleging that the Work
   or a Contribution incorporated within the Work constitutes direct
   or contributory patent infringement, then any patent licenses
   granted to You under this License for that Work shall terminate
   as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
   Work or Derivative Works thereof in any medium, with or without
   modifications, and in Source or Object form, provided that You
   meet the following conditions:

   (a) You must give any other recipients of the Work or
       Derivative Works a copy of this License; and

   (b) You must cause any modified files to carry prominent notices
       stating that You changed the files; and

   (c) You must retain, in the Source form of any Derivative Works
       that You distribute, all copyright, trademark, patent,
       attribution notices from the Source form of the Work,
       excluding those notices that do not pertain to any part of
       the Derivative Works; and

   (d) If the Work includes a "NOTICE" text file as part of its
       distribution, then any Derivative Works that You distribute must
       include a readable copy of the attribution notices contained
       within such NOTICE file, excluding those notices that do not
       pertain to any part of the Derivative Works, in at least one
       of the following places: within a NOTICE text file distributed
       as part of the Derivative Works; within the Source form or
       documentation, if provided along with the Derivative Works; or,
       within a display generated by the Derivative Works, if and
       wherever such third-party notices normally appear. The contents
       of the NOTICE file are for informational purposes only and
       do not modify the License. You may add Your own attribution
       notices within Derivative Works that You distribute, alongside
       or as an addendum to the NOTICE text from the Work, provided
       that such additional attribution notices cannot be construed
       as modifying the License.

   You may add Your own copyright notice to Your modifications and
   may provide additional or different license terms and conditions
   for use, reproduction, or distribution of Your modifications, or
   for any such Derivative Works as a whole, provided Your use,
   reproduction, and distribution of the Work otherwise complies with
   the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise,
   any Contribution intentionally submitted for inclusion in the Work
   by You to the Licensor shall be under the terms and conditions of
   this License, without any additional terms or conditions.
   Notwithstanding the above, nothing herein shall supersede or modify
   the terms of any separate contributor license agreement you may have
   executed with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade
   names, trademarks, service marks, or product names of the Licensor,
   except as required for reasonable and customary use in describing the
   origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or
   agreed to in writing, Licensor provides the Work (and each
   Contributor provides its Contributions) on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
   implied, including, without limitation, any warranties or conditions
   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
   PARTICULAR PURPOSE. You are solely responsible for determining the
   appropriateness of using or redistributing the Work and assume any
   risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory,
   whether in tort (including negligence), contract, or otherwise,
   unless required by applicable law (such as deliberate and grossly
   negligent acts) or agreed to in writing, shall any Contributor be
   liable to You for damages, including any direct, indirect, special,
   incidental, or consequential damages of any character arising as a
   result of this License or out of the use or inability to use the
   Work (including but not limited to damages for loss of goodwill,
   work stoppage, computer failure or malfunction, or any and all
   other commercial damages or losses), even if such Contributor
   has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Support. When redistributing
   the Work or Derivative Works thereof, You may choose to offer,
   and charge a fee for, acceptance of support, warranty, indemnity,
   or other liability obligations and/or rights consistent with this
   License. However, in accepting such obligations, You may act only
   on Your own behalf and on Your sole responsibility, not on behalf
   of any other Contributor, and only if You agree to indemnify,
   defend, and hold each Contributor harmless for any liability
   incurred by, or claims asserted against, such Contributor by reason
   of your accepting any such warranty or support.

END OF TERMS AND CONDITIONS

License Selection Guide

Choose MIT License If:

  • ✅ You want maximum simplicity and permissiveness
  • ✅ Your project is proprietary or closed-source
  • ✅ You prefer minimal legal complexity
  • ✅ You don't need explicit patent protection
  • ✅ You want broad compatibility with other projects

Choose Apache 2.0 License If:

  • ✅ You want explicit patent grant protection
  • ✅ Your project involves potential patent considerations
  • ✅ You need trademark protection provisions
  • ✅ You prefer more detailed legal terms
  • ✅ You're contributing to Apache ecosystem projects

Compliance Requirements

For MIT License

When using Hayasen under the MIT License:

  1. Include Copyright Notice: Include the MIT license text with copyright notice in your distribution
  2. Attribution: Maintain attribution to the original authors
  3. License Propagation: Include the license file with any redistribution

For Apache 2.0 License

When using Hayasen under the Apache 2.0 License:

  1. Include License: Include the Apache 2.0 license text
  2. Copyright Notices: Preserve all copyright notices from the source
  3. NOTICE File: If a NOTICE file exists, include it in redistributions
  4. Modification Notices: Mark any files you modify
  5. Patent Grant: Benefit from explicit patent grant provisions

Cargo.toml Configuration

Include both licenses in your Cargo.toml:

[package]
name = "hayasen"
version = "x.x.x"
license = "MIT OR Apache-2.0"
authors = ["Your Name <your.email@example.com>"]
description = "Embedded sensor driver library for motion tracking devices"
repository = "https://github.com/yourusername/hayasen"
documentation = "https://docs.rs/hayasen"
keywords = ["embedded", "sensor", "mpu9250", "imu", "no-std"]
categories = ["embedded", "hardware-support", "no-std"]

File Structure

Ensure these license files are present in your repository root:

hayasen/
├── LICENSE-MIT         # Full MIT license text
├── LICENSE-APACHE      # Full Apache 2.0 license text
├── Cargo.toml         # With license = "MIT OR Apache-2.0"
├── README.md          # License section referencing both
└── src/
    └── lib.rs         # With license headers

Source Code Headers

Include license headers in your source files:

#![allow(unused)]
fn main() {
// Licensed under the MIT License or Apache License, Version 2.0
// at your option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Hayasen - Embedded sensor driver library
//! 
//! Copyright (c) [YEAR] [COPYRIGHT_HOLDER]
//! 
//! Licensed under either:
//! - MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
//! - Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
//! 
//! at your option.
}

Third-Party Dependencies

When adding dependencies, ensure license compatibility:

  • MIT: Compatible with both MIT and Apache 2.0
  • Apache 2.0: Compatible with both licenses
  • BSD-2-Clause/BSD-3-Clause: Compatible with both
  • ⚠️ GPL variants: May require careful consideration
  • Copyleft licenses: May not be compatible with permissive dual licensing

Attribution

When using Hayasen in your projects, attribution can be provided as:

This project uses Hayasen (https://github.com/yourusername/hayasen),
licensed under MIT OR Apache-2.0.

Or in a more formal NOTICE file:

Hayasen Embedded Sensor Library
Copyright (c) [YEAR] [COPYRIGHT_HOLDER]
Licensed under MIT OR Apache-2.0

This product includes software developed by [Your Name/Organization].

Commercial Use

Both licenses explicitly permit commercial use:

  • No royalties or licensing fees required
  • Modification rights for proprietary applications
  • Distribution rights in commercial products
  • Sublicensing permitted under both licenses

Contributing

By contributing to Hayasen, you agree that your contributions will be licensed under the same dual MIT/Apache-2.0 license terms. This ensures consistency across the entire codebase and maintains the dual licensing benefits for all users.

Contributor License Agreement

Contributors should be aware that:

  1. Contributions are licensed under both MIT and Apache 2.0
  2. You retain copyright to your contributions
  3. You grant broad rights to use, modify, and distribute your contributions
  4. Patent rights are handled according to Apache 2.0 terms

Frequently Asked Questions

Q: Can I use Hayasen in my commercial product?

A: Yes, both licenses explicitly permit commercial use without restrictions.

Q: Do I need to open-source my modifications?

A: No, both licenses are permissive and do not require you to open-source derivative works.

Q: Which license should I choose for my project?

A: Choose based on your needs:

  • MIT for simplicity and broad compatibility
  • Apache 2.0 for explicit patent protection

Q: Can I relicense Hayasen under a different license?

A: You can license your derivative work under different terms, but you must maintain attribution and comply with the original license terms.

Q: What if I only want to use one of the licenses?

A: You can choose to use Hayasen under either license exclusively. Simply follow the terms of your chosen license.


Disclaimer: This license information is provided for informational purposes. For legal advice regarding license compliance, consult with a qualified attorney.