# coding: utf-8
import time
from .inout import InOut
from ..tool import ft232h
from .._global import OptionalModule
try:
import smbus2
except (ModuleNotFoundError, ImportError):
smbus2 = OptionalModule("smbus2")
# Register Map
NAU7802_Scale_Registers = {'PU_CTRL': 0x00,
'CTRL1': 0x01,
'CTRL2': 0x02,
'OCAL1_B2': 0x03,
'OCAL1_B1': 0x04,
'OCAL1_B0': 0x05,
'GCAL1_B3': 0x06,
'GCAL1_B2': 0x07,
'GCAL1_B1': 0x08,
'GCAL1_B0': 0x09,
'OCAL2_B2': 0x0A,
'OCAL2_B1': 0x0B,
'OCAL2_B0': 0x0C,
'GCAL2_B3': 0x0D,
'GCAL2_B2': 0x0E,
'GCAL2_B1': 0x0F,
'GCAL2_B0': 0x10,
'I2C_CONTROL': 0x11,
'ADCO_B2': 0x12,
'ADCO_B1': 0x13,
'ADCO_B0': 0x14,
'ADC': 0x15,
'OTP_B1': 0x16,
'OTP_B0': 0x17,
'PGA': 0x1B,
'PGA_PWR': 0x1C,
'DEVICE_REV': 0x1F}
# Bits within the PU_CTRL register
NAU7802_PU_CTRL_Bits = {'PU_CTRL_RR': 0,
'PU_CTRL_PUD': 1,
'PU_CTRL_PUA': 2,
'PU_CTRL_PUR': 3,
'PU_CTRL_CS': 4,
'PU_CTRL_CR': 5,
'PU_CTRL_OSCS': 6,
'PU_CTRL_AVDDS': 7}
# Bits within the CTRL2 register
NAU7802_CTRL2_Bits = {'CTRL2_CALMOD': 0,
'CTRL2_CALS': 2,
'CTRL2_CAL_ERROR': 3,
'CTRL2_CRS': 4,
'CTRL2_CHS': 7}
# Bits within the PGA PWR register
NAU7802_PGA_PWR_Bits = {'PGA_PWR_PGA_CURR': 0,
'PGA_PWR_ADC_CURR': 2,
'PGA_PWR_MSTR_BIAS_CURR': 4,
'PGA_PWR_PGA_CAP_EN': 7}
# Allowed Low drop out regulator voltages
NAU7802_LDO_Values = {2.4: 0b111,
2.7: 0b110,
3.0: 0b101,
3.3: 0b100,
3.6: 0b011,
3.9: 0b010,
4.2: 0b001,
4.5: 0b000}
# Allowed gains
NAU7802_Gain_Values = {1: 0b000,
2: 0b001,
4: 0b010,
8: 0b011,
16: 0b100,
32: 0b101,
64: 0b110,
128: 0b111}
# Allowed samples per second
NAU7802_SPS_Values = {10: 0b000,
20: 0b001,
40: 0b010,
80: 0b011,
320: 0b111}
# Calibration state
NAU7802_Cal_Status = {'CAL_SUCCESS': 0,
'CAL_IN_PROGRESS': 1,
'CAL_FAILURE': 2}
NAU7802_Backends = ['Pi4', 'ft232h']
[docs]class Nau7802(InOut):
"""Class for controlling Sparkfun's NAU7802 load cell conditioner.
The Nau7802 InOut block is meant for reading output values from a NAU7802
load cell conditioner, using the I2C protocol. The output is in Volts by
default, but can be converted into Newtons using ``gain`` and ``offset``.
Warning:
Only available on Raspberry Pi for now !
"""
[docs] def __init__(self,
backend: str,
i2c_port: int = 1,
device_address: int = 0x2A,
gain_hardware: int = 128,
sample_rate: int = 80,
gain: float = 1,
offset: float = 0,
ft232h_ser_num: str = None) -> None:
"""Checks the arguments validity.
Args:
backend (:obj:`str`): The backend for communicating with the NAU7802.
Should be one of:
::
'Pi4', 'ft232h'
i2c_port (:obj:`int`, optional): The I2C port over which the NAU7802
should communicate. On most Raspberry Pi models the default I2C port is
`1`.
device_address (:obj:`int`, optional): The I2C address of the NAU7802. It
is impossible to change this address, so it is not possible to have
several NAU7802 on the same i2c port.
gain_hardware (:obj:`int`, optional): The gain to be used by the
programmable gain amplifier. Setting a high gain allows to read small
voltages with a better precision, but it might saturate the sensor for
higher voltages. Available gains are:
::
1, 2, 4, 8, 16, 32, 64, 128
sample_rate (:obj:`int`, optional): The sample rate for data conversion.
The higher the rate, the greater the noise. Available sample rates are:
::
10, 20, 40, 80, 320
gain (:obj:`float`, optional): Allows to tune the output value according
to the formula:
::
output = gain * tension + offset.
offset (:obj:`float`, optional): Allows to tune the output value
according to the formula:
::
output = gain * tension + offset.
ft232h_ser_num (:obj:`str`, optional): If backend is `'ft232h'`, the
serial number of the ft232h to use for communication.
"""
InOut.__init__(self)
if backend not in NAU7802_Backends:
raise ValueError("backend should be in {}".format(NAU7802_Backends))
if backend == 'Pi4':
self._bus = smbus2.SMBus(i2c_port)
else:
self._bus = ft232h('I2C', ft232h_ser_num)
self._device_address = device_address
if gain_hardware not in NAU7802_Gain_Values:
raise ValueError("gain_hardware should be in {}".format(list(
NAU7802_Gain_Values.keys())))
else:
self._gain_hardware = gain_hardware
if sample_rate not in NAU7802_SPS_Values:
raise ValueError("sample_rate should be in {}".format(list(
NAU7802_SPS_Values.keys())))
else:
self._sample_rate = sample_rate
self._gain = gain
self._offset = offset
[docs] def open(self) -> None:
"""Sets the I2C communication and device"""
if not self._is_connected():
raise IOError("The NAU7802 is not connected")
# Resetting the device
self._set_bit(NAU7802_PU_CTRL_Bits['PU_CTRL_RR'],
NAU7802_Scale_Registers['PU_CTRL'], 1)
time.sleep(0.001)
self._set_bit(NAU7802_PU_CTRL_Bits['PU_CTRL_RR'],
NAU7802_Scale_Registers['PU_CTRL'], 0)
# Powering up the device - takes approx 200µs
self._set_bit(NAU7802_PU_CTRL_Bits['PU_CTRL_PUD'],
NAU7802_Scale_Registers['PU_CTRL'], 1)
self._set_bit(NAU7802_PU_CTRL_Bits['PU_CTRL_PUA'],
NAU7802_Scale_Registers['PU_CTRL'], 1)
t_wait = 0
while not self._get_bit(NAU7802_PU_CTRL_Bits['PU_CTRL_PUR'],
NAU7802_Scale_Registers['PU_CTRL']):
time.sleep(0.001)
t_wait += 0.001
if t_wait > 0.1:
raise TimeoutError
# Setting the Low Drop Out voltage to 3.3V and setting the gain
value = NAU7802_Gain_Values[self._gain_hardware]
self._bus.write_byte_data(self._device_address,
NAU7802_Scale_Registers['CTRL1'], value)
self._set_bit(NAU7802_PU_CTRL_Bits['PU_CTRL_AVDDS'],
NAU7802_Scale_Registers['PU_CTRL'], 1)
# Setting the sample rate
self._bus.write_byte_data(self._device_address,
NAU7802_Scale_Registers['CTRL2'],
NAU7802_SPS_Values[self._sample_rate] << 4)
# Turning off CLK_CHP
self._bus.write_byte_data(self._device_address,
NAU7802_Scale_Registers['ADC'], 0x30)
# Enabling 330pF decoupling cap on channel 2
self._set_bit(NAU7802_PGA_PWR_Bits['PGA_PWR_PGA_CAP_EN'],
NAU7802_Scale_Registers['PGA_PWR'], 1)
# Re-calibrating the analog front-end
self._set_bit(NAU7802_CTRL2_Bits['CTRL2_CALS'],
NAU7802_Scale_Registers['CTRL2'], 1)
t_wait = 0
while self._cal_afe_status() != NAU7802_Cal_Status['CAL_SUCCESS']:
time.sleep(0.001)
t_wait += 0.001
if t_wait > 1:
raise TimeoutError
if self._cal_afe_status() == NAU7802_Cal_Status['CAL_FAILURE']:
raise IOError("Calibration failed !")
[docs] def get_data(self) -> list:
"""Reads the registers containing the conversion result
The output is in Volts by default, and can be converted into Newtons using
gain and offset.
Returns:
:obj:`list`: A list containing the timeframe and the output value
"""
# Waiting for data to be ready
t_wait = 0
while not self._data_available():
time.sleep(0.0001)
t_wait += 0.0001
if t_wait > 0.1:
raise TimeoutError
out = [time.time()]
# Reading the output data
block = self._bus.read_i2c_block_data(self._device_address,
NAU7802_Scale_Registers['ADCO_B2'], 3)
value_raw = (block[0] << 16) | (block[1] << 8) | block[2]
# Converting raw data into Volts or Newtons
if block[0] >> 7:
value = (-(2 ** 23 - 1) + (value_raw & 0x7FFFFF)) / (
2 ** 23 - 1) * 0.5 * 3.3 / self._gain_hardware
else:
value = value_raw / (2 ** 23 - 1) * 0.5 * 3.3 / self._gain_hardware
out.append(self._offset + self._gain * value)
return out
[docs] def close(self) -> None:
"""Powers down the device"""
# Powering down the device
self._set_bit(NAU7802_PU_CTRL_Bits['PU_CTRL_PUD'],
NAU7802_Scale_Registers['PU_CTRL'], 0)
time.sleep(0.001)
self._set_bit(NAU7802_PU_CTRL_Bits['PU_CTRL_PUA'],
NAU7802_Scale_Registers['PU_CTRL'], 0)
self._bus.close()
def _is_connected(self) -> bool:
"""Tries reading a byte from the device
Returns:
:obj:`bool`: True if reading was successful, else False
"""
try:
self._bus.read_byte(self._device_address)
return True
except IOError:
return False
def _data_available(self) -> int:
"""Reads the data available bit
Returns:
:obj:`int`: 1 if data is available, else 0
"""
return self._get_bit(NAU7802_PU_CTRL_Bits['PU_CTRL_CR'],
NAU7802_Scale_Registers['PU_CTRL'])
def _set_bit(self,
bit_number: int,
register_address: int,
bit: int) -> None:
"""Sets a given bit in the specified register
Args:
bit_number (:obj:`int`): Position of the bit in the register
register_address (:obj:`int`): Index of the register
bit (:obj:`int`): Value of the bit
"""
value = self._bus.read_i2c_block_data(self._device_address,
register_address, 1)[0]
if bit:
value |= (1 << bit_number)
else:
value &= ~(1 << bit_number)
self._bus.write_byte_data(self._device_address, register_address, value)
def _get_bit(self,
bit_number: int,
register_address: int) -> bool:
"""Reads a given bit in the specified register
Args:
bit_number (:obj:`int`): Position of the bit in the register
register_address (:obj:`int`): Index of the register
Returns:
:obj:`bool`: True if bit value is 1, else False
"""
value = self._bus.read_i2c_block_data(self._device_address,
register_address, 1)[0]
value = value >> bit_number & 1
return bool(value)
def _cal_afe_status(self) -> int:
"""Reads the calibration status bits
Returns:
:obj:`int`: The int value corresponding to the current calibration status
"""
if self._get_bit(NAU7802_CTRL2_Bits['CTRL2_CAL_ERROR'],
NAU7802_Scale_Registers['CTRL2']):
return NAU7802_Cal_Status['CAL_FAILURE']
elif self._get_bit(NAU7802_CTRL2_Bits['CTRL2_CALS'],
NAU7802_Scale_Registers['CTRL2']):
return NAU7802_Cal_Status['CAL_IN_PROGRESS']
else:
return NAU7802_Cal_Status['CAL_SUCCESS']