Git Product home page Git Product logo

eldruin / ads1x1x-rs Goto Github PK

View Code? Open in Web Editor NEW
30.0 3.0 10.0 165 KB

Platform-agnostic Rust driver for the ADS1x1x ultra-small, low-power analog-to-digital converters (ADC). Compatible with ADS1013, ADS1014, ADS1015, ADS1113, ADS1114 and ADS1115

Home Page: https://blog.eldruin.com/ads1x1x-analog-to-digital-converter-driver-in-rust/

License: Apache License 2.0

Rust 100.00%
adc rust embedded embedded-hal embedded-hal-driver no-std i2c

ads1x1x-rs's Introduction

Rust ADS1x1x ultra-small, low-power analog-to-digital converters (ADC) driver

crates.io Docs Minimum Supported Rust Version Build Status Coverage Status

This is a platform agnostic Rust driver for the ADS1013, ADS1014, ADS1015, ADS1113, ADS1114 and ADS1115 ultra-small, low-power analog-to-digital converters (ADC), based on the embedded-hal traits.

Introductory blog post

This driver allows you to:

  • Set the operating mode to one-shot or continuous. See: into_continuous().
  • Make a measurement in one-shot mode. See: read().
  • Start continuous conversion mode. See: start().
  • Read the last measurement made in continuous conversion mode. See: read().
  • Set the data rate. See: set_data_rate().
  • Set the full-scale range (gain amplifier). See set_full_scale_range().
  • Read whether a measurement is in progress. See: is_measurement_in_progress().
  • Set the ALERT/RDY pin to be used as conversion-ready pin. See: use_alert_rdy_pin_as_ready().
  • Comparator:
    • Set the low and high thresholds. See: set_high_threshold_raw().
    • Set the comparator mode. See: set_comparator_mode().
    • Set the comparator polarity. See: set_comparator_polarity().
    • Set the comparator latching. See: set_comparator_latching().
    • Set the comparator queue. See: set_comparator_queue().
    • Disable the comparator. See: disable_comparator().

The devices

The devices are precision, low power, 12/16-bit analog-to-digital converters (ADC) that provide all features necessary to measure the most common sensor signals in an ultra-small package. Depending on the device, these integrate a programmable gain amplifier (PGA), voltage reference, oscillator and high-accuracy temperature sensor.

The devices can perform conversions at data rates up to 3300 samples per second (SPS). The PGA offers input ranges from ±256 mV to ±6.144 V, allowing both large and small signals to be measured with high resolution. An input multiplexer (MUX) allows to measure two differential or four single-ended inputs. The high-accuracy temperature sensor can be used for system-level temperature monitoring or cold-junction compensation for thermocouples.

The devices operate either in continuous-conversion mode, or in a single-shot mode that automatically powers down after a conversion. Single-shot mode significantly reduces current consumption during idle periods. Data is transferred through I2C.

Here is a comparison of the caracteristics of the devices:

Device Resolution Sample Rate Channels Multi-channel Features
ADS1013 12-bit Max 3300 SPS 1 N/A
ADS1014 12-bit Max 3300 SPS 1 N/A Comparator, PGA
ADS1015 12-bit Max 3300 SPS 4 Multiplexed Comparator, PGA
ADS1113 16-bit Max 860 SPS 1 N/A
ADS1114 16-bit Max 860 SPS 1 N/A Comparator, PGA
ADS1115 16-bit Max 860 SPS 4 Multiplexed Comparator, PGA

Datasheets:

Usage

To use this driver, import this crate and an embedded_hal implementation, then instantiate the appropriate device. In the following examples an instance of the device ADS1013 will be created as an example. Other devices can be created with similar methods like: Ads1x1x::new_ads1114(...).

Please find additional examples using hardware in this repository: driver-examples

use linux_embedded_hal::I2cdev;
use nb::block;

use ads1x1x::{channel, Ads1x1x, SlaveAddr};

fn main() {
    let dev = I2cdev::new("/dev/i2c-1").unwrap();
    let mut adc = Ads1x1x::new_ads1013(dev, SlaveAddr::default());
    let value = block!(adc.read(channel::DifferentialA0A1)).unwrap();
    println!("Measurement: {}", value);
    // get I2C device back
    let _dev = adc.destroy_ads1013();
}

