From ec2c626ffd8dd6764ca2d619ea72a3a3d549e8ff Mon Sep 17 00:00:00 2001 From: Liz Date: Mon, 7 Jul 2025 14:35:29 -0400 Subject: [PATCH] pre-commit, docs, full test --- README.rst | 11 ++ adafruit_as5600.py | 154 ++++++++++------------ docs/conf.py | 2 +- docs/examples.rst | 9 ++ docs/index.rst | 6 +- examples/as5600_fulltest.py | 241 ++++++++++++++++++++++++++++++++++ examples/as5600_simpletest.py | 26 ++-- 7 files changed, 348 insertions(+), 101 deletions(-) create mode 100644 examples/as5600_fulltest.py diff --git a/README.rst b/README.rst index 6f46fad..a6a35f6 100644 --- a/README.rst +++ b/README.rst @@ -94,8 +94,19 @@ Usage Example .. code-block:: python + import time + import board import adafruit_as5600 + i2c = board.I2C() + sensor = adafruit_as5600.AS5600(i2c) + + while True: + print(f"Raw angle: {sensor.raw_angle}") + print(f"Scaled angle: {sensor.angle}") + print(f"Magnitude: {sensor.magnitude}") + time.sleep(2) + Documentation ============= API documentation for this library can be found on `Read the Docs `_. diff --git a/adafruit_as5600.py b/adafruit_as5600.py index f0d702b..2e621e2 100644 --- a/adafruit_as5600.py +++ b/adafruit_as5600.py @@ -26,14 +26,15 @@ Implementation Notes * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register """ -from micropython import const from adafruit_bus_device.i2c_device import I2CDevice +from adafruit_register.i2c_bit import ROBit, RWBit +from adafruit_register.i2c_bits import RWBits from adafruit_register.i2c_struct import ROUnaryStruct, UnaryStruct -from adafruit_register.i2c_bits import RWBits, ROBits -from adafruit_register.i2c_bit import RWBit, ROBit +from micropython import const try: - import typing # pylint: disable=unused-import + from typing import Optional + from busio import I2C except ImportError: pass @@ -42,21 +43,21 @@ __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_AS5600.git" # I2C Address -AS5600_DEFAULT_ADDRESS = const(0x36) +_ADDR = const(0x36) # Register addresses -_AS5600_REG_ZMCO = const(0x00) # ZMCO register (burn count) -_AS5600_REG_ZPOS_H = const(0x01) # Zero position high byte -_AS5600_REG_MPOS_H = const(0x03) # Maximum position high byte -_AS5600_REG_MANG_H = const(0x05) # Maximum angle high byte -_AS5600_REG_CONF_L = const(0x08) # Configuration register low byte -_AS5600_REG_CONF_H = const(0x07) # Configuration register high byte -_AS5600_REG_STATUS = const(0x0B) # Status register -_AS5600_REG_RAWANGLE_H = const(0x0C) # Raw angle high byte -_AS5600_REG_ANGLE_H = const(0x0E) # Scaled angle high byte -_AS5600_REG_AGC = const(0x1A) # Automatic Gain Control register -_AS5600_REG_MAGNITUDE_H = const(0x1B) # Magnitude high byte -_AS5600_REG_BURN = const(0xFF) # Burn command register +_ZMCO = const(0x00) # ZMCO register (burn count) +_ZPOS_H = const(0x01) # Zero position high byte +_MPOS_H = const(0x03) # Maximum position high byte +_MANG_H = const(0x05) # Maximum angle high byte +_CONF_L = const(0x08) # Configuration register low byte +_CONF_H = const(0x07) # Configuration register high byte +_STATUS = const(0x0B) # Status register +_RAWANGLE_H = const(0x0C) # Raw angle high byte +_ANGLE_H = const(0x0E) # Scaled angle high byte +_AGC = const(0x1A) # Automatic Gain Control register +_MAGNITUDE_H = const(0x1B) # Magnitude high byte +_BURN = const(0xFF) # Burn command register # Power mode constants POWER_MODE_NOM = const(0x00) # Normal mode (default) @@ -89,56 +90,70 @@ SLOW_FILTER_4X = const(0x02) # 4x SLOW_FILTER_2X = const(0x03) # 2x # Fast filter threshold constants -FAST_FILTER_THRESH_SLOW_ONLY = const(0x00) # Slow filter only (default) -FAST_FILTER_THRESH_6LSB = const(0x01) # 6 LSB -FAST_FILTER_THRESH_7LSB = const(0x02) # 7 LSB -FAST_FILTER_THRESH_9LSB = const(0x03) # 9 LSB -FAST_FILTER_THRESH_18LSB = const(0x04) # 18 LSB -FAST_FILTER_THRESH_21LSB = const(0x05) # 21 LSB -FAST_FILTER_THRESH_24LSB = const(0x06) # 24 LSB -FAST_FILTER_THRESH_10LSB = const(0x07) # 10 LSB +FAST_FILTER_SLOW_ONLY = const(0x00) # Slow filter only (default) +FAST_FILTER_6LSB = const(0x01) # 6 LSB +FAST_FILTER_7LSB = const(0x02) # 7 LSB +FAST_FILTER_9LSB = const(0x03) # 9 LSB +FAST_FILTER_18LSB = const(0x04) # 18 LSB +FAST_FILTER_21LSB = const(0x05) # 21 LSB +FAST_FILTER_24LSB = const(0x06) # 24 LSB +FAST_FILTER_10LSB = const(0x07) # 10 LSB class AS5600: """Driver for the AS5600 12-bit contactless position sensor. :param ~busio.I2C i2c_bus: The I2C bus the AS5600 is connected to. - :param int address: The I2C device address. Defaults to :const:`AS5600_DEFAULT_ADDRESS` + :param int address: The I2C device address. Defaults to :const:`_ADDR` """ - # Register definitions using adafruit_register - _zmco = ROUnaryStruct(_AS5600_REG_ZMCO, "B") # Read-only burn count + _zmco = ROUnaryStruct(_ZMCO, "B") # Read-only burn count # 12-bit position registers (stored as 16-bit big-endian) - _zpos = UnaryStruct(_AS5600_REG_ZPOS_H, ">H") - _mpos = UnaryStruct(_AS5600_REG_MPOS_H, ">H") - _mang = UnaryStruct(_AS5600_REG_MANG_H, ">H") - _rawangle = ROUnaryStruct(_AS5600_REG_RAWANGLE_H, ">H") - _angle = ROUnaryStruct(_AS5600_REG_ANGLE_H, ">H") + _zpos = UnaryStruct(_ZPOS_H, ">H") + _mpos = UnaryStruct(_MPOS_H, ">H") + _mang = UnaryStruct(_MANG_H, ">H") + _rawangle = ROUnaryStruct(_RAWANGLE_H, ">H") + _angle = ROUnaryStruct(_ANGLE_H, ">H") # 8-bit registers - _agc = ROUnaryStruct(_AS5600_REG_AGC, "B") - _magnitude = ROUnaryStruct(_AS5600_REG_MAGNITUDE_H, ">H") + agc: int = ROUnaryStruct(_AGC, "B") + """The current AGC (Automatic Gain Control) value. + Range is 0-255 in 5V mode, 0-128 in 3.3V mode.""" + _magnitude = ROUnaryStruct(_MAGNITUDE_H, ">H") # Status register bits - _mh = ROBit(_AS5600_REG_STATUS, 3) # MH (magnet too strong) - _ml = ROBit(_AS5600_REG_STATUS, 4) # ML (magnet too weak) - _md = ROBit(_AS5600_REG_STATUS, 5) # MD (magnet detected) + min_gain_overflow: bool = ROBit(_STATUS, 3) # MH (magnet too strong) + """True if AGC minimum gain overflow occurred (magnet too strong).""" + max_gain_overflow: bool = ROBit(_STATUS, 4) # ML (magnet too weak) + """True if AGC maximum gain overflow occurred (magnet too weak).""" + magnet_detected: bool = ROBit(_STATUS, 5) # MD (magnet detected) + """True if a magnet is detected, otherwise False""" # Configuration bits - _power_mode = RWBits(2, _AS5600_REG_CONF_L, 0) - _hysteresis = RWBits(2, _AS5600_REG_CONF_L, 2) - _output_stage = RWBits(2, _AS5600_REG_CONF_L, 4) - _pwm_freq = RWBits(2, _AS5600_REG_CONF_L, 6) - _slow_filter = RWBits(2, _AS5600_REG_CONF_H, 0) - _fast_filter_thresh = RWBits(3, _AS5600_REG_CONF_H, 2) - _watchdog = RWBit(_AS5600_REG_CONF_H, 5) # Bit 13 of the 16-bit config register + _power_mode = RWBits(2, _CONF_L, 0) + _hysteresis = RWBits(2, _CONF_L, 2) + _output_stage = RWBits(2, _CONF_L, 4) + _pwm_freq = RWBits(2, _CONF_L, 6) + _slow_filter = RWBits(2, _CONF_H, 0) + _fast_filter = RWBits(3, _CONF_H, 2) + watchdog: bool = RWBit(_CONF_H, 5) # Bit 13 of the 16-bit config register + """Enable or disable the watchdog timer.""" - def __init__(self, i2c: I2C, address: int = AS5600_DEFAULT_ADDRESS) -> None: - self.i2c_device = I2CDevice(i2c, address) - # Check if we can communicate with the device - # The AS5600 doesn't have a WHO_AM_I register, so we'll just try reading status - _ = self.magnet_detected + def __init__(self, i2c: I2C, address: int = _ADDR) -> None: + try: + self.i2c_device = I2CDevice(i2c, address) + # Check if we can communicate with the device + self.watchdog = False + self.power_mode = POWER_MODE_NOM + self.hysteresis = HYSTERESIS_OFF + self.slow_filter = SLOW_FILTER_16X + self.fast_filter_threshold = FAST_FILTER_SLOW_ONLY + self.z_position = 0 + self.m_position = 4095 + self.max_angle = 4095 + except ValueError: + raise ValueError(f"No I2C device found at address 0x{address:02X}") @property def zm_count(self) -> int: @@ -196,42 +211,11 @@ class AS5600: This is scaled according to ZPOS/MPOS/MANG settings.""" return self._angle & 0x0FFF - @property - def agc_min_gain_overflow(self) -> bool: - """True if AGC minimum gain overflow occurred (magnet too strong).""" - return self._mh - - @property - def agc_max_gain_overflow(self) -> bool: - """True if AGC maximum gain overflow occurred (magnet too weak).""" - return self._ml - - @property - def magnet_detected(self) -> bool: - """True if a magnet is detected.""" - return self._md - - @property - def agc(self) -> int: - """The current AGC (Automatic Gain Control) value. - Range is 0-255 in 5V mode, 0-128 in 3.3V mode.""" - return self._agc - @property def magnitude(self) -> int: """The magnitude value from the CORDIC processor (0-4095).""" return self._magnitude & 0x0FFF - @property - def watchdog(self) -> bool: - """Enable or disable the watchdog timer.""" - return self._watchdog - - @watchdog.setter - def watchdog(self, value: bool) -> None: - """Enable or disable the watchdog timer.""" - self._watchdog = value - @property def power_mode(self) -> int: """The power mode setting. Use POWER_MODE_* constants.""" @@ -294,12 +278,12 @@ class AS5600: @property def fast_filter_threshold(self) -> int: - """The fast filter threshold setting. Use FAST_FILTER_THRESH_* constants.""" - return self._fast_filter_thresh + """The fast filter threshold setting. Use FAST_FILTER_* constants.""" + return self._fast_filter @fast_filter_threshold.setter def fast_filter_threshold(self, value: int) -> None: - """Set the fast filter threshold. Use FAST_FILTER_THRESH_* constants.""" + """Set the fast filter threshold. Use FAST_FILTER_* constants.""" if not 0 <= value <= 7: raise ValueError("Invalid fast filter threshold setting") - self._fast_filter_thresh = value + self._fast_filter = value diff --git a/docs/conf.py b/docs/conf.py index 14d8dd9..abdeaa6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ extensions = [ # 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 = ["digitalio", "busio", "adafruit_register"] autodoc_preserve_defaults = True diff --git a/docs/examples.rst b/docs/examples.rst index 786f936..7102d13 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -6,3 +6,12 @@ Ensure your device works with this simple test. .. literalinclude:: ../examples/as5600_simpletest.py :caption: examples/as5600_simpletest.py :linenos: + +Full test +---------- + +Full test of the library + +.. literalinclude:: ../examples/as5600_fulltest.py + :caption: examples/as5600_fulltest.py + :linenos: diff --git a/docs/index.rst b/docs/index.rst index f167d01..68057b7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,14 +24,12 @@ Table of Contents .. toctree:: :caption: Tutorials -.. todo:: Add any Learn guide links here. If there are none, then simply delete this todo and leave - the toctree above for use later. + Learn Guide: .. toctree:: :caption: Related Products -.. todo:: Add any product links here. If there are none, then simply delete this todo and leave - the toctree above for use later. + Adafruit AS5600 Magnetic Angle Sensor .. toctree:: :caption: Other Links diff --git a/examples/as5600_fulltest.py b/examples/as5600_fulltest.py new file mode 100644 index 0000000..abc153c --- /dev/null +++ b/examples/as5600_fulltest.py @@ -0,0 +1,241 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Liz Clark for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +Full library testing example for the Adafruit AS5600 CircuitPython library + +This example tests all functionality of the AS5600 magnetic angle sensor +""" + +import time +import board +import adafruit_as5600 + +# Initialize I2C and AS5600 +i2c = board.I2C() # uses board.SCL and board.SDA +as5600 = adafruit_as5600.AS5600(i2c) + +print("Adafruit AS5600 Full Test") +print("AS5600 found!") +print() + +# Test zm_count property +zm_count = as5600.zm_count +print(f"ZM Count (burn count): {zm_count}") + +# Test z_position property +z_pos = as5600.z_position +print(f"Z Position: {z_pos}") + +# Test setting z_position (XOR current value with 0xADA to change it) +test_pos = (z_pos ^ 0xADA) & 0x0FFF # XOR with 0xADA and keep within 12-bit range +print(f"Setting Z Position to {test_pos} (0x{test_pos:03X})... ") +try: + as5600.z_position = test_pos + print("Success") + new_z_pos = as5600.z_position + print(f"New Z Position: {new_z_pos} (0x{new_z_pos:03X})") +except Exception as e: + print(f"Failed: {e}") + +# Test m_position property +m_pos = as5600.m_position +print(f"M Position: {m_pos}") + +# Test setting m_position (XOR current value with 0xBEE) +test_m_pos = (m_pos ^ 0xBEE) & 0x0FFF +print(f"Setting M Position to {test_m_pos} (0x{test_m_pos:03X})... ") +try: + as5600.m_position = test_m_pos + print("Success") + new_m_pos = as5600.m_position + print(f"New M Position: {new_m_pos} (0x{new_m_pos:03X})") +except Exception as e: + print(f"Failed: {e}") + +# Test max_angle property +max_angle = as5600.max_angle +print(f"Max Angle: {max_angle}") + +# Test setting max_angle (XOR current value with 0xCAB) +test_max_angle = (max_angle ^ 0xCAB) & 0x0FFF +print(f"Setting Max Angle to {test_max_angle} (0x{test_max_angle:03X})... ") +try: + as5600.max_angle = test_max_angle + print("Success") + new_max_angle = as5600.max_angle + print(f"New Max Angle: {new_max_angle} (0x{new_max_angle:03X})") +except Exception as e: + print(f"Failed: {e}") + +# Test watchdog property +print("Turning on watchdog... ") +try: + as5600.watchdog = True + print("Success") + print(f"Watchdog status: {'ENABLED' if as5600.watchdog else 'DISABLED'}") +except Exception as e: + print(f"Failed: {e}") + +print("Turning off watchdog...") +try: + as5600.watchdog = False + print("Success") + print(f"Watchdog status: {'ENABLED' if as5600.watchdog else 'DISABLED'}") +except Exception as e: + print(f"Failed: {e}") + +# Test power_mode property +print("Setting power mode...") +try: + as5600.power_mode = adafruit_as5600.POWER_MODE_NOM + print("Success") + mode = as5600.power_mode + print("Power mode: ") + if mode == adafruit_as5600.POWER_MODE_NOM: + print("Normal") + elif mode == adafruit_as5600.POWER_MODE_LPM1: + print("Low Power Mode 1") + elif mode == adafruit_as5600.POWER_MODE_LPM2: + print("Low Power Mode 2") + elif mode == adafruit_as5600.POWER_MODE_LPM3: + print("Low Power Mode 3") +except Exception as e: + print(f"Failed: {e}") + +# Test hysteresis property +print("Setting hysteresis...") +try: + as5600.hysteresis = adafruit_as5600.HYSTERESIS_OFF + print("Success") + hysteresis = as5600.hysteresis + print("Hysteresis: ") + if hysteresis == adafruit_as5600.HYSTERESIS_OFF: + print("OFF") + elif hysteresis == adafruit_as5600.HYSTERESIS_1LSB: + print("1 LSB") + elif hysteresis == adafruit_as5600.HYSTERESIS_2LSB: + print("2 LSB") + elif hysteresis == adafruit_as5600.HYSTERESIS_3LSB: + print("3 LSB") +except Exception as e: + print(f"Failed: {e}") + +# Test output_stage property +print("Setting output stage...") +try: + as5600.output_stage = adafruit_as5600.OUTPUT_STAGE_ANALOG_FULL + print("Success") + output_stage = as5600.output_stage + print("Output stage: ") + if output_stage == adafruit_as5600.OUTPUT_STAGE_ANALOG_FULL: + print("Analog Full (0% to 100%)") + elif output_stage == adafruit_as5600.OUTPUT_STAGE_ANALOG_REDUCED: + print("Analog Reduced (10% to 90%)") + elif output_stage == adafruit_as5600.OUTPUT_STAGE_DIGITAL_PWM: + print("Digital PWM") + elif output_stage == adafruit_as5600.OUTPUT_STAGE_RESERVED: + print("Reserved") +except Exception as e: + print(f"Failed: {e}") + +# Test pwm_frequency property +print("Setting PWM frequency...") +try: + as5600.pwm_frequency = adafruit_as5600.PWM_FREQ_115HZ + print("Success") + pwm_freq = as5600.pwm_frequency + print("PWM frequency: ") + if pwm_freq == adafruit_as5600.PWM_FREQ_115HZ: + print("115 Hz") + elif pwm_freq == adafruit_as5600.PWM_FREQ_230HZ: + print("230 Hz") + elif pwm_freq == adafruit_as5600.PWM_FREQ_460HZ: + print("460 Hz") + elif pwm_freq == adafruit_as5600.PWM_FREQ_920HZ: + print("920 Hz") +except Exception as e: + print(f"Failed: {e}") + +# Test slow_filter property +print("Setting slow filter to 16x (options: 16X=0, 8X=1, 4X=2, 2X=3)... ") +try: + as5600.slow_filter = adafruit_as5600.SLOW_FILTER_16X + print("Success") + slow_filter = as5600.slow_filter + print("Slow filter: ") + if slow_filter == adafruit_as5600.SLOW_FILTER_16X: + print("16x") + elif slow_filter == adafruit_as5600.SLOW_FILTER_8X: + print("8x") + elif slow_filter == adafruit_as5600.SLOW_FILTER_4X: + print("4x") + elif slow_filter == adafruit_as5600.SLOW_FILTER_2X: + print("2x") +except Exception as e: + print(f"Failed: {e}") + +# Test fast_filter_threshold property +print("Setting fast filter threshold... ") +try: + as5600.fast_filter_threshold = adafruit_as5600.FAST_FILTER_SLOW_ONLY + print("Success") + fast_thresh = as5600.fast_filter_threshold + print("Fast filter threshold: ", end="") + if fast_thresh == adafruit_as5600.FAST_FILTER_SLOW_ONLY: + print("Slow filter only") + elif fast_thresh == adafruit_as5600.FAST_FILTER_6LSB: + print("6 LSB") + elif fast_thresh == adafruit_as5600.FAST_FILTER_7LSB: + print("7 LSB") + elif fast_thresh == adafruit_as5600.FAST_FILTER_9LSB: + print("9 LSB") + elif fast_thresh == adafruit_as5600.FAST_FILTER_18LSB: + print("18 LSB") + elif fast_thresh == adafruit_as5600.FAST_FILTER_21LSB: + print("21 LSB") + elif fast_thresh == adafruit_as5600.FAST_FILTER_24LSB: + print("24 LSB") + elif fast_thresh == adafruit_as5600.FAST_FILTER_10LSB: + print("10 LSB") +except Exception as e: + print(f"Failed: {e}") + +# Reset position settings to defaults +print("\nResetting position settings to defaults...") +as5600.z_position = 0 +as5600.m_position = 4095 +as5600.max_angle = 4095 + +print("\nStarting continuous angle reading...") +print("=" * 80) + +# Continuously read and display angle values +while True: + # Get angle readings + raw_angle = as5600.raw_angle + angle = as5600.angle + + # Build output string + output = f"Raw: {raw_angle:4d} (0x{raw_angle:03X}) | Scaled: {angle:4d} (0x{angle:03X})" + + # Check status conditions + if as5600.magnet_detected: + output += " | Magnet: YES" + else: + output += " | Magnet: NO " + + if as5600.min_gain_overflow: + output += " | MH: magnet too strong" + + if as5600.max_gain_overflow: + output += " | ML: magnet too weak" + + # Get AGC and Magnitude values + agc = as5600.agc + magnitude = as5600.magnitude + output += f" | AGC: {agc:3d} | Mag: {magnitude:4d}" + + print(output) + time.sleep(2) diff --git a/examples/as5600_simpletest.py b/examples/as5600_simpletest.py index 93b3a4d..3b15de7 100644 --- a/examples/as5600_simpletest.py +++ b/examples/as5600_simpletest.py @@ -1,24 +1,28 @@ # SPDX-FileCopyrightText: Copyright (c) 2025 Liz Clark for Adafruit Industries # # SPDX-License-Identifier: MIT +"""AS5600 Simple Test""" import time + import board + import adafruit_as5600 -# Create I2C bus and sensor instance i2c = board.I2C() sensor = adafruit_as5600.AS5600(i2c) -# Configure sensor -sensor.power_mode = adafruit_as5600.POWER_MODE_NOM -sensor.hysteresis = adafruit_as5600.HYSTERESIS_OFF -sensor.slow_filter = adafruit_as5600.SLOW_FILTER_16X - while True: # Read angle values - print(f"Raw angle: {sensor.raw_angle}") - print(f"Scaled angle: {sensor.angle}") - print(f"Magnitude: {sensor.magnitude}") - print(f"Magnet detected: {sensor.magnet_detected}") - time.sleep(2) \ No newline at end of file + if sensor.magnet_detected: + if sensor.max_gain_overflow is True: + print("Magnet is too weak") + if sensor.min_gain_overflow is True: + print("Magnet is too strong") + print(f"Raw angle: {sensor.raw_angle}") + print(f"Scaled angle: {sensor.angle}") + print(f"Magnitude: {sensor.magnitude}") + else: + print("Waiting for magnet..") + print() + time.sleep(2)