Skip to content

Commit 168b1ed

Browse files
authored
feat: implement continuous conversion mode (#9)
* feat: add support for the ADS101x family of chips
1 parent c962a85 commit 168b1ed

31 files changed

+2177
-236
lines changed

examples/01_singleshot.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env python3
2+
"""
3+
perform a single-shot measurement
4+
5+
In single-shot mode, the ADC performs one conversion of the input signal
6+
upon request, stores the conversion value to an internal conversion
7+
register, and then enters a power-down state. This mode is intended to
8+
provide significant power savings in systems that only require periodic
9+
conversions or when there are long idle periods between conversions.
10+
11+
This example works with all variations:
12+
- ADS1013, ADS1014, ADS1015
13+
- ADS1113, ADS1114, ADS1115
14+
15+
usage:
16+
pdm run examples/01_singleshot.py
17+
"""
18+
19+
import time
20+
21+
import board # type: ignore
22+
23+
# module busio and board provide no type hints
24+
import busio # type: ignore
25+
26+
from feeph.ads1xxx import DRS, UNIT, Ads1113, Ads1113Config
27+
28+
if __name__ == '__main__':
29+
i2c_bus = busio.I2C(scl=board.SCL, sda=board.SDA)
30+
ads1x13 = Ads1113(i2c_bus=i2c_bus)
31+
32+
# create a configuration
33+
# Without any parameters provided this will match the default
34+
# configuration for this device.
35+
my_config = Ads1113Config()
36+
37+
# take our first measurement
38+
# It is recommended to provide a configuration at least once in order
39+
# to ensure the device uses the expected configuration.
40+
value1 = ads1x13.get_ssc_measurement(config=my_config)
41+
print("value1: {value1}µV")
42+
43+
# take a second measurement
44+
# if no configuration is provided we continue to use the previous one
45+
value2 = ads1x13.get_ssc_measurement()
46+
print("value2: {value1}µV")
47+
48+
# convert from MicroVolt to Volt
49+
# This might skew the value due to conversion to float!
50+
value3 = float(value1) / (1000 * 1000)
51+
print("value3: {value2:0.6f}V")
52+
53+
# show the raw value
54+
# range:
55+
# -32768 ≤ x ≤ 32767
56+
# granularity:
57+
# ADS101x: 15 steps (..., 15, 30, 45, ...)
58+
# ADS111x: 1 step (..., 1, 2, 3, 4, ...)
59+
value4 = ads1x13.get_ssc_measurement(unit=UNIT.STEPS)
60+
print("value4: {value1}")
61+
62+
print('-' * 80)
63+
64+
# take multiple measurements and alternate between 2 configurations
65+
config1 = Ads1113Config(drs=DRS.MODE4)
66+
config2 = Ads1113Config(drs=DRS.MODE5)
67+
while True:
68+
value4 = ads1x13.get_ssc_measurement(config=config1)
69+
print("value4: {value1}µV")
70+
value5 = ads1x13.get_ssc_measurement(config=config2)
71+
print("value5: {value1}µV")
72+
time.sleep(1)

examples/02_continuous.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env python3
2+
"""
3+
monitor a channel (continuous-conversion mode)
4+
5+
In continuous-conversion mode, the ADC automatically begins a conversion of
6+
the input signal as soon as the previous conversion is completed. The rate
7+
of continuous conversion is equal to the programmed data rate. Data can be
8+
read at any time and always reflect the most recent completed conversion.
9+
10+
The ADS1014 and ADS1014 feature a programmable digital comparator that can
11+
issue an alert on the ALERT/RDY pin if the low or high threshold values are
12+
exceeded. Together with continuous-conversion mode this feature can be used
13+
to trigger a hardware interrupt and alert on abnormal conditions.
14+
15+
This example works with all variations:
16+
- ADS1013, ADS1014, ADS1015
17+
- ADS1113, ADS1114, ADS1115
18+
(Please note that the samping rates on ADS101x and 111x are different.)
19+
20+
usage:
21+
pdm run examples/02_continuous.py
22+
"""
23+
24+
import time
25+
26+
import board # type: ignore
27+
28+
# module busio and board provide no type hints
29+
import busio # type: ignore
30+
31+
from feeph.ads1xxx import DOM, DRS, Ads1113, Ads1113Config
32+
33+
if __name__ == '__main__':
34+
i2c_bus = busio.I2C(scl=board.SCL, sda=board.SDA)
35+
ads1x13 = Ads1113(i2c_bus=i2c_bus)
36+
37+
# create a configuration
38+
# - enable continuous conversion mode
39+
# - set desired data rate
40+
# on ADS1x14/ADS1x15: configure comparator (clat, cmod, cpol & cque)
41+
my_config = Ads1113Config(dom=DOM.CCM, drs=DRS.MODE2)
42+
43+
if ads1x13.configure(config=my_config):
44+
while True:
45+
# on an ADS111x in MODE2 there should be a new sample
46+
# every 0.5 seconds (ADS101x is much faster)
47+
value = ads1x13.get_ccm_measurement()
48+
print("value: {value1}µV")
49+
time.sleep(0.5)
50+
else:
51+
print("Unable to configure ADC.")

examples/03_comparator.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env python3
2+
"""
3+
configure the comparator
4+
5+
The ADS1014 and ADS1014 feature a programmable digital comparator that can
6+
issue an alert on the ALERT/RDY pin if the low or high threshold values are
7+
exceeded. Together with continuous-conversion mode this feature can be used
8+
to trigger a hardware interrupt and alert on abnormal conditions.
9+
10+
This example works with all variations:
11+
- ADS1013, ADS1014, ADS1015
12+
- ADS1113, ADS1114, ADS1115
13+
(Please note that the samping rates on ADS101x and 111x are different.)
14+
15+
usage:
16+
pdm run examples/03_comparator.py
17+
"""
18+
19+
import time
20+
21+
import board # type: ignore
22+
23+
# module busio and board provide no type hints
24+
import busio # type: ignore
25+
26+
from feeph.ads1xxx import DOM, DRS, Ads1114, Ads1114Config
27+
28+
if __name__ == '__main__':
29+
i2c_bus = busio.I2C(scl=board.SCL, sda=board.SDA)
30+
ads1x14 = Ads1114(i2c_bus=i2c_bus)
31+
32+
# create a configuration
33+
# - enable continuous conversion mode
34+
# - set desired data rate
35+
# on ADS1x14/ADS1x15: configure comparator (clat, cmod, cpol & cque)
36+
my_config = Ads1114Config(dom=DOM.CCM, drs=DRS.MODE2)
37+
38+
if ads1x14.configure(config=my_config):
39+
while True:
40+
# on an ADS111x in MODE2 there should be a new sample
41+
# every 0.5 seconds (ADS101x is much faster)
42+
value = ads1x14.get_ccm_measurement()
43+
print("value: {value1}µV")
44+
time.sleep(0.5)
45+
else:
46+
print("Unable to configure ADC.")

examples/04_amplifier.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env python3
2+
"""
3+
configure the programmable gain amplifier
4+
5+
This example requires a PGA:
6+
- ADS1014, ADS1015
7+
- ADS1114, ADS1115
8+
9+
usage:
10+
pdm run examples/04_amplifier.py
11+
"""
12+
13+
import board # type: ignore
14+
15+
# module busio and board provide no type hints
16+
import busio # type: ignore
17+
18+
from feeph.ads1xxx import PGA, Ads1114, Ads1114Config
19+
20+
if __name__ == '__main__':
21+
i2c_bus = busio.I2C(scl=board.SCL, sda=board.SDA)
22+
ads1x14 = Ads1114(i2c_bus=i2c_bus)
23+
24+
# make sure the voltage between AIN0 and GND does not exceed 0.5V
25+
# (e.g. use a resistive divider)
26+
#
27+
# check feeph/ads1xxx/settings.py for an explanation of this setting
28+
# or hover on 'PGA' and let your IDE show the docstring
29+
config1 = Ads1114Config(pga=PGA.MODE2) # FSR = ±2.048V
30+
config2 = Ads1114Config(pga=PGA.MODE4) # FSR = ±0.512V
31+
32+
value1 = ads1x14.get_ssc_measurement(config=config1)
33+
print("value1: {value1}µV") # 63µV resolution
34+
value2 = ads1x14.get_ssc_measurement(config=config2)
35+
print("value2: {value2}µV") # 16µV resolution

examples/05_multiplexer.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env python3
2+
"""
3+
configure the input multiplexer
4+
5+
This example requires a MUX:
6+
- ADS1015
7+
- ADS1115
8+
9+
usage:
10+
pdm run examples/05_multiplexer.py
11+
"""
12+
13+
import time
14+
15+
import board # type: ignore
16+
17+
# module busio and board provide no type hints
18+
import busio # type: ignore
19+
20+
from feeph.ads1xxx import MUX, PGA, Ads1115, Ads1115Config
21+
22+
if __name__ == '__main__':
23+
i2c_bus = busio.I2C(scl=board.SCL, sda=board.SDA)
24+
ads1x15 = Ads1115(i2c_bus=i2c_bus)
25+
26+
# configure 2 channels
27+
# - config_abs: measure against ground (±2.0V)
28+
# - config_dif: measure delta, using a higher precision (±0.5V)
29+
#
30+
# check feeph/ads1xxx/settings.py for an explanation of this setting
31+
# or hover on 'MUX' and let your IDE show the docstring
32+
config_abs = Ads1115Config(mux=MUX.MODE4, pga=PGA.MODE1) # AIN0»GND
33+
config_dif = Ads1115Config(mux=MUX.MODE0, pga=PGA.MODE4) # AIN0»AIN1
34+
35+
# switch between both configurations and take measurements
36+
while True:
37+
value1 = ads1x15.get_ssc_measurement(config=config_abs)
38+
print("value1: {value1}µV")
39+
value2 = ads1x15.get_ssc_measurement(config=config_dif)
40+
print("value2: {value1}µV")
41+
time.sleep(1)

examples/demonstrator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,4 @@
3535
ads1115.reset_device_registers()
3636

3737
# take a single-shot measurement
38-
LH.info("measurement: %0.6fV", ads1115.get_singleshot_measurement() / (1000 * 1000))
38+
LH.info("measurement: %0.6fV", ads1115.get_ssc_measurement() / (1000 * 1000))

feeph/ads1xxx/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,16 @@
55

66
# the following imports are provided for user convenience
77
# flake8: noqa: F401
8+
# 12bit / 3300 samples per second
9+
from feeph.ads1xxx.ads1013 import Ads1013, Ads1013Config
10+
from feeph.ads1xxx.ads1014 import Ads1014, Ads1014Config
11+
from feeph.ads1xxx.ads1015 import Ads1015, Ads1015Config
12+
13+
# 16bit / 860 samples per second
814
from feeph.ads1xxx.ads1113 import Ads1113, Ads1113Config
915
from feeph.ads1xxx.ads1114 import Ads1114, Ads1114Config
1016
from feeph.ads1xxx.ads1115 import Ads1115, Ads1115Config
11-
from feeph.ads1xxx.settings import *
17+
18+
# config settings
19+
from feeph.ads1xxx.conversions import UNIT
20+
from feeph.ads1xxx.settings import CLAT, CMOD, CPOL, CQUE, DOM, DRS, MUX, PGA, SSC

feeph/ads1xxx/ads1013.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env python3
2+
"""
3+
ADS1013 - Ultra-Small, Low-Power, I2C-Compatible, 3.3-kSPS, 12-Bit ADCs
4+
With Internal Reference, Oscillator
5+
6+
datasheet: https://www.ti.com/lit/ds/symlink/ads1013.pdf
7+
"""
8+
9+
import logging
10+
11+
from attrs import define
12+
13+
from feeph.ads1xxx.ads1x1x import Ads1x1x, Ads1x1xConfig
14+
from feeph.ads1xxx.conversions import UNIT, convert_step_to_value
15+
from feeph.ads1xxx.settings import CLAT, CMOD, CPOL, CQUE, DOM, DRS, MUX, PGA, SSC
16+
17+
LH = logging.getLogger('feeph.ads1xxx')
18+
19+
20+
@define
21+
class Ads1013Config(Ads1x1xConfig):
22+
# fmt: off
23+
ssc: SSC = SSC.NO_OP # single-shot conversion trigger
24+
dom: DOM = DOM.SSM # device operation mode
25+
drs: DRS = DRS.MODE4 # data rate setting
26+
# fmt: on
27+
28+
def as_uint16(self) -> int:
29+
# non-configurable values are set to their default
30+
value = 0b0000_0000_0000_0000
31+
value |= self.ssc.value
32+
value |= MUX.MODE0.value # no input multiplexer
33+
value |= PGA.MODE2.value # no programmable gain amplifier
34+
value |= self.dom.value
35+
value |= self.drs.value
36+
value |= CMOD.TRD.value # no comparator mode
37+
value |= CPOL.ALO.value # no comparator polarity
38+
value |= CLAT.NLC.value # no comparator latch
39+
value |= CQUE.DIS.value # no comparator queue
40+
return value
41+
42+
# technically it's possible to set the alert threshold registers but it
43+
# doesn't have any effect since there is no comparator which uses them
44+
45+
def get_atlo(self, unit: UNIT = UNIT.MICRO) -> int:
46+
return convert_step_to_value(step=0x8000, unit=unit, pga=PGA.MODE2)
47+
48+
def get_athi(self, unit: UNIT = UNIT.MICRO) -> int:
49+
return convert_step_to_value(step=0x7FFF, unit=unit, pga=PGA.MODE2)
50+
51+
52+
class Ads1013(Ads1x1x):
53+
_has_pga = False

feeph/ads1xxx/ads1014.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env python3
2+
"""
3+
ADS1014 - Ultra-Small, Low-Power, I2C-Compatible, 3.3-kSPS, 12-Bit ADCs
4+
With Internal Reference, Oscillator, and Programmable Comparator
5+
6+
datasheet: https://www.ti.com/lit/ds/symlink/ads1014.pdf
7+
"""
8+
9+
import logging
10+
11+
from attrs import define
12+
13+
from feeph.ads1xxx.ads1x1x import Ads1x1x, Ads1x1xConfig
14+
from feeph.ads1xxx.conversions import UNIT, convert_step_to_value, convert_value_to_step
15+
from feeph.ads1xxx.settings import CLAT, CMOD, CPOL, CQUE, DOM, DRS, MUX, PGA, SSC
16+
17+
LH = logging.getLogger('feeph.ads1xxx')
18+
19+
20+
@define
21+
class Ads1014Config(Ads1x1xConfig):
22+
# fmt: off
23+
ssc: SSC = SSC.NO_OP # single-shot conversion trigger
24+
pga: PGA = PGA.MODE2 # programmable gain amplifier
25+
dom: DOM = DOM.SSM # device operation mode
26+
drs: DRS = DRS.MODE4 # data rate setting
27+
cmod: CMOD = CMOD.TRD # comparator mode
28+
cpol: CPOL = CPOL.ALO # comparator polarity
29+
clat: CLAT = CLAT.NLC # comparator latch
30+
cque: CQUE = CQUE.DIS # comparator queue
31+
atlo: int = 0x8000 # alert threshold low (-32768)
32+
athi: int = 0x7FFF # alert threshold high (32767)
33+
# fmt: on
34+
35+
def as_uint16(self) -> int:
36+
value = 0b0000_0000_0000_0000
37+
value |= self.ssc.value
38+
value |= MUX.MODE0.value # no input multiplexer
39+
value |= self.pga.value
40+
value |= self.dom.value
41+
value |= self.drs.value
42+
value |= self.cmod.value
43+
value |= self.cpol.value
44+
value |= self.clat.value
45+
value |= self.cque.value
46+
return value
47+
48+
def get_atlo(self, unit: UNIT = UNIT.MICRO) -> int:
49+
return convert_step_to_value(step=self.atlo, unit=unit, pga=self.pga)
50+
51+
def set_atlo(self, value: int, unit: UNIT = UNIT.MICRO) -> bool:
52+
try:
53+
self.atlo = convert_value_to_step(value=value, unit=unit, pga=self.pga)
54+
return True
55+
except ValueError:
56+
return False
57+
58+
def get_athi(self, unit: UNIT = UNIT.MICRO) -> int:
59+
return convert_step_to_value(step=self.athi, unit=unit, pga=self.pga)
60+
61+
def set_athi(self, value: int, unit: UNIT = UNIT.MICRO) -> bool:
62+
try:
63+
self.athi = convert_value_to_step(value=value, unit=unit, pga=self.pga)
64+
return True
65+
except ValueError:
66+
return False
67+
68+
69+
class Ads1014(Ads1x1x):
70+
_has_pga = True

0 commit comments

Comments
 (0)