Support

For questions, issues, feature requests, and other changes, please file an issue in the github project.

License

Licensed under either of

at your option.

Contributing

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

ads1x1x-rs's People

Contributors

david-oconnor avatar eldruin avatar reitermarkus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

ads1x1x-rs's Issues

trait for new_ads1015() structure?

I am trying to define a function with code for reading a couple of ads1015s so the loop is easier to read. The difficulty is that this requires the type for the structure returned by Ads1x1x::new_ads1015() which is used to read channels. Since the structure type is messy I would like to use a trait, but can only find traits for channels, not for the object doing the reading. The code below is a simplified extract which illustrates the problem.

Click to expand
// read_adc for blue pill stm32f103

#![deny(unsafe_code)]
#![no_std]
#![no_main]

#[cfg(debug_assertions)]
use panic_semihosting as _;

#[cfg(not(debug_assertions))]
use panic_halt as _;

use cortex_m_rt::entry;

use ads1x1x::{channel as AdcChannel, Ads1x1x, FullScaleRange, SlaveAddr};

use nb::block;

use stm32f1xx_hal::{
    delay::Delay,
    device::I2C2,
    i2c::{BlockingI2c, DutyCycle, Mode, Pins},
    pac::{CorePeripherals, Peripherals},
    prelude::*,
};

fn setup() -> (BlockingI2c<I2C2, impl Pins<I2C2>>, Delay) {
    let cp = CorePeripherals::take().unwrap();
    let p = Peripherals::take().unwrap();

    let mut rcc = p.RCC.constrain();
    let clocks = rcc.cfgr.freeze(&mut p.FLASH.constrain().acr);

    let mut gpiob = p.GPIOB.split(&mut rcc.apb2); // for i2c scl and sda

    // can have (scl, sda) using I2C1  on (PB8, PB9 ) or on  (PB6, PB7)
    //     or   (scl, sda) using I2C2  on (PB10, PB11)

    let i2c = BlockingI2c::i2c2(
        p.I2C2,
        (
            gpiob.pb10.into_alternate_open_drain(&mut gpiob.crh), // scl on PB10
            gpiob.pb11.into_alternate_open_drain(&mut gpiob.crh), // sda on PB11
        ),
        //&mut afio.mapr,  need this for i2c1 but not i2c2
        Mode::Fast {
            frequency: 100_000.hz(),
            duty_cycle: DutyCycle::Ratio2to1,
        },
        clocks,
        &mut rcc.apb1,
        1000,
        10,
        1000,
        1000,
    );

    let delay = Delay::new(cp.SYST, clocks);

    (i2c, delay) // return tuple (i2c, delay)
}

fn read_adc(adc_a: impl Ads1x1x, adc_b: impl Ads1x1x) -> (i16, i16, i16, i16, [i16; 3]) {

    // signature needs type of Ads1x1x::new_ads1015() or trait to use impl 

    let bat_ma =
        block!(adc_a.read(&mut AdcChannel::DifferentialA1A3)).unwrap_or(8091);
    let load_ma =
        block!(adc_a.read(&mut AdcChannel::DifferentialA2A3)).unwrap_or(8091);

    // toggle FullScaleRange to measure battery voltage, not just diff across shunt resistor

    adc_a.set_full_scale_range(FullScaleRange::Within4_096V).unwrap();
    let bat_mv = block!(adc_a.read(&mut AdcChannel::SingleA0)).unwrap_or(8091);
    adc_a.set_full_scale_range(FullScaleRange::Within0_256V).unwrap();

    // second adc
    let values_b = [
        block!(adc_b.read(&mut AdcChannel::SingleA0)).unwrap_or(8091),
        block!(adc_b.read(&mut AdcChannel::SingleA1)).unwrap_or(8091),
        block!(adc_b.read(&mut AdcChannel::SingleA2)).unwrap_or(8091),
    ];

    let temp_c = block!(adc_b.read(&mut AdcChannel::SingleA3)).unwrap_or(8091);

    (bat_mv, bat_ma, load_ma, temp_c, values_b)
}

