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

Signed bit in ADS1015 #4

Closed
mattjg908 opened this issue Mar 2, 2022 · 7 comments
Closed

Signed bit in ADS1015 #4

mattjg908 opened this issue Mar 2, 2022 · 7 comments

Comments

@mattjg908
Copy link

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

{:ok, <<val::signed-size(16)>>} <- I2C.write_read(bus, addr, @sensor_register, 2) do

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:

{:ok, <<val::signed-size(16)>>} <- I2C.write_read(bus, addr, @sensor_register, 2) do

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)

val = val / 16 # equivalent to val >> 4

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
Copy link
Collaborator

tomjoro commented Mar 2, 2022

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
Copy link
Author

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
Copy link
Owner

mmmries commented Mar 2, 2022

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
Copy link
Owner

mmmries commented Mar 2, 2022

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
Copy link
Author

mattjg908 commented Mar 3, 2022

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
Copy link
Collaborator

tomjoro commented Mar 5, 2022

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.

  • If gain is 2048 then val returns 2047 when measuring +2.047V
  • If gain is 4096 then val returns 2047 when measuring +4.096V
  • If gain is 6144, then val returns 2047 when measuring +6.144V
  • etc.

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
Copy link
Author

mattjg908 commented Mar 7, 2022

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:

  • there is already an open issue for returning voltage
  • I can get the information I need from what is currently returned
  • I believe I raised the concern about a potential issue from a lack of understanding about what ought to be returned and/or the setting I should be using for these readings

Thanks for the help @tomjoro and @mmmries !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants