From ab94c36135ad60fc91887ff949b7c6f6b0cecd8a Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Thu, 17 Oct 2019 17:49:36 -0400 Subject: [PATCH 1/6] Initial implementation. --- adafruit_mcp9600.py | 139 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 6 deletions(-) diff --git a/adafruit_mcp9600.py b/adafruit_mcp9600.py index c342f5a..62bf414 100644 --- a/adafruit_mcp9600.py +++ b/adafruit_mcp9600.py @@ -44,14 +44,15 @@ * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice """ -# imports - -__version__ = "0.0.0-auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MCP9600.git" - from struct import unpack from micropython import const from adafruit_bus_device.i2c_device import I2CDevice +from adafruit_register.i2c_struct import UnaryStruct, ROUnaryStruct +from adafruit_register.i2c_bits import RWBits, ROBits +from adafruit_register.i2c_bit import RWBit, ROBit + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MCP9600.git" _DEFAULT_ADDRESS = const(0x67) @@ -61,9 +62,115 @@ _REGISTER_THERM_CFG = const(0x05) _REGISTER_VERSION = const(0x20) -class MCP9600(): + +class BurstModeSamples: + """An enum-like class representing the options for number of burst mode samples.""" + BURST_SAMPLES_1 = 0b000 + BURST_SAMPLES_2 = 0b001 + BURST_SAMPLES_4 = 0b010 + BURST_SAMPLES_8 = 0b011 + BURST_SAMPLES_16 = 0b100 + BURST_SAMPLES_32 = 0b101 + BURST_SAMPLES_64 = 0b110 + BURST_SAMPLES_128 = 0b111 + + +class ShutdownModes: + """An enum-like class representing the options for shutdown modes""" + NORMAL = 0b00 + SHUTDOWN = 0b01 + BURST = 0b10 + + +class MCP9600: """Interface to the MCP9600 thermocouple amplifier breakout""" + # Alert temperature monitor options + AMBIENT = 1 + THERMOCOUPLE = 0 + + # Temperature change type to trigger alert. Rising is heating up. Falling is cooling down. + RISING = 1 + FALLING = 0 + + # Alert output options + ACTIVE_HIGH = 1 + ACTIVE_LOW = 0 + + # Alert mode options + INTERRUPT = 1 # Interrupt clear option must be set when using this mode! + COMPARATOR = 0 + + # TODO: Get clarification on this and potentially update names + # Interrupt clear options + CLEAR_INTERRUPT_FLAG = 1 + NORMAL_STATE = 0 + + # Ambient (cold-junction) temperature sensor resolution options + AMBIENT_RESOLUTION_0_0625 = 0 # 0.0625 degrees Celsius + AMBIENT_RESOLUTION_0_25 = 1 # 0.25 degrees Celsius + + # STATUS - 0x4 + burst_complete = RWBit(0x4, 7) + temperature_update = RWBit(0x4, 6) + input_range = ROBit(0x4, 4) + alert_1 = ROBit(0x4, 0) + alert_2 = ROBit(0x4, 1) + alert_3 = ROBit(0x4, 2) + alert_4 = ROBit(0x4, 3) + # Device Configuration - 0x6 + ambient_resolution = RWBit(0x6, 7) + burst_mode_samples = RWBits(3, 0x6, 2) + shutdown_mode = RWBits(2, 0x6, 0) + # Alert 1 Configuration - 0x8 + _alert_1_interrupt_clear = RWBit(0x8, 7) + _alert_1_monitor = RWBit(0x8, 4) + _alert_1_temp_direction = RWBit(0x8, 3) + _alert_1_state = RWBit(0x8, 2) + _alert_1_mode = RWBit(0x8, 1) + _alert_1_enable = RWBit(0x8, 0) + """Set to ``True`` to enable alert output. Set to ``False`` to disable alert output.""" + # Alert 2 Configuration - 0x9 + _alert_2_interrupt_clear = RWBit(0x9, 7) + _alert_2_monitor = RWBit(0x9, 4) + _alert_2_temp_direction = RWBit(0x9, 3) + _alert_2_state = RWBit(0x9, 2) + _alert_2_mode = RWBit(0x9, 1) + _alert_2_enable = RWBit(0x9, 0) + # Alert 3 Configuration - 0xa + _alert_3_interrupt_clear = RWBit(0xa, 7) + _alert_3_monitor = RWBit(0xa, 4) + _alert_3_temp_direction = RWBit(0xa, 3) + _alert_3_state = RWBit(0xa, 2) + _alert_3_mode = RWBit(0xa, 1) + _alert_3_enable = RWBit(0xa, 0) + # Alert 4 Configuration - 0xb + _alert_4_interrupt_clear = RWBit(0xb, 7) + _alert_4_monitor = RWBit(0xb, 4) + _alert_4_temp_direction = RWBit(0xb, 3) + _alert_4_state = RWBit(0xb, 2) + _alert_4_mode = RWBit(0xb, 1) + _alert_4_enable = RWBit(0xb, 0) + # Alert 1 Hysteresis - 0xc + _alert_1_hysteresis = UnaryStruct(0xc, ">H") + # Alert 2 Hysteresis - 0xd + _alert_2_hysteresis = UnaryStruct(0xd, ">H") + # Alert 3 Hysteresis - 0xe + _alert_3_hysteresis = UnaryStruct(0xe, ">H") + # Alert 4 Hysteresis - 0xf + _alert_4_hysteresis = UnaryStruct(0xf, ">H") + # Alert 1 Limit - 0x10 + _alert_1_temperature_limit = UnaryStruct(0x10, ">H") + # Alert 2 Limit - 0x11 + _alert_2_limit = UnaryStruct(0x11, ">H") + # Alert 3 Limit - 0x12 + _alert_3_limit = UnaryStruct(0x12, ">H") + # Alert 4 Limit - 0x13 + _alert_4_limit = UnaryStruct(0x13, ">H") + # Device ID/Revision - 0x20 + _device_id = ROBits(8, 0x20, 8, register_width=2, lsb_first=False) + _revision_id = ROBits(8, 0x20, 0, register_width=2) + types = ("K", "J", "T", "N", "S", "E", "B", "R") def __init__(self, i2c, address=_DEFAULT_ADDRESS, tctype="K", tcfilter=0): @@ -82,6 +189,26 @@ def __init__(self, i2c, address=_DEFAULT_ADDRESS, tctype="K", tcfilter=0): self.buf[1] = tcfilter | (ttype << 4) with self.i2c_device as tci2c: tci2c.write(self.buf, end=2) + if self._device_id != 0x40: + raise RuntimeError("Failed to find MCP9600 - check wiring!") + + def alert_config(self, *, alert_number, alert_temp_source, alert_temp_limit, alert_hysteresis, + alert_temp_direction, alert_mode, alert_state): + """ For rising temps, hysteresis is below alert limit; falling temps, above alert limit + Alert is enabled by default when set. To disable, use alert_disable method.""" + setattr(self, '_alert_%d_monitor' % alert_number, alert_temp_source) + setattr(self, '_alert_%d_temperature_limit' % alert_number, int(alert_temp_limit / 0.0625)) + setattr(self, '_alert_%d_hysteresis' % alert_number, alert_hysteresis) + setattr(self, '_alert_%d_temp_direction' % alert_number, alert_temp_direction) + setattr(self, '_alert_%d_mode' % alert_number, alert_mode) + setattr(self, '_alert_%d_state' % alert_number, alert_state) + setattr(self, '_alert_%d_enable' % alert_number, True) + + def alert_disable(self, alert_number): + setattr(self, '_alert_%d_enable' % alert_number, False) + + def alert_interrupt_clear(self, alert_number, interrupt_clear=True): + setattr(self, '_alert_%d_interrupt_clear' % alert_number, interrupt_clear) @property def version(self): From 517bf46fe77886fcb232867a35b4f7df3243203a Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Wed, 23 Oct 2019 13:40:18 -0400 Subject: [PATCH 2/6] Features added. --- adafruit_mcp9600.py | 98 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 23 deletions(-) diff --git a/adafruit_mcp9600.py b/adafruit_mcp9600.py index 62bf414..4c1ba8c 100644 --- a/adafruit_mcp9600.py +++ b/adafruit_mcp9600.py @@ -1,6 +1,7 @@ # The MIT License (MIT) # # Copyright (c) 2019 Dan Cogliano for Adafruit Industries +# Copyright (c) 2019 Kattni Rembor for Adafruit Industries # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -26,7 +27,7 @@ CircuitPython driver for the MCP9600 thermocouple I2C amplifier -* Author(s): Dan Cogliano +* Author(s): Dan Cogliano, Kattni Rembor Implementation Notes -------------------- @@ -63,8 +64,15 @@ _REGISTER_VERSION = const(0x20) -class BurstModeSamples: - """An enum-like class representing the options for number of burst mode samples.""" +class MCP9600: + """Interface to the MCP9600 thermocouple amplifier breakout""" + + # Shutdown mode options + NORMAL = 0b00 + SHUTDOWN = 0b01 + BURST = 0b10 + + # Burst mode sample options BURST_SAMPLES_1 = 0b000 BURST_SAMPLES_2 = 0b001 BURST_SAMPLES_4 = 0b010 @@ -74,17 +82,6 @@ class BurstModeSamples: BURST_SAMPLES_64 = 0b110 BURST_SAMPLES_128 = 0b111 - -class ShutdownModes: - """An enum-like class representing the options for shutdown modes""" - NORMAL = 0b00 - SHUTDOWN = 0b01 - BURST = 0b10 - - -class MCP9600: - """Interface to the MCP9600 thermocouple amplifier breakout""" - # Alert temperature monitor options AMBIENT = 1 THERMOCOUPLE = 0 @@ -101,27 +98,35 @@ class MCP9600: INTERRUPT = 1 # Interrupt clear option must be set when using this mode! COMPARATOR = 0 - # TODO: Get clarification on this and potentially update names - # Interrupt clear options - CLEAR_INTERRUPT_FLAG = 1 - NORMAL_STATE = 0 - # Ambient (cold-junction) temperature sensor resolution options AMBIENT_RESOLUTION_0_0625 = 0 # 0.0625 degrees Celsius AMBIENT_RESOLUTION_0_25 = 1 # 0.25 degrees Celsius # STATUS - 0x4 burst_complete = RWBit(0x4, 7) + """Burst complete""" temperature_update = RWBit(0x4, 6) + """Temperature update""" input_range = ROBit(0x4, 4) + """Input range""" alert_1 = ROBit(0x4, 0) + """Alert 1 status.""" alert_2 = ROBit(0x4, 1) + """Alert 2 status.""" alert_3 = ROBit(0x4, 2) + """Alert 3 status.""" alert_4 = ROBit(0x4, 3) + """Alert 4 status.""" # Device Configuration - 0x6 ambient_resolution = RWBit(0x6, 7) + """Ambient (cold-junction) temperature resolution. Options are ``AMBIENT_RESOLUTION_0_0625`` + (0.0625 degrees Celsius) or ``AMBIENT_RESOLUTION_0_25`` (0.25 degrees Celsius).""" burst_mode_samples = RWBits(3, 0x6, 2) + """The number of samples taken during a burst in burst mode. Options are ``BURST_SAMPLES_1``, + ``BURST_SAMPLES_2``, ``BURST_SAMPLES_4``, ``BURST_SAMPLES_8``, ``BURST_SAMPLES_16``, + ``BURST_SAMPLES_32``, ``BURST_SAMPLES_64``, ``BURST_SAMPLES_128``.""" shutdown_mode = RWBits(2, 0x6, 0) + """Shutdown modes. Options are ``NORMAL``, ``SHUTDOWN``, and ``BURST``.""" # Alert 1 Configuration - 0x8 _alert_1_interrupt_clear = RWBit(0x8, 7) _alert_1_monitor = RWBit(0x8, 4) @@ -129,7 +134,6 @@ class MCP9600: _alert_1_state = RWBit(0x8, 2) _alert_1_mode = RWBit(0x8, 1) _alert_1_enable = RWBit(0x8, 0) - """Set to ``True`` to enable alert output. Set to ``False`` to disable alert output.""" # Alert 2 Configuration - 0x9 _alert_2_interrupt_clear = RWBit(0x9, 7) _alert_2_monitor = RWBit(0x9, 4) @@ -194,8 +198,54 @@ def __init__(self, i2c, address=_DEFAULT_ADDRESS, tctype="K", tcfilter=0): def alert_config(self, *, alert_number, alert_temp_source, alert_temp_limit, alert_hysteresis, alert_temp_direction, alert_mode, alert_state): - """ For rising temps, hysteresis is below alert limit; falling temps, above alert limit - Alert is enabled by default when set. To disable, use alert_disable method.""" + """Configure a specified alert pin. Alert is enabled by default when alert is configured. + To disable an alert pin, use ``alert_disable``. + :param int alert_number: The alert pin number. Must be 1-4. + :param alert_temp_source: The temperature source to monitor for the alert. Options are: + ``THERMOCOUPLE`` or ``AMBIENT``. + :param float alert_temp_limit: The temperature in degrees Celsius at which the alert should + trigger. For rising temperatures, the alert will trigger when the + temperature rises above this limit. For falling temperatures, the + alert will trigger when the temperature falls below this limit. + :param float alert_hysteresis: The alert hysteresis range. Must be 0-255 degrees Celsius. + For rising temperatures, the hysteresis is below alert limit. For + falling temperatures, the hysteresis is above alert limit. See + data-sheet for further information. + :param alert_temp_direction: The direction the temperature must change to trigger the alert. + Options are ``RISING`` (heating up) or ``FALLING`` (cooling + down). + :param alert_mode: The alert mode. Options are ``COMPARATOR`` or ``INTERRUPT``. If setting + mode to ``INTERRUPT``, use ``alert_interrupt_clear`` to clear the + interrupt flag. + :param alert_state: Alert pin output state. Options are ``ACTIVE_HIGH`` or ``ACTIVE_LOW``. + + + For example, to configure alert 1: + + .. code-block:: python + + import board + import busio + import digitalio + import adafruit_mcp9600 + + i2c = busio.I2C(board.SCL, board.SDA, frequency=100000) + mcp = adafruit_mcp9600.MCP9600(i2c) + alert_1 = digitalio.DigitalInOut(board.D5) + alert_1.switch_to_input() + + mcp.burst_mode_samples = mcp.BURST_SAMPLES_1 + mcp.ambient_resolution = mcp.AMBIENT_RESOLUTION_0_25 + mcp.alert_config(alert_number=1, alert_temp_source=mcp.THERMOCOUPLE, + alert_temp_limit=25, alert_hysteresis=0, + alert_temp_direction=mcp.RISING, alert_mode=mcp.COMPARATOR, + alert_state=mcp.ACTIVE_LOW) + + """ + if alert_number not in (1, 2, 3, 4): + raise ValueError("Alert pin number must be 1-4.") + if not (0 <= alert_hysteresis < 256): + raise ValueError("Hysteresis value must be 0-255.") setattr(self, '_alert_%d_monitor' % alert_number, alert_temp_source) setattr(self, '_alert_%d_temperature_limit' % alert_number, int(alert_temp_limit / 0.0625)) setattr(self, '_alert_%d_hysteresis' % alert_number, alert_hysteresis) @@ -205,9 +255,12 @@ def alert_config(self, *, alert_number, alert_temp_source, alert_temp_limit, ale setattr(self, '_alert_%d_enable' % alert_number, True) def alert_disable(self, alert_number): + """Configuring an alert using ``alert_config()`` enables the specified alert by default. + Use ``alert_disable`` to disable an alert pin.""" setattr(self, '_alert_%d_enable' % alert_number, False) def alert_interrupt_clear(self, alert_number, interrupt_clear=True): + """Setting ``interrupt_clear==True`` clears the interrupt flag.""" setattr(self, '_alert_%d_interrupt_clear' % alert_number, interrupt_clear) @property @@ -225,7 +278,6 @@ def ambient_temperature(self): value -= 4096 return value - @property def temperature(self): """ Hot junction temperature in Celsius """ From 52b532c24678e0c79f2fd6d1ae7f5e77c3231ea3 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Wed, 23 Oct 2019 13:44:05 -0400 Subject: [PATCH 3/6] lint. --- adafruit_mcp9600.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_mcp9600.py b/adafruit_mcp9600.py index 4c1ba8c..778398a 100644 --- a/adafruit_mcp9600.py +++ b/adafruit_mcp9600.py @@ -48,7 +48,7 @@ from struct import unpack from micropython import const from adafruit_bus_device.i2c_device import I2CDevice -from adafruit_register.i2c_struct import UnaryStruct, ROUnaryStruct +from adafruit_register.i2c_struct import UnaryStruct from adafruit_register.i2c_bits import RWBits, ROBits from adafruit_register.i2c_bit import RWBit, ROBit @@ -244,7 +244,7 @@ def alert_config(self, *, alert_number, alert_temp_source, alert_temp_limit, ale """ if alert_number not in (1, 2, 3, 4): raise ValueError("Alert pin number must be 1-4.") - if not (0 <= alert_hysteresis < 256): + if not 0 <= alert_hysteresis < 256: raise ValueError("Hysteresis value must be 0-255.") setattr(self, '_alert_%d_monitor' % alert_number, alert_temp_source) setattr(self, '_alert_%d_temperature_limit' % alert_number, int(alert_temp_limit / 0.0625)) From 21e0af4d6260b779e29f2a8e9ec885e041e9dc37 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Wed, 23 Oct 2019 13:46:51 -0400 Subject: [PATCH 4/6] It begins. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 5e719c7..f2379a3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ # Uncomment the below if you use native CircuitPython modules such as # digitalio, micropython and busio. List the modules you use. Without it, the # autodoc module docs will fail to generate with a warning. -# autodoc_mock_imports = ["digitalio", "busio"] +autodoc_mock_imports = ["adafruit_register"] intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None),'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)} From 71534e439d41f246ae099f949b104b17155239e7 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Wed, 23 Oct 2019 13:57:12 -0400 Subject: [PATCH 5/6] Still Sphinx. --- adafruit_mcp9600.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/adafruit_mcp9600.py b/adafruit_mcp9600.py index 778398a..4c1e3ef 100644 --- a/adafruit_mcp9600.py +++ b/adafruit_mcp9600.py @@ -200,17 +200,19 @@ def alert_config(self, *, alert_number, alert_temp_source, alert_temp_limit, ale alert_temp_direction, alert_mode, alert_state): """Configure a specified alert pin. Alert is enabled by default when alert is configured. To disable an alert pin, use ``alert_disable``. + :param int alert_number: The alert pin number. Must be 1-4. :param alert_temp_source: The temperature source to monitor for the alert. Options are: ``THERMOCOUPLE`` or ``AMBIENT``. :param float alert_temp_limit: The temperature in degrees Celsius at which the alert should - trigger. For rising temperatures, the alert will trigger when the - temperature rises above this limit. For falling temperatures, the - alert will trigger when the temperature falls below this limit. + trigger. For rising temperatures, the alert will trigger when + the temperature rises above this limit. For falling + temperatures, the alert will trigger when the temperature + falls below this limit. :param float alert_hysteresis: The alert hysteresis range. Must be 0-255 degrees Celsius. - For rising temperatures, the hysteresis is below alert limit. For - falling temperatures, the hysteresis is above alert limit. See - data-sheet for further information. + For rising temperatures, the hysteresis is below alert limit. + For falling temperatures, the hysteresis is above alert + limit. See data-sheet for further information. :param alert_temp_direction: The direction the temperature must change to trigger the alert. Options are ``RISING`` (heating up) or ``FALLING`` (cooling down). From 95e59d4a0fe89e434eb2e29088ee65b849a09280 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Wed, 23 Oct 2019 14:28:29 -0400 Subject: [PATCH 6/6] Updating docs. --- adafruit_mcp9600.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/adafruit_mcp9600.py b/adafruit_mcp9600.py index 4c1e3ef..039dea7 100644 --- a/adafruit_mcp9600.py +++ b/adafruit_mcp9600.py @@ -104,11 +104,11 @@ class MCP9600: # STATUS - 0x4 burst_complete = RWBit(0x4, 7) - """Burst complete""" + """Burst complete.""" temperature_update = RWBit(0x4, 6) - """Temperature update""" + """Temperature update.""" input_range = ROBit(0x4, 4) - """Input range""" + """Input range.""" alert_1 = ROBit(0x4, 0) """Alert 1 status.""" alert_2 = ROBit(0x4, 1) @@ -203,7 +203,8 @@ def alert_config(self, *, alert_number, alert_temp_source, alert_temp_limit, ale :param int alert_number: The alert pin number. Must be 1-4. :param alert_temp_source: The temperature source to monitor for the alert. Options are: - ``THERMOCOUPLE`` or ``AMBIENT``. + ``THERMOCOUPLE`` (hot-junction) or ``AMBIENT`` (cold-junction). + Temperatures are in Celsius. :param float alert_temp_limit: The temperature in degrees Celsius at which the alert should trigger. For rising temperatures, the alert will trigger when the temperature rises above this limit. For falling @@ -216,8 +217,11 @@ def alert_config(self, *, alert_number, alert_temp_source, alert_temp_limit, ale :param alert_temp_direction: The direction the temperature must change to trigger the alert. Options are ``RISING`` (heating up) or ``FALLING`` (cooling down). - :param alert_mode: The alert mode. Options are ``COMPARATOR`` or ``INTERRUPT``. If setting - mode to ``INTERRUPT``, use ``alert_interrupt_clear`` to clear the + :param alert_mode: The alert mode. Options are ``COMPARATOR`` or ``INTERRUPT``. In + comparator mode, the pin will follow the alert, so if the temperature + drops, for example, the alert pin will go back low. In interrupt mode, + by comparison, once the alert goes off, you must manually clear it. If + setting mode to ``INTERRUPT``, use ``alert_interrupt_clear`` to clear the interrupt flag. :param alert_state: Alert pin output state. Options are ``ACTIVE_HIGH`` or ``ACTIVE_LOW``. @@ -258,11 +262,25 @@ def alert_config(self, *, alert_number, alert_temp_source, alert_temp_limit, ale def alert_disable(self, alert_number): """Configuring an alert using ``alert_config()`` enables the specified alert by default. - Use ``alert_disable`` to disable an alert pin.""" + Use ``alert_disable`` to disable an alert pin. + + :param int alert_number: The alert pin number. Must be 1-4. + + """ + if alert_number not in (1, 2, 3, 4): + raise ValueError("Alert pin number must be 1-4.") setattr(self, '_alert_%d_enable' % alert_number, False) def alert_interrupt_clear(self, alert_number, interrupt_clear=True): - """Setting ``interrupt_clear==True`` clears the interrupt flag.""" + """Turns off the alert flag in the MCP9600, and clears the pin state (not used if the alert + is in comparator mode). Required when ``alert_mode`` is ``INTERRUPT``. + + :param int alert_number: The alert pin number. Must be 1-4. + :param bool interrupt_clear: The bit to write the interrupt state flag + + """ + if alert_number not in (1, 2, 3, 4): + raise ValueError("Alert pin number must be 1-4.") setattr(self, '_alert_%d_interrupt_clear' % alert_number, interrupt_clear) @property