mmmries / ads1115

Elixir Circuit Library for Interacting with an ADS1115 or ADS1015 Analog-to-Digital Chip
10 stars 8 forks source link

Signed bit in ADS1015 #4

Closed mattjg908 closed 2 years ago

mattjg908 commented 2 years ago

Hello, thank you for the nice library! I am not sure if I'm correct about this so I thought I'd ask here...

TLDR, I think this line should be updated

https://github.com/mmmries/ads1115/blob/71811175f8ad3c0d82add4e5b5fa2925de98b881/lib/ads1015.ex#L55

Possible Issue (or just me making a mistake)

I'm using an Rpi3 and a Pimoroni Automation Hat mini which uses the ADS1015 chip

The ADS1015 uses scaling,

The ADS1015 is a 12-bit ADC, but since the 12th bit is the sign-bit there are only 11-bits of resolution available for positive voltage readings. The input voltage for 24v channels is scaled from 0-25.85v (25.85 rather than 24 due to how the resistor divider is set up) to 0-3.3v. Since the full-scale range of the ADC is set to 4.096v, this means that 0-3.3v gives only ~1649 possible usable values making the input measurement granularity somewhere around 0.015v (25.85 / 1649) for the 24v inputs and 0.002v for the 3.3v input.

This point about ADS1015 scaling is also mentioned here,

First, the ADC is 11bit, the 12bit claim of the ADS1015 is misleading since the 12th bit is the sign bit for the differential feature and does not contribute any resolution in single-ended mode, it just disambiguates between positive and negative readings.

I have taken a 5 v wire and plugged it into one of the analog inputs oof the hat and then used this ads11115 library to read the power as a test. For context:

iex(77)> ADS1015.config(ref, address) {:ok, %ADS1015.Config{ comp_latch: false, comp_mode: :traditional, comp_polarity: :active_low, comp_queue: :disabled, data_rate: 1600, gain: 2048, mode: :single_shot, mux: {:ain1, :gnd}, performing_conversion: false }}

So, if I then undo the scaling I'd expect ~5 volts (essentially taking the same formula from the previous link)

scale_down =  fn(reading) -> (((reading / 2048) * 4096) / 3300) * 25.85 end
{:ok, reading} = ADS1015.read(ref, address, {:ain1, :gnd})
scale_down.(reading)
10.026666666666667

So, short story long, I think the line below should ignore the first bit: https://github.com/mmmries/ads1115/blob/71811175f8ad3c0d82add4e5b5fa2925de98b881/lib/ads1015.ex#L55

and perhaps should be:

{:ok, <<val::15-signed, _sign_bit::1, _rest::binary>>} <- I2C.write_read(bus, addr, @sensor_register, 2)

So then continuing with that I divide by 16 (shift) https://github.com/mmmries/ads1115/blob/71811175f8ad3c0d82add4e5b5fa2925de98b881/lib/ads1015.ex#L57

And now when I undo the scaling I get the expected ~5 volts

scale_down.(val)
iex(86)> scale_down.(val)
5.013333333333334

I also tested this update by connecting another Rpi's ground and 3.3 volt to the Pimoroni Hat, and with the changes I mentioned above it reads 3.297833333333333 as expected, without the changes mentioned it here it reads 6.595666666666666

tomjoro commented 2 years ago

Thanks for the information and digging - I wasn't aware of this. Does this only effect the final voltage conversion? Currently this library doesn't actually return voltage, just the 'val' (although there is an open issue about getting final voltage).

mattjg908 commented 2 years ago

Hi @tomjoro , thanks for the quick reply! The only thing I've used the library for is final voltage so I'm not sure if anything else is affected. I just learned of i2c's existence yesterday so I'm still gaining context.

I see the other issue so perhaps this one should be closed in light of that? Happy to help if that is desired/helpful although, again, I'm still gaining context into what a lot of this means.

If the other issue is not implemented, I'm curious on your and @mmmries thoughts on this issue, and I'm wondering what val used for?

mmmries commented 2 years ago

It's been a while since I was using this, but I think converting to a final voltage also requires some knowledge about the inputs to the ADC, right? Meaning that the user would have to pass in the reference voltage that they are comparing against?

In your example above I would have thought that when we pattern match with a <<val::signed-size(16)>> the sign bit would already be account for since we're asking the BEAM to treat this as a signed integer. Do we know how the ADS1015 pads it's 12 bits of data? I think the BEAM is assuming that the sign bit will be the first bit, but if the ADC sends something like 0000 and then a signed twelve-bit number, we would need to change that pattern match to be <<_ignored::unsigned-size(4), val::signed-size(12)>> so that we capture the right number.

mmmries commented 2 years ago

Looking at the datasheet here it looks like the 16-bit register reserves the 4 lowest bits as always being "0" and then the remaining bits as a signed 12-bit number.

It's been a little while since I've thought about these kinds of binaries, so I'm not sure if it matters whether we do the divide by 16 (ie shift 4 bits) before we treat it as a 2's complement number? Maybe the right pattern match is <<val::signed-size(12), _rest ::binary>>?

mattjg908 commented 2 years ago

Hi!

It's been a while since I was using this, but I think converting to a final voltage also requires some knowledge about the inputs to the ADC, right?

I think I'm doing that by first doing ADS1015.read(ref, 72, {:ain0, :gnd}), until I do that I only ever get <<0, 0>> back from I2C.write_read/4. After I do that, it gives me

iex(51)> {:ok, <<val::signed-size(16)>>} = I2C.write_read(bus, addr, <<0>>, 2)
{:ok, <<39, 240>>}

I would have thought that when we pattern match with a <<val::signed-size(16)>> the sign bit would already be account for

I would have expected so as well, I've also tried specifying little-endian which isn't right. It seems to me, for some reason, the sign-bit comes back as the LSB. Maybe it's something to do with the datasheet saying this data comes back in 2's complement? I don't understand why if I just get rid of the first bit, it works as I'd expect.

Maybe the right pattern match is <<val::signed-size(12), _rest ::binary>>?

I did try that (..._rest::binary will err out, seems like you need to mention the size explicitly). While it does seem to negate the need to shift, it still returns an unexpectedly high value 1

I don't know why, but it seems like the sign-bit is the LSB for some reason

tomjoro commented 2 years ago

Hi,

Do you have the PGA (gain) settings at gain: 2048, ? From your snippet above:

iex(77)> ADS1015.config(ref, address)
{:ok,
%ADS1015.Config{
comp_latch: false,
comp_mode: :traditional,
comp_polarity: :active_low,
comp_queue: :disabled,
data_rate: 1600,
gain: 2048,     # <--- gain must be larger than the largest voltage you want to measure
mode: :single_shot,
mux: {:ain1, :gnd},
performing_conversion: false
}}

If so, that would mean the maximum voltage you can measure is +2.048V.

The PGA setting actually controls what the maximum voltage can be read.

As I understand, the way the PGA works is that the PGA zooms in on the voltage range. You need to set the gain to 6144 to read +5V.

And also use the same gain, i.e. 6144, in the voltage conversion.

  def convert_to_voltage(fsr, gain \\ 2048) do
mattjg908 commented 2 years ago

Hi @tomjoro , Thank you for explaining that! Yes, I must have gain set unintentionally low.

I think I'm going to close this issue because:

Thanks for the help @tomjoro and @mmmries !