diff --git a/adafruit_mcp3xxx/analog_in.py b/adafruit_mcp3xxx/analog_in.py index efc2991..894f994 100755 --- a/adafruit_mcp3xxx/analog_in.py +++ b/adafruit_mcp3xxx/analog_in.py @@ -26,40 +26,45 @@ differential ADC readings. * Author(s): Brent Rubell + +.. warning:: + The ADC chips supported by this library do not use negative numbers. If the resulting + differential read is less than 0, then the returned integer value (and voltage value) is ``0``. + If for some reason the voltage on a channel is greater than the reference voltage or + less than 0, then the returned integer value is ``65472‬`` or ``0`` respectively. + """ +from .mcp3xxx import MCP3xxx + class AnalogIn(): """AnalogIn Mock Implementation for ADC Reads. - :param ~mcp3004.MCP3004,~mcp3008.MCP3008 mcp: The mcp object. + :param MCP3002,MCP3004,MCP3008 mcp: The mcp object. :param int positive_pin: Required pin for single-ended. :param int negative_pin: Optional pin for differential reads. - """ def __init__(self, mcp, positive_pin, negative_pin=None): + if not isinstance(mcp, MCP3xxx): + raise ValueError("mcp object is not a sibling of MCP3xxx class.") self._mcp = mcp self._pin_setting = positive_pin - self._negative_pin = negative_pin - self.is_differential = False - if negative_pin is not None: - self.is_differential = True - self._channels = [] - try: - self._pins = self._mcp.MCP3008_DIFF_PINS - except AttributeError: - self._pins = self._mcp.MCP3004_DIFF_PINS - self._pin_setting = self._pins.get((self._pin_setting, self._negative_pin), - "Difference pin not found.") - - def __getitem__(self, key): - return self._channels[self._pins[key]] + self.is_differential = negative_pin is not None + if self.is_differential: + self._pin_setting = self._mcp.DIFF_PINS.get((positive_pin, negative_pin), None) + if self._pin_setting is None: + raise ValueError("Differential pin mapping not defined. Please read the " + "documentation for valid differential channel mappings.") @property def value(self): - """Returns the value of an ADC pin as an integer.""" + """Returns the value of an ADC pin as an integer. Due to 10-bit accuracy of the chip, the + returned values range [0, 65472].""" return self._mcp.read(self._pin_setting, is_differential=self.is_differential) << 6 @property def voltage(self): - """Returns the voltage from the ADC pin as a floating point value.""" + """Returns the voltage from the ADC pin as a floating point value. Due to the 10-bit + accuracy of the chip, returned values range from 0 to (``reference_voltage`` * + 65472 / 65535)""" return (self.value * self._mcp.reference_voltage) / 65535 diff --git a/adafruit_mcp3xxx/mcp3002.py b/adafruit_mcp3xxx/mcp3002.py new file mode 100644 index 0000000..942b36e --- /dev/null +++ b/adafruit_mcp3xxx/mcp3002.py @@ -0,0 +1,62 @@ +# The MIT License (MIT) +# +# Copyright (c) 2018 Brent Rubell for Adafruit +# Copyright (c) 2019 Brendan Doherty +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +:py:class:`~adafruit_mcp3xxx.MCP3002.MCP3002` +================================================ +MCP3002 2-channel, 10-bit, analog-to-digital +converter instance. + +* Author(s): Brent Rubell, Brendan Doherty + +For proper wiring, please refer to `Package Type diagram +`_ and `Pin Description +`_ section of the MCP3002 +datasheet. +""" +from .mcp3xxx import MCP3xxx + +# MCP3002 Pin Mapping +P0 = 0 +P1 = 1 + +class MCP3002(MCP3xxx): + """ + MCP3002 Differential channel mapping. The following list of available differential readings + takes the form ``(positive_pin, negative_pin) = (channel A) - (channel B)``. + + - (P0, P1) = CH0 - CH1 + - (P1, P0) = CH1 - CH0 + + See also the warning in the `AnalogIn`_ class API. + """ + DIFF_PINS = { + (0, 1) : P0, + (1, 0) : P1 + } + + def read(self, pin, is_differential=False): + self._out_buf[0] = 0x40 | ((not is_differential) << 5) | (pin << 4) + with self._spi_device as spi: + #pylint: disable=no-member + spi.write_readinto(self._out_buf, self._in_buf, out_end=2, in_end=2) + return ((self._in_buf[0] & 0x03) << 8) | self._in_buf[1] diff --git a/adafruit_mcp3xxx/mcp3004.py b/adafruit_mcp3xxx/mcp3004.py index 0bb15b9..db407cb 100755 --- a/adafruit_mcp3xxx/mcp3004.py +++ b/adafruit_mcp3xxx/mcp3004.py @@ -26,6 +26,11 @@ converter instance. * Author(s): Brent Rubell + +For proper wiring, please refer to `Package Types diagram +`_ and `Pin Description section +`_ of the MCP3004/MCP3008 +datasheet. """ from .mcp3xxx import MCP3xxx @@ -37,17 +42,24 @@ P3 = 3 class MCP3004(MCP3xxx): - """ - MCP3004 Differential channel mapping. - - 0: CH0 = IN+, CH1 = IN- - - 1: CH1 = IN+, CH0 = IN- - - 2: CH2 = IN+, CH3 = IN- - - 3: CH3 = IN+, CH2 = IN- + MCP3004 Differential channel mapping. The following list of available differential readings + takes the form ``(positive_pin, negative_pin) = (channel A) - (channel B)``. + + - (P0, P1) = CH0 - CH1 + - (P1, P0) = CH1 - CH0 + - (P2, P3) = CH2 - CH3 + - (P3, P2) = CH3 - CH2 + + See also the warning in the `AnalogIn`_ class API. """ - MCP3004_DIFF_PINS = { + DIFF_PINS = { (0, 1) : P0, (1, 0) : P1, (2, 3) : P2, (3, 2) : P3 } + + def __init__(self, spi_bus, cs, ref_voltage=3.3): + super(MCP3004, self).__init__(spi_bus, cs, ref_voltage=ref_voltage) + self._out_buf[0] = 0x01 diff --git a/adafruit_mcp3xxx/mcp3008.py b/adafruit_mcp3xxx/mcp3008.py index dffebbb..99ee54f 100755 --- a/adafruit_mcp3xxx/mcp3008.py +++ b/adafruit_mcp3xxx/mcp3008.py @@ -26,6 +26,11 @@ converter instance. * Author(s): Brent Rubell + +For proper wiring, please refer to the `Package Types diagram +`_ and `Pin Description section +`_ of the MCP3004/MCP3008 +datasheet. """ from .mcp3xxx import MCP3xxx @@ -41,19 +46,22 @@ P7 = 7 class MCP3008(MCP3xxx): - """ - MCP3008 Differential channel mapping. - - 0: CH0 = IN+, CH1 = IN- - - 1: CH1 = IN+, CH0 = IN- - - 2: CH2 = IN+, CH3 = IN- - - 3: CH3 = IN+, CH2 = IN- - - 4: CH4 = IN+, CH5 = IN- - - 5: CH5 = IN+, CH4 = IN- - - 6: CH6 = IN+, CH7 = IN- - - 7: CH7 = IN+, CH6 = IN- + MCP3008 Differential channel mapping. The following list of available differential readings + takes the form ``(positive_pin, negative_pin) = (channel A) - (channel B)``. + + - (P0, P1) = CH0 - CH1 + - (P1, P0) = CH1 - CH0 + - (P2, P3) = CH2 - CH3 + - (P3, P2) = CH3 - CH2 + - (P4, P5) = CH4 - CH5 + - (P5, P4) = CH5 - CH4 + - (P6, P7) = CH6 - CH7 + - (P7, P6) = CH7 - CH6 + + See also the warning in the `AnalogIn`_ class API. """ - MCP3008_DIFF_PINS = { + DIFF_PINS = { (0, 1) : P0, (1, 0) : P1, (2, 3) : P2, @@ -63,3 +71,7 @@ class MCP3008(MCP3xxx): (6, 7) : P6, (7, 6) : P7 } + + def __init__(self, spi_bus, cs, ref_voltage=3.3): + super(MCP3008, self).__init__(spi_bus, cs, ref_voltage=ref_voltage) + self._out_buf[0] = 0x01 diff --git a/adafruit_mcp3xxx/mcp3xxx.py b/adafruit_mcp3xxx/mcp3xxx.py index a9e34f5..83aeb22 100755 --- a/adafruit_mcp3xxx/mcp3xxx.py +++ b/adafruit_mcp3xxx/mcp3xxx.py @@ -40,29 +40,33 @@ * Adafruit CircuitPython firmware for the supported boards: https://github.com/adafruit/circuitpython/releases * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice + +.. note:: The ADC chips' input pins (AKA "channels") are aliased in this library + as integer variables whose names start with "P" (eg ``MCP3008.P0`` is channel 0 on the MCP3008 + chip). Each module that contains a driver class for a particular ADC chip has these aliases + predefined accordingly. This is done for code readability and prevention of erroneous SPI + commands. + +.. important:: + The differential reads (comparisons done by the ADC chip) are limited to certain pairs of + channels. These predefined pairs are referenced in this documentation as differential + channel mappings. Please refer to the driver class of your ADC chip (`MCP3008`_, + `MCP3004`_, `MCP3002`_) for a list of available differential channel mappings. """ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MCP3xxx.git" - -from micropython import const from adafruit_bus_device.spi_device import SPIDevice -# MCP3004/008 data transfer commands -_MCP30084_OUT_BUFF = const(0x00) -_MCP30084_DIFF_READ = const(0x02) -_MCP30084_SINGLE_READ = const(0x3) - - class MCP3xxx: """ - MCP3xxx Interface. + This abstract base class is meant to be inherited by `MCP3008`_, `MCP3004`_, + or `MCP3002`_ child classes. :param ~adafruit_bus_device.spi_device.SPIDevice spi_bus: SPI bus the ADC is connected to. :param ~digitalio.DigitalInOut cs: Chip Select Pin. :param float ref_voltage: Voltage into (Vin) the ADC. - """ def __init__(self, spi_bus, cs, ref_voltage=3.3): self._spi_device = SPIDevice(spi_bus, cs) @@ -72,26 +76,23 @@ def __init__(self, spi_bus, cs, ref_voltage=3.3): @property def reference_voltage(self): - """Returns the MCP3xxx's reference voltage.""" + """Returns the MCP3xxx's reference voltage. (read-only)""" return self._ref_voltage def read(self, pin, is_differential=False): - """SPI Interface for MCP3xxx-based ADCs reads. + """SPI Interface for MCP3xxx-based ADCs reads. Due to 10-bit accuracy, the returned + value ranges [0, 1023]. :param int pin: individual or differential pin. :param bool is_differential: single-ended or differential read. + .. note:: This library offers a helper class called `AnalogIn`_ for both single-ended + and differential reads. If you opt to not implement `AnalogIn`_ during differential + reads, then the ``pin`` parameter should be the first of the two pins associated with + the desired differential channel mapping. """ - command = (_MCP30084_DIFF_READ if is_differential else _MCP30084_SINGLE_READ) << 6 - command |= pin << 3 - self._out_buf[0] = command - self._out_buf[1] = _MCP30084_OUT_BUFF - self._out_buf[2] = _MCP30084_OUT_BUFF + self._out_buf[1] = ((not is_differential) << 7) | (pin << 4) with self._spi_device as spi: #pylint: disable=no-member - spi.write_readinto(self._out_buf, self._in_buf, out_start=0, - out_end=len(self._out_buf), in_start=0, in_end=len(self._in_buf)) - result = (self._in_buf[0] & 0x01) << 9 - result |= self._in_buf[1] << 1 - result |= self._in_buf[2] >> 7 - return result + spi.write_readinto(self._out_buf, self._in_buf) + return ((self._in_buf[1] & 0x03) << 8) | self._in_buf[2] diff --git a/docs/api.rst b/docs/api.rst index f2c126d..e444afb 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,16 +1,21 @@ -API ------------- - -.. automodule:: adafruit_mcp3xxx.mcp3xxx - :members: - -.. automodule:: adafruit_mcp3xxx.mcp3004 - :members: - :show-inheritance: - -.. automodule:: adafruit_mcp3xxx.mcp3008 - :members: - :show-inheritance: - -.. automodule:: adafruit_mcp3xxx.analog_in - :members: +API +------------ + +.. automodule:: adafruit_mcp3xxx.mcp3xxx + :members: + +.. automodule:: adafruit_mcp3xxx.analog_in + :members: + +.. automodule:: adafruit_mcp3xxx.mcp3008 + :members: + :show-inheritance: + +.. automodule:: adafruit_mcp3xxx.mcp3004 + :members: + :show-inheritance: + +.. automodule:: adafruit_mcp3xxx.mcp3002 + :members: + :exclude-members: read + :show-inheritance: diff --git a/docs/examples.rst b/docs/examples.rst index 83f59dc..9f1fdef 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -11,10 +11,18 @@ Ensure your device works with this simple test. :caption: examples/mcp3xxx_mcp3004_single_ended_simpletest.py :linenos: +.. literalinclude:: ../examples/mcp3xxx_mcp3002_single_ended_simpletest.py + :caption: examples/mcp3xxx_mcp3002_single_ended_simpletest.py + :linenos: + .. literalinclude:: ../examples/mcp3xxx_mcp3008_differential_simpletest.py :caption: examples/mcp3xxx_mcp3008_differential_simpletest.py :linenos: .. literalinclude:: ../examples/mcp3xxx_mcp3004_differential_simpletest.py :caption: examples/mcp3xxx_mcp3004_differential_simpletest.py - :linenos: \ No newline at end of file + :linenos: + +.. literalinclude:: ../examples/mcp3xxx_mcp3002_differential_simpletest.py + :caption: examples/mcp3xxx_mcp3002_differential_simpletest.py + :linenos: diff --git a/examples/mcp3xxx_mcp3002_differential_simpletest.py b/examples/mcp3xxx_mcp3002_differential_simpletest.py new file mode 100644 index 0000000..59d7743 --- /dev/null +++ b/examples/mcp3xxx_mcp3002_differential_simpletest.py @@ -0,0 +1,20 @@ +import busio +import digitalio +import board +import adafruit_mcp3xxx.mcp3002 as MCP +from adafruit_mcp3xxx.analog_in import AnalogIn + +# create the spi bus +spi = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI) + +# create the cs (chip select) +cs = digitalio.DigitalInOut(board.D5) + +# create the mcp object +mcp = MCP.MCP3002(spi, cs) + +# create a differential ADC channel between Pin 0 and Pin 1 +chan = AnalogIn(mcp, MCP.P0, MCP.P1) + +print('Differential ADC Value: ', chan.value) +print('Differential ADC Voltage: ' + str(chan.voltage) + 'V') diff --git a/examples/mcp3xxx_mcp3002_single_ended_simpletest.py b/examples/mcp3xxx_mcp3002_single_ended_simpletest.py new file mode 100644 index 0000000..843b2ef --- /dev/null +++ b/examples/mcp3xxx_mcp3002_single_ended_simpletest.py @@ -0,0 +1,20 @@ +import busio +import digitalio +import board +import adafruit_mcp3xxx.mcp3002 as MCP +from adafruit_mcp3xxx.analog_in import AnalogIn + +# create the spi bus +spi = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI) + +# create the cs (chip select) +cs = digitalio.DigitalInOut(board.D5) + +# create the mcp object +mcp = MCP.MCP3002(spi, cs) + +# create an analog input channel on pin 0 +chan = AnalogIn(mcp, MCP.P0) + +print('Raw ADC Value: ', chan.value) +print('ADC Voltage: ' + str(chan.voltage) + 'V')