#[entry]
fn main() -> ! {

    let (i2c, mut delay) = setup();

    let manager = shared_bus::BusManager::<cortex_m::interrupt::Mutex<_>, _>::new(i2c);

    let mut adc_a = Ads1x1x::new_ads1015(manager.acquire(), SlaveAddr::Alternative(false, false)); //addr = GND
    let mut adc_b = Ads1x1x::new_ads1015(manager.acquire(), SlaveAddr::Alternative(false, true)); //addr =  V

    adc_a
        .set_full_scale_range(FullScaleRange::Within0_256V)
        .unwrap();
    adc_b
        .set_full_scale_range(FullScaleRange::Within4_096V)
        .unwrap();


    loop {
        let (_bat_mv, _bat_ma, _load_ma, _temp_c, _values_b) = read_adc(adc_a, adc_b);
        
        // do other things
        delay.delay_ms(2000_u16); // sleep for 2s
    }
}

Since I don't have the trait right it gives

fn read_adc(adc_a: impl Ads1x1x, adc_b: impl Ads1x1x) -> (i16, i16, i16, i16, [i16; 3]) {
                          ^^^^^^^ not a trait

Is there a way to specify the trait to do this, or just a better way to approach the whole thing?

The `use_alert_rdy_pin_as_ready` does not enable pulse in continuous-conversion mode as documented

I've been trying to get an ADS1115 to emit an interrupt signal when in continuous-conversion mode using this crate. According to the documentation, the use_alert_rdy_pin_as_ready() function says:

This the ALERT/RDY pin outputs the OS bit when in OneShot mode, and provides a continuous-conversion ready pulse when in continuous-conversion mode.

However, this doesn't seem to work as expected. According to the TI documentation here: https://www.ti.com/lit/ds/symlink/ads1115.pdf

Set the COMP_QUE[1:0] bits to any 2-bit value other than 11 to keep the ALERT/RDY pin enabled, and allow the conversion ready signal to appear at the ALERT/RDY pin output.

But the function itself calls self.disable_comparator() which sets the COMP_QUEUE value to 0b11. This disables pulsing, and I need to manually call set_comparator_queue(ComparatorQueue::One) to set those bits low and and enable it again.

I'm not sure if this is an issue in the function itself or the documentation.

How to improve reading speed.

I'm using this crate to read a ADS1115 sensor on a raspberry pi4.
It's something like follows:

        let dev = I2cdev::new("/dev/i2c-1").unwrap();
        let address = adc_Address::default();
        let adc = Ads1x1x::new_ads1115(dev, address);
        ...
        adc.reset_internal_driver_state();
        adc
            .set_full_scale_range(ads1x1x::FullScaleRange::Within4_096V)
            .unwrap();
        ....    
        pub fn read_adc(&mut self, channel: AdcChannel) -> f32 {
        let conversion_volts: f32 = 0.000_125; // According to data-sheet, LSB = 125 μV for ±4.096 scale register, navigator's default
        block!(self.adc.read(channel.into())).unwrap() as f32 * conversion_volts
    }
       ...
        pub fn read_adc_all(&mut self) -> ADCData {
        ADCData {
            channel: [
                self.read_adc(AdcChannel::Ch0),
                self.read_adc(AdcChannel::Ch1),
                self.read_adc(AdcChannel::Ch2),
                self.read_adc(AdcChannel::Ch3),
            ],
        }
    }

Actually to read a single channel (SingleA0) , all ( A0..A4), I've something like:

read_adc                time:   [9.9452 ms 9.9682 ms 9.9893 ms]
read_adc_all            time:   [39.947 ms 40.032 ms 40.107 ms]

adding a self.adc.set_data_rate(DataRate16Bit::Sps860).unwrap();
It was improoved like follows:

`read_adc                time:   [3.0672 ms 3.0759 ms 3.0840 ms]
                        change: [-69.244% -69.143% -69.034%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 23 outliers among 100 measurements (23.00%)
  17 (17.00%) low severe
  1 (1.00%) low mild
  4 (4.00%) high mild
  1 (1.00%) high severe

read_adc_all            time:   [12.350 ms 12.375 ms 12.409 ms]
                        change: [-69.174% -69.086% -68.976%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 23 outliers among 100 measurements (23.00%)
  14 (14.00%) low severe
  1 (1.00%) low mild
  3 (3.00%) high mild
  5 (5.00%) high severe`

Can I reach more faster data for the 4 channels?

Example across function / struct boundary

Hi. It would be nice to have an example where you pass the adc to a function, struct or enum, or otherwise where you have to declare its type. Eg:

fn read(adc: Ads1x1x<
        ?
        ?,
        ads1x1x::DataRate16Bit,
        ads1x1x::mode::OneShot>) -> f32 {
// ...
}|

toggling FullScaleRange?

(Maybe really a hardware question.)

I am using ads1x1x-rs to read differential voltage across low-side shunt resistors to give a rough estimate of load current and battery charge/drain. There is also a solar panel which is not being measured. I've tested with a bluepill and blackpill stm32f411 and it works reasonably. (Maybe even better than should be expected given that I haven't bothered with op amps or capacitors to calm the noise.) For this I am setting FullScaleRange::Within0_256V.

