Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration register #3

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7259b5d
Bump edition and versions
tdittr Dec 2, 2022
caa0057
Add configuration register
tdittr Dec 2, 2022
34ee03e
Run cargo-fmt
tdittr Dec 2, 2022
d2a870a
Add register descriptions
tdittr Dec 5, 2022
59cf550
Listen to clippy and add docs and annotations
tdittr Dec 5, 2022
e1beb18
Update examples and tests
tdittr Apr 21, 2023
f47cd60
Add Address encoding
tdittr Apr 21, 2023
7f4c58a
Make Address easier to use
tdittr May 8, 2023
c228f8e
Make another attempt for calibration
tdittr May 8, 2023
ae5d77d
Fix clippy warnings
tdittr May 8, 2023
6665648
Fix rustdoc warnings
tdittr May 8, 2023
a186cfa
Split out errors
tdittr May 8, 2023
1c8a1b2
Split out errors into a module
tdittr May 8, 2023
8bb540a
Add round-trip tests and add missing methods
tdittr May 9, 2023
2501279
Remove dust from PR, update embedded-hal to 1.0.0-rc1
tdittr Oct 20, 2023
bda32c2
Get back into the driver by documenting the tests
tdittr Oct 20, 2023
3eaa0a3
Add myself to the manifest
tdittr Oct 20, 2023
db7c75f
Make paranoid behavior optional
tdittr Oct 20, 2023
a91bf6e
Improve error handling during initialization
tdittr Oct 20, 2023
0a75f11
Use type system we never read a register and interpret it as another...
tdittr Oct 20, 2023
732d93d
Add todo for later
tdittr Oct 20, 2023
f97e817
Draft: read all registers to try and make sure they are consistent...
tdittr Oct 20, 2023
6814058
Update to embedded-hal 1.0
tdittr Jan 12, 2024
f567686
Create async version and generate sync version from it
tdittr Jan 12, 2024
801584b
Clean up old CI
tdittr Jan 12, 2024
0b13baf
Fix broken docs links
tdittr Jan 12, 2024
ba3cffe
Add examples and fix parts which felt wrong in examples
tdittr Feb 6, 2024
bbacd2b
Write a readme
tdittr Feb 6, 2024
3e49e98
Reduce code duplication using macro magic
tdittr Feb 6, 2024
5337823
Add readme as crate docs
tdittr Feb 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 0 additions & 4 deletions .cargo/config

This file was deleted.

2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

