333 lines
13 KiB
Python
Executable file
333 lines
13 KiB
Python
Executable file
# SPDX-FileCopyrightText: Copyright (c) 2023 JG for Cedar Grove Maker Studios
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""
|
|
`cedargrove_nau7802`
|
|
================================================================================
|
|
|
|
A CircuitPython driver class for the NAU7802 24-bit ADC. Supports dual analog
|
|
inputs.
|
|
|
|
|
|
* Author(s): JG
|
|
|
|
Implementation Notes
|
|
--------------------
|
|
|
|
**Hardware:**
|
|
|
|
* `NAU7802 FeatherWing; OSH Park project (16-SOIC version)
|
|
<https://oshpark.com/shared_projects/qFvEU3Bn>`_
|
|
* `NAU7802 FeatherWing; OSH Park project (16-DIP version)
|
|
<https://oshpark.com/shared_projects/ZfryHYnc>`_
|
|
* `SparkFun Quicc Scale (single channel) <https://www.sparkfun.com/products/15242>`_
|
|
* `Adafruit NAU78082 STEMMA QT (single channel) <https://www.adafruit.com/product/4538>`_
|
|
|
|
**Software and Dependencies:**
|
|
|
|
* Adafruit CircuitPython firmware for the supported boards:
|
|
https://circuitpython.org/downloads
|
|
|
|
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
|
|
* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
|
|
"""
|
|
|
|
import struct
|
|
import time
|
|
|
|
from adafruit_bus_device.i2c_device import I2CDevice
|
|
from adafruit_register.i2c_bit import ROBit, RWBit
|
|
|
|
# from adafruit_register.i2c_struct import UnaryStruct
|
|
from adafruit_register.i2c_bits import ROBits, RWBits
|
|
from adafruit_register.i2c_struct import ROUnaryStruct
|
|
|
|
__version__ = "0.0.0+auto.0"
|
|
__repo__ = "https://github.com/CedarGroveStudios/CircuitPython_NAU7802.git"
|
|
|
|
# DEVICE REGISTER MAP
|
|
_PU_CTRL = 0x00 # Power-Up Control RW
|
|
_CTRL1 = 0x01 # Control 1 RW
|
|
_CTRL2 = 0x02 # Control 2 RW
|
|
_ADCO_B2 = 0x12 # ADC_OUT[23:16] R-
|
|
_ADCO_B1 = 0x13 # ADC_OUT[16: 8] R-
|
|
_ADCO_B0 = 0x14 # ADC_OUT[ 7: 0] R-
|
|
_OTP_B1 = 0x15 # OTP[15: 8] R-
|
|
_ADC = 0x15 # ADC Control -W
|
|
_OTP_B0 = 0x16 # OTP[ 7: 0] R-
|
|
_PGA = 0x1B # Programmable Gain Amplifier RW
|
|
_PWR_CTRL = 0x1C # Power Control RW
|
|
_REV_ID = 0x1F # Chip Revision ID R-
|
|
|
|
|
|
class LDOVoltage:
|
|
"""Internal low-dropout voltage regulator settings."""
|
|
|
|
LDO_3V0 = 0x5 # LDO 3.0 volts; _CTRL1[5:3] = 5
|
|
LDO_2V7 = 0x6 # LDO 2.7 volts; _CTRL1[5:3] = 6
|
|
LDO_2V4 = 0x7 # LDO 2.4 volts; _CTRL1[5:3] = 7
|
|
|
|
|
|
class Gain:
|
|
"""Analog differential amplifier gain settings."""
|
|
|
|
GAIN_X1 = 0x0 # Gain X1; _CTRL1[2:0] = 0 (chip default)
|
|
GAIN_X2 = 0x1 # Gain X1; _CTRL1[2:0] = 1
|
|
GAIN_X4 = 0x2 # Gain X1; _CTRL1[2:0] = 2
|
|
GAIN_X8 = 0x3 # Gain X1; _CTRL1[2:0] = 3
|
|
GAIN_X16 = 0x4 # Gain X1; _CTRL1[2:0] = 4
|
|
GAIN_X32 = 0x5 # Gain X1; _CTRL1[2:0] = 5
|
|
GAIN_X64 = 0x6 # Gain X1; _CTRL1[2:0] = 6
|
|
GAIN_X128 = 0x7 # Gain X1; _CTRL1[2:0] = 7
|
|
|
|
|
|
class ConversionRate:
|
|
"""ADC conversion rate settings."""
|
|
|
|
RATE_10SPS = 0x0 # 10 samples/sec; _CTRL2[6:4] = 0 (default)
|
|
RATE_20SPS = 0x1 # 20 samples/sec; _CTRL2[6:4] = 1
|
|
RATE_40SPS = 0x2 # 40 samples/sec; _CTRL2[6:4] = 2
|
|
RATE_80SPS = 0x3 # 80 samples/sec; _CTRL2[6:4] = 3
|
|
RATE_320SPS = 0x7 # 320 samples/sec; _CTRL2[6:4] = 7
|
|
|
|
|
|
class CalibrationMode:
|
|
"""Calibration mode state settings."""
|
|
|
|
INTERNAL = 0x0 # Offset Calibration Internal; _CTRL2[1:0] = 0 (chip default)
|
|
OFFSET = 0x2 # Offset Calibration System; _CTRL2[1:0] = 2
|
|
GAIN = 0x3 # Gain Calibration System; _CTRL2[1:0] = 3
|
|
|
|
|
|
class NAU7802:
|
|
"""The primary NAU7802 class."""
|
|
|
|
def __init__(self, i2c_bus, address=0x2A, active_channels=1):
|
|
"""Instantiate NAU7802; LDO 3v0 volts, gain 128, 10 samples per second
|
|
conversion rate, disabled ADC chopper clock, low ESR caps, and PGA output
|
|
stabilizer cap if in single channel mode."""
|
|
self.i2c_device = I2CDevice(i2c_bus, address)
|
|
if not self.reset():
|
|
raise RuntimeError("NAU7802 device could not be reset")
|
|
if not self.enable(True):
|
|
raise RuntimeError("NAU7802 device could not be enabled")
|
|
self.ldo_voltage = "3V0" # 3.0-volt internal analog power (AVDD)
|
|
self._pu_ldo_source = True # Internal analog power (AVDD)
|
|
self.gain = 128 # X128
|
|
self._c2_conv_rate = ConversionRate.RATE_10SPS # 10SPS default
|
|
self._adc_chop_clock = 0x3 # 0x3 = Disable ADC chopper clock
|
|
self._pga_ldo_mode = 0x0 # 0x0 = Use low ESR capacitors
|
|
self._act_channels = active_channels
|
|
# 0x1 = Enable PGA out stabilizer cap for single channel use
|
|
self._pc_cap_enable = 0x1
|
|
if self._act_channels == 2:
|
|
# 0x0 = Disable PGA out stabilizer cap for dual channel use
|
|
self._pc_cap_enable = 0x0
|
|
self._calib_mode = None # Initialize for later use
|
|
self._adc_out = None # Initialize for later use
|
|
|
|
# DEFINE I2C DEVICE BITS, NYBBLES, BYTES, AND REGISTERS
|
|
# Chip Revision R-
|
|
_rev_id = ROBits(4, _REV_ID, 0, 1, False)
|
|
# Register Reset (RR) RW
|
|
_pu_reg_reset = RWBit(_PU_CTRL, 0, 1, False)
|
|
# Power-Up Digital Circuit (PUD) RW
|
|
_pu_digital = RWBit(_PU_CTRL, 1, 1, False)
|
|
# Power-Up Analog Circuit (PUA) RW
|
|
_pu_analog = RWBit(_PU_CTRL, 2, 1, False)
|
|
# Power-Up Ready Status (PUR) R-
|
|
_pu_ready = ROBit(_PU_CTRL, 3, 1, False)
|
|
# Power-Up Conversion Cycle Start (CS) RW
|
|
_pu_cycle_start = RWBit(_PU_CTRL, 4, 1, False)
|
|
# Power-Up Cycle Ready (CR) R-
|
|
_pu_cycle_ready = ROBit(_PU_CTRL, 5, 1, False)
|
|
# Power-Up AVDD Source (ADDS) RW
|
|
_pu_ldo_source = RWBit(_PU_CTRL, 7, 1, False)
|
|
# Control_1 Gain (GAINS) RW
|
|
_c1_gains = RWBits(3, _CTRL1, 0, 1, False)
|
|
# Control_1 LDO Voltage (VLDO) RW
|
|
_c1_vldo_volts = RWBits(3, _CTRL1, 3, 1, False)
|
|
# Control_2 Calibration Mode (CALMOD) RW
|
|
_c2_cal_mode = RWBits(2, _CTRL2, 0, 1, False)
|
|
# Control_2 Calibration Start (CALS) RW
|
|
_c2_cal_start = RWBit(_CTRL2, 2, 1, False)
|
|
# Control_2 Calibration Error (CAL_ERR) RW
|
|
_c2_cal_error = RWBit(_CTRL2, 3, 1, False)
|
|
# Control_2 Conversion Rate (CRS) RW
|
|
_c2_conv_rate = RWBits(3, _CTRL2, 4, 1, False)
|
|
# Control_2 Channel Select (CHS) RW
|
|
_c2_chan_select = RWBit(_CTRL2, 7, 1, False)
|
|
# ADC Result Output MSByte R-
|
|
_adc_out_2 = ROUnaryStruct(_ADCO_B2, ">B")
|
|
# ADC Result Output MidSByte R-
|
|
_adc_out_1 = ROUnaryStruct(_ADCO_B1, ">B")
|
|
# ADC Result Output LSByte R-
|
|
_adc_out_0 = ROUnaryStruct(_ADCO_B0, ">B")
|
|
# ADC Chopper Clock Frequency Select -W
|
|
_adc_chop_clock = RWBits(2, _ADC, 4, 1, False)
|
|
# PGA Stability/Accuracy Mode (LDOMODE) RW
|
|
_pga_ldo_mode = RWBit(_PGA, 6, 1, False)
|
|
# Power_Ctrl PGA Capacitor (PGA_CAP_EN) RW
|
|
_pc_cap_enable = RWBit(_PWR_CTRL, 7, 1, False)
|
|
|
|
@property
|
|
def chip_revision(self):
|
|
"""The chip revision code."""
|
|
return self._rev_id
|
|
|
|
@property
|
|
def channel(self):
|
|
"""Selected channel number (1 or 2)."""
|
|
return self._c2_chan_select + 1
|
|
|
|
@channel.setter
|
|
def channel(self, chan=1):
|
|
"""Select the active channel. Valid channel numbers are 1 and 2.
|
|
Returns True unless a cycle ready (CR) timeout occurs."""
|
|
|
|
self.read() # Clear the data buffer
|
|
|
|
if chan == 1:
|
|
self._c2_chan_select = 0x0
|
|
elif chan == 2 and self._act_channels == 2:
|
|
self._c2_chan_select = 0x1
|
|
else:
|
|
raise ValueError("Invalid Channel Number")
|
|
|
|
# Check cycle ready flag; timeout after 1.0 sec
|
|
start_check = time.monotonic()
|
|
while not self._pu_cycle_ready:
|
|
if time.monotonic() - start_check > 1.0:
|
|
return False
|
|
return True
|
|
|
|
@property
|
|
def ldo_voltage(self):
|
|
"""Representation of the LDO voltage value."""
|
|
return self._ldo_voltage
|
|
|
|
@ldo_voltage.setter
|
|
def ldo_voltage(self, voltage="EXTERNAL"):
|
|
"""Select the LDO Voltage. Valid voltages are '2V4', '2V7', '3V0'."""
|
|
if not f"LDO_{voltage}" in dir(LDOVoltage):
|
|
raise ValueError("Invalid LDO Voltage")
|
|
self._ldo_voltage = voltage
|
|
if self._ldo_voltage == "2V4":
|
|
self._c1_vldo_volts = LDOVoltage.LDO_2V4
|
|
elif self._ldo_voltage == "2V7":
|
|
self._c1_vldo_volts = LDOVoltage.LDO_2V7
|
|
elif self._ldo_voltage == "3V0":
|
|
self._c1_vldo_volts = LDOVoltage.LDO_3V0
|
|
|
|
@property
|
|
def gain(self):
|
|
"""The programmable amplifier (PGA) gain factor."""
|
|
return self._gain
|
|
|
|
@gain.setter
|
|
def gain(self, factor=1):
|
|
"""Select PGA gain factor. Valid values are 1, 2, 4, 8, 16, 32, 64,
|
|
and 128."""
|
|
if not f"GAIN_X{factor}" in dir(Gain):
|
|
raise ValueError("Invalid Gain Factor")
|
|
self._gain = factor
|
|
if self._gain == 1:
|
|
self._c1_gains = Gain.GAIN_X1
|
|
elif self._gain == 2:
|
|
self._c1_gains = Gain.GAIN_X2
|
|
elif self._gain == 4:
|
|
self._c1_gains = Gain.GAIN_X4
|
|
elif self._gain == 8:
|
|
self._c1_gains = Gain.GAIN_X8
|
|
elif self._gain == 16:
|
|
self._c1_gains = Gain.GAIN_X16
|
|
elif self._gain == 32:
|
|
self._c1_gains = Gain.GAIN_X32
|
|
elif self._gain == 64:
|
|
self._c1_gains = Gain.GAIN_X64
|
|
elif self._gain == 128:
|
|
self._c1_gains = Gain.GAIN_X128
|
|
|
|
@property
|
|
def poll_rate(self):
|
|
"""ADC conversion/polling rate."""
|
|
return self._c2_conv_rate
|
|
|
|
@poll_rate.setter
|
|
def poll_rate(self, rate=0):
|
|
"""Select polling rate. Valid values are 10, 20, 40, 80, and 320."""
|
|
if not f"RATE_{rate}SPS" in dir(ConversionRate):
|
|
raise ValueError("Invalid Conversion Rate")
|
|
self._rate = rate
|
|
if self._rate == 10:
|
|
self._c2_conv_rate = ConversionRate.RATE_10SPS
|
|
if self._rate == 20:
|
|
self._c2_conv_rate = ConversionRate.RATE_20SPS
|
|
if self._rate == 40:
|
|
self._c2_conv_rate = ConversionRate.RATE_40SPS
|
|
if self._rate == 80:
|
|
self._c2_conv_rate = ConversionRate.RATE_80SPS
|
|
if self._rate == 320:
|
|
self._c2_conv_rate = ConversionRate.RATE_320SPS
|
|
|
|
def enable(self, power=True):
|
|
"""Enable(start) or disable(stop) the internal analog and digital
|
|
systems power. Enable = True; Disable (low power) = False. Returns
|
|
True when enabled; False when disabled."""
|
|
self._enable = power
|
|
if self._enable:
|
|
self._pu_analog = True
|
|
self._pu_digital = True
|
|
time.sleep(0.750) # Wait 750ms; minimum 400ms
|
|
self._pu_start = True # Start acquisition system cycling
|
|
return self._pu_ready
|
|
self._pu_analog = False
|
|
self._pu_digital = False
|
|
time.sleep(0.010) # Wait 10ms (200us minimum)
|
|
return False
|
|
|
|
def available(self):
|
|
"""Read the ADC data-ready status. True when data is available; False when
|
|
ADC data is unavailable."""
|
|
return self._pu_cycle_ready
|
|
|
|
def read(self):
|
|
"""Reads the 24-bit ADC data. Returns a signed integer value with
|
|
24-bit resolution. Assumes that the ADC data-ready bit was checked
|
|
to be True."""
|
|
adc = self._adc_out_2 << 24 # [31:24] << MSByte
|
|
adc = adc | (self._adc_out_1 << 16) # [23:16] << MidSByte
|
|
adc = adc | (self._adc_out_0 << 8) # [15: 8] << LSByte
|
|
adc = adc.to_bytes(4, "big") # Pack to 4-byte (32-bit) structure
|
|
value = struct.unpack(">i", adc)[0] # Unpack as 4-byte signed integer
|
|
self._adc_out = value / 128 # Restore to 24-bit signed integer value
|
|
return self._adc_out
|
|
|
|
def reset(self):
|
|
"""Resets all device registers and enables digital system power.
|
|
Returns the power ready status bit value: True when system is ready;
|
|
False when system not ready for use."""
|
|
self._pu_reg_reset = True # Reset all registers
|
|
time.sleep(0.100) # Wait 100ms; 10ms minimum
|
|
self._pu_reg_reset = False
|
|
self._pu_digital = True
|
|
time.sleep(0.750) # Wait 750ms; 400ms minimum
|
|
return self._pu_ready
|
|
|
|
def calibrate(self, mode="INTERNAL"):
|
|
"""Perform the calibration procedure. Valid calibration modes
|
|
are 'INTERNAL', 'OFFSET', and 'GAIN'. True if successful."""
|
|
if not mode in dir(CalibrationMode):
|
|
raise ValueError("Invalid Calibration Mode")
|
|
self._calib_mode = mode
|
|
if self._calib_mode == "INTERNAL": # Internal PGA offset (zero setting)
|
|
self._c2_cal_mode = CalibrationMode.INTERNAL
|
|
elif self._calib_mode == "OFFSET": # External PGA offset (zero setting)
|
|
self._c2_cal_mode = CalibrationMode.OFFSET
|
|
elif self._calib_mode == "GAIN": # External PGA full-scale gain setting
|
|
self._c2_cal_mode = CalibrationMode.GAIN
|
|
self._c2_cal_start = True
|
|
while self._c2_cal_start:
|
|
time.sleep(0.010) # 10ms
|
|
return not self._c2_cal_error
|