I am wondering about also measuring the high side voltage, for which I would want FullScaleRange::Within4_096V. Is it reasonable to consider toggling FullScaleRange between these settings when I move to different pins in my read loop, or is that asking for real trouble? I've read the datasheet but didn't find anything useful on this. (They sometimes skip stupid novice details.)

Release to crates.io to allow embedded-hal 1.0 compat

Would it be possible to make a release to be able to use this library as a crates.io dependency that doesn't break dependencies on embedded-hal v1.0? Right now, I am using a clone of this library locally and putting the path in my Cargo.toml, but that feels a little strange.

`Ads1015` and `Resolution12Bit` need to be public

In rtic Shared and Local I need a type which should be something like Ads1x1x<embedded_hal_bus::i2c::RefCellDevice<'_, stm32f4xx_hal::i2c::I2c<I2C1>>, Ads1015, Resolution12Bit, ads1x1x::mode::OneShot>

But Ads1015 an Resolution12Bit seem to be private now. Could you make them public again please.

Example for STM32F4

I am in the process of porting a micropython firmware to rust using embassy and need to interact with an ADS1015.
Would it be possible to provide a sample code please?

Setting the address

Hi. How do you set alternate addresses, using the convention described in the datasheets? Eg for the ADS1115, it has 4 address possibilities: Address pin connected to:

  • GND (0x48, default)
  • VCC (0x49)
  • SDA
  • SCL

This doesn't seem to line up with the API in a way I can find in the docs:

let (a1, a0) = (true, false);
let address = SlaveAddr::Alternative(a1, a0);
let adc = Ads1x1x::new_ads1013(dev, address);

Ie, I think it should be set up so you enter the address (0x48 etc, or SlaveAddr should have those 4 variants above. Thoughts?

ADS1115 read return always 32767

Hello,

I'm using ADS1115 and when I call read method like this: block!(self.adc.read(&mut channel::SingleA0));
the function return always: 32767, and this is really strange.

When I call the read method like this: block!(self.adc.read(&mut channel::DifferentialA0A1));
it seems to work correctly.

The dependencies that I'm using are:

ads1x1x = "0.2.1"
i2cdev = "0.4.4"
embedded-hal = "0.2.4"
linux-embedded-hal = "0.3.0"
nb = "1.0.0"

Please could you help me with this problem?
Thanks

Simeon

Interest in supporting `async`?

Hey there! I have a project where I'll need to use an ADS1110 inside of an embassy based project.

I'll certainly base my work off of this crate, but I'm not sure the best way of supporting both sync and async approaches. From a first guess, I'd likely keep all the constants and register definitions shared, but have two separate types and I/O impls for blocking and async interfaces.

I'll plan to do this async conversion in a fork to start off, but if I get it to a reasonable place, would you be interested in having me upstream those changes?

Thanks!

ADS1119 support?

Thanks for the great crate! (and all the other ones as well)

Do you know if this library supports or could support the ADS1119? The numbering scheme makes it seem like it might be compatible, but that may be misleading.

We were trying to use 2 ADS1115, but we ran into I2C address conflict issues and the EE I am working with suggested we use the 1119 instead.

I'm fairly new to the embedded world, so any guidance is appreciated.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.