/target/
**/*.rs.bk
Cargo.lock
.idea
8 changes: 0 additions & 8 deletions .travis.yml

This file was deleted.

57 changes: 51 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,62 @@
[package]
name = "ina219"
description = "INA219 current/power monitor driver"
version = "0.1.0"
edition = "2021"
version = "0.2.0"
keywords = ["ina219", "driver", "i2c", "current", "no_std"]
repository = "https://github.com/scttnlsn/ina219"
authors = ["Scott Nelson <scott@scottnelson.co>"]
authors = ["Scott Nelson <scott@scottnelson.co>", "Tamme Dittrich <tamme@tweedegolf.com>"]
license = "MIT/Apache-2.0"

[features]
default = ["sync", "async", "paranoid"]

# Provide a blocking driver implementation
sync = ["dep:embedded-hal"]

# Provide an async driver implementation
async = ["dep:embedded-hal-async"]


# Use the standard library and impl std::error::Error on all error types
std = []

# Perform checks to see if the INA219 reacts as expected such as:
# - All measurements are in the configured ranges
# - We can read back the configuration we have written without any changes
#
# This does cause some overhead such as memorizing the last written configuration and performing more actions on the
# bus. So it can be disabled by setting `default-features = false`.
paranoid = []

# When using a raspberry pi and the I2C transaction an operation not supported error is raised. This feature replaces
# the usage of a single transaction with multiple write_read operations.
no_transaction = []

[[example]]
name = "calibration"
required-features = ["sync", "std", "no_transaction"]

[[example]]
name = "custom-calibration"
required-features = ["sync", "std", "no_transaction"]

[[example]]
name = "full-config"
required-features = ["sync", "std", "no_transaction"]

[[example]]
name = "minimal"
required-features = ["sync", "std", "no_transaction"]

[[example]]
name = "triggered"
required-features = ["sync", "std", "no_transaction"]

[dependencies]
byteorder = {version = "1.2.1", default-features = false}
embedded-hal = "0.2"
embedded-hal = { version = "1.0.0", optional = true }
embedded-hal-async = { version = "1.0.0", optional = true }

[dev-dependencies]
linux-embedded-hal = "0.2"
embedded-hal-mock = "0.4"
linux-embedded-hal = "0.4.0"
embedded-hal-mock = { version = "0.10.0", default-features = false, features = ["eh1"] }
42 changes: 17 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,28 @@
# ina219

[![Travis CI Status](https://travis-ci.org/scttnlsn/ina219.svg?branch=master)](https://travis-ci.org/scttnlsn/ina219)
[![crates.io](https://img.shields.io/crates/v/ina219.svg)](https://crates.io/crates/ina219)

[INA219](http://www.ti.com/product/INA219) current/power monitor driver for Rust
Blocking and async driver for the [INA219](http://www.ti.com/product/INA219) current/power monitor by Texas Instruments.

## Example

```rust
extern crate ina219;
extern crate linux_embedded_hal as hal;

use hal::I2cdev;
use ina219::{INA219, INA219_ADDR};
## Features
This crate has the following feature flags (default features in bold):

fn main() {
let device = I2cdev::new("/dev/i2c-1").unwrap();
let mut ina = INA219::new(device, INA219_ADDR);
| Name | Description |
|----------------|------------------------------------------------------------------------|
| **sync** | Provide a blocking driver implementation |
| **async** | Provide an async driver implementation |
| **paranoid** | Perform extra checks |
| no_transaction | Disable use of transactions and perform individual system calls |
| std | Use the standard library and impl std::error::Error on all error types |

ina.calibrate(0x0100).unwrap();
For more detailed descriptions see [Cargo.toml](Cargo.toml).

let voltage = ina.voltage().unwrap();
println!("bus voltage: {:?}", voltage);
## Calibration
This driver includes ways to use the calibration feature of the INA219. However, the errors introduced by the
calculations can be unintuitive. So it can make sense to just compute the current and power in software.

let shunt_voltage = ina.shunt_voltage().unwrap();
println!("shunt voltage: {:?}", shunt_voltage);

let current = ina.current().unwrap();
println!("current: {:?}", current);

let power = ina.power().unwrap();
println!("power: {:?}", power);
}

```
## Examples
The [examples](examples/) folder contains code that demonstrates how this driver can be used. They were tested on a
Raspberry Pi with an INA219 that was configured for address 0x42.
21 changes: 21 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

fn main() -> std::io::Result<()> {
// Produce an sync version from the async one
println!("cargo:rerun-if-changed=src/async.rs");
let asynced = std::fs::read_to_string("src/async.rs")?;

let asynced = asynced.replace("embedded_hal_async", "embedded_hal");
let asynced = asynced.replace("async", "");
let asynced = asynced.replace(".await", "");

let mut out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
out_path.push("de-asynced.rs");

File::create(out_path)?.write_all(asynced.as_bytes())?;

Ok(())
}
28 changes: 28 additions & 0 deletions examples/calibration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use ina219::address::Address;
use ina219::calibration::{IntCalibration, MicroAmpere};
use ina219::SyncIna219;
use linux_embedded_hal::I2cdev;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
// Resolution of 1A, and a shunt of 1mOhm
let calib = IntCalibration::new(MicroAmpere(1_000_000), 1_000).unwrap();

let device = I2cdev::new("/dev/i2c-1")?;
let mut ina = SyncIna219::new_calibrated(device, Address::from_byte(0x42)?, calib)?;

let measurement = ina.next_measurement()?.expect("A measurement is ready");

println!("{:#?}", measurement);

let err = (measurement.current.0 / 1_000_000)
- i64::from(measurement.shunt_voltage.shunt_voltage_mv());
assert!(err.abs() < 10);

let err = (measurement.power.0 / 1_000_000).abs_diff(
measurement.current.0 / 1_000_000 * i64::from(measurement.bus_voltage.voltage_mv() / 1000),
);
assert!(err < 100);

Ok(())
}
78 changes: 78 additions & 0 deletions examples/custom-calibration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use ina219::address::Address;
use ina219::calibration::Calibration;
use ina219::measurements::{CurrentRegister, PowerRegister};
use ina219::SyncIna219;
use linux_embedded_hal::I2cdev;
use std::error::Error;

struct MyCalib;

impl MyCalib {
const R_OHM: f32 = 0.001; // 1mOhm
const CURRENT_LSB: f32 = 1.0; // 1A

fn new() -> Self {
Self
}
}

impl Calibration for MyCalib {
type Current = u16; // in A
type Power = u16; // in W

fn register_bits(&self) -> u16 {
(0.04096 / (Self::CURRENT_LSB * Self::R_OHM)) as u16
}

fn current_from_register(&self, reg: CurrentRegister) -> Self::Current {
reg.0
}

fn power_from_register(&self, reg: PowerRegister) -> Self::Power {
reg.0 * 20
}
}

fn main() -> Result<(), Box<dyn Error>> {
let device = I2cdev::new("/dev/i2c-1")?;
let mut ina = SyncIna219::new_calibrated(device, Address::from_byte(0x42)?, MyCalib::new())?;

let measurements = ina.next_measurement()?.expect("Measurement is done");

println!("{:#?}", measurements);

// NOTE: The calibration can introduce quite a bit of error, we allow for 10% for testing

let shunt = measurements.shunt_voltage.shunt_voltage_mv() as u16;
let err = shunt / 10;
let plausible_currents = (shunt - err)..(shunt + err);
assert!(plausible_currents.contains(&measurements.current));

let expected_power = measurements.current * (measurements.bus_voltage.voltage_mv() / 1000);
let err = expected_power / 10;
let plausible_power = (expected_power - err)..(expected_power + err);
assert!(plausible_power.contains(&measurements.power));

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use ina219::calibration::simulate;
use ina219::measurements::{BusVoltage, ShuntVoltage};

#[test]
fn does_not_overflow() {
// Worst case is we get 16A at 20V over our 1mOhm resistor

let bus = BusVoltage::from_mv(20_000); // 20V = 20_000mV
let shunt = ShuntVoltage::from_10uv(16_000 / 10); // 0.001 Ohm * 16A = 0.016V = 16_000µV
assert_eq!(shunt.shunt_voltage_uv(), 16_000);

let measurements = simulate(&MyCalib, bus, shunt).expect("Does not overflow");

assert!((15..17).contains(&measurements.current)); // Calculation does include some error
assert_eq!(measurements.power, measurements.current * 20);
}
}
46 changes: 46 additions & 0 deletions examples/full-config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use ina219::address::Address;
use ina219::configuration::{
BusVoltageRange, Configuration, MeasuredSignals, OperatingMode, Reset, Resolution,
ShuntVoltageRange,
};
use ina219::SyncIna219;
use linux_embedded_hal::I2cdev;
use std::error::Error;
use std::time::Duration;

fn main() -> Result<(), Box<dyn Error>> {
let device = I2cdev::new("/dev/i2c-1")?;
let mut ina = SyncIna219::new(device, Address::from_byte(0x42)?)?;

ina.set_configuration(Configuration {
// Be extra precise, but take some extra time
bus_resolution: Resolution::Avg128,
shunt_resolution: Resolution::Avg128,

// We only care about low voltage bus and shunt, values larger are truncated to the max
bus_voltage_range: BusVoltageRange::Fsr16v,
shunt_voltage_range: ShuntVoltageRange::Fsr40mv,

// Measure both signals continuously (default)
operating_mode: OperatingMode::Continous(MeasuredSignals::ShutAndBusVoltage),

// Do not perform a reset
reset: Reset::Run,
})?;

// Wait for the for measurement to be done
let conversion_time: Duration = ina.configuration()?.conversion_time().unwrap();
std::thread::sleep(conversion_time);

let measurements = ina.next_measurement()?.expect("Conversion is done now");
println!(
"Bus: {:.2} V",
measurements.bus_voltage.voltage_mv() as f32 / 1000.0
);
println!(
"Shunt: {:.2} mV",
measurements.shunt_voltage.shunt_voltage_mv() as f32
);

Ok(())
}
17 changes: 17 additions & 0 deletions examples/minimal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use ina219::address::Address;
use ina219::SyncIna219;
use linux_embedded_hal::I2cdev;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
let device = I2cdev::new("/dev/i2c-1")?;
let mut ina = SyncIna219::new(device, Address::from_byte(0x42)?)?;

// Wait until a result is ready
std::thread::sleep(ina.configuration()?.conversion_time().unwrap());

println!("Bus Voltage: {}", ina.bus_voltage()?);
println!("Shunt Voltage: {}", ina.shunt_voltage()?);

Ok(())
}
43 changes: 43 additions & 0 deletions examples/triggered.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use ina219::address::Address;
use ina219::configuration::{Configuration, MeasuredSignals, OperatingMode};
use ina219::SyncIna219;
use linux_embedded_hal::I2cdev;
use std::error::Error;
use std::time::Duration;

fn main() -> Result<(), Box<dyn Error>> {
let device = I2cdev::new("/dev/i2c-1")?;
let mut ina = SyncIna219::new(device, Address::from_byte(0x42)?)?;

ina.set_configuration(Configuration {
// Only measure if we kindly ask
operating_mode: OperatingMode::Triggered(MeasuredSignals::ShutAndBusVoltage),
..Configuration::default()
})?;

// Wait for the for measurement to be done
let conversion_time: Duration = ina.configuration()?.conversion_time().unwrap();
std::thread::sleep(conversion_time);

// Writing the configuration started the first measurement
let measurements = ina.next_measurement()?;
println!("After configuration: {:?}", measurements);
assert!(measurements.is_some());

// If we wait and check again there will be no new data
std::thread::sleep(conversion_time);
let measurements = ina.next_measurement()?;
println!("After no trigger: {:?}", measurements);
assert!(measurements.is_none());

// But we can start a new conversion using a trigger
ina.trigger()?;

std::thread::sleep(conversion_time);

let measurements = ina.next_measurement()?;
println!("After trigger: {:?}", measurements);
assert!(measurements.is_some());

Ok(())
}