Create nau7802py.py

This commit is contained in:
kolossos 2020-07-12 20:03:23 +00:00 committed by GitHub
parent 959ea1d9be
commit b713a3f6f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 407 additions and 0 deletions

407
nau7802py.py Normal file
View File

@ -0,0 +1,407 @@
import smbus
import time
# Fork of https://github.com/longapalooza/nau7802py
# MIT-LICENCE
# Register Map
Scale_Registers = {'NAU7802_PU_CTRL': 0x00,
'NAU7802_CTRL1': 1,
'NAU7802_CTRL2': 2,
'NAU7802_OCAL1_B2': 3,
'NAU7802_OCAL1_B1': 4,
'NAU7802_OCAL1_B0': 5,
'NAU7802_GCAL1_B3': 6,
'NAU7802_GCAL1_B2': 7,
'NAU7802_GCAL1_B1': 8,
'NAU7802_GCAL1_B0': 9,
'NAU7802_OCAL2_B2': 10,
'NAU7802_OCAL2_B1': 11,
'NAU7802_OCAL2_B0': 12,
'NAU7802_GCAL2_B3': 13,
'NAU7802_GCAL2_B2': 14,
'NAU7802_GCAL2_B1': 15,
'NAU7802_GCAL2_B0': 16,
'NAU7802_I2C_CONTROL': 17,
'NAU7802_ADCO_B2': 18,
'NAU7802_ADCO_B1': 19,
'NAU7802_ADCO_B0': 20,
'NAU7802_ADC': 0x15, # Shared ADC and OTP 32:24
'NAU7802_OTP_B1': 22, # OTP 23:16 or 7:0?
'NAU7802_OTP_B0': 23, # OTP 15:8
'NAU7802_PGA': 0x1B,
'NAU7802_PGA_PWR': 0x1C,
'NAU7802_DEVICE_REV': 0x1F}
# Bits within the PU_CRTL register
PU_CTRL_Bits = {'NAU7802_PU_CTRL_RR': 0,
'NAU7802_PU_CTRL_PUD': 1,
'NAU7802_PU_CTRL_PUA': 2,
'NAU7802_PU_CTRL_PUR': 3,
'NAU7802_PU_CTRL_CS': 4,
'NAU7802_PU_CTRL_CR': 5,
'NAU7802_PU_CTRL_OSCS': 6,
'NAU7802_PU_CTRL_AVDDS': 7}
# Bits within the CTRL1 register
CTRL1_Bits = {'NAU7802_CTRL1_GAIN': 2,
'NAU7802_CTRL1_VLDO': 5,
'NAU7802_CTRL1_DRDY_SEL': 6,
'NAU7802_CTRL1_CRP': 7}
# Bits within the CTRL2 register
CTRL2_Bits = {'NAU7802_CTRL2_CALMOD': 0,
'NAU7802_CTRL2_CALS': 2,
'NAU7802_CTRL2_CAL_ERROR': 3,
'NAU7802_CTRL2_CRS': 4,
'NAU7802_CTRL2_CHS': 7}
# Bits within the PGA register
PGA_Bits = {'NAU7802_PGA_CHP_DIS': 0,
'NAU7802_PGA_INV': 3,
'NAU7802_PGA_BYPASS_EN': 4,
'NAU7802_PGA_OUT_EN': 5,
'NAU7802_PGA_LDOMODE': 6,
'NAU7802_PGA_RD_OTP_SEL': 7}
# Bits within the PGA PWR register
PGA_PWR_Bits = {'NAU7802_PGA_PWR_PGA_CURR': 0,
'NAU7802_PGA_PWR_ADC_CURR': 2,
'NAU7802_PGA_PWR_MSTR_BIAS_CURR': 4,
'NAU7802_PGA_PWR_PGA_CAP_EN': 7}
# Allowed Low drop out regulator voltages
NAU7802_LDO_Values = {'NAU7802_LDO_2V4': 0b111,
'NAU7802_LDO_2V7': 0b110,
'NAU7802_LDO_3V0': 0b101,
'NAU7802_LDO_3V3': 0b100,
'NAU7802_LDO_3V6': 0b011,
'NAU7802_LDO_3V9': 0b010,
'NAU7802_LDO_4V2': 0b001,
'NAU7802_LDO_4V5': 0b000}
# Allowed gains
NAU7802_Gain_Values = {'NAU7802_GAIN_128': 0b111,
'NAU7802_GAIN_64': 0b110,
'NAU7802_GAIN_32': 0b101,
'NAU7802_GAIN_16': 0b100,
'NAU7802_GAIN_8': 0b011,
'NAU7802_GAIN_4': 0b010,
'NAU7802_GAIN_2': 0b001,
'NAU7802_GAIN_1': 0b000}
# Allowed samples per second
NAU7802_SPS_Values = {'NAU7802_SPS_320': 0b111,
'NAU7802_SPS_80': 0b011,
'NAU7802_SPS_40': 0b010,
'NAU7802_SPS_20': 0b001,
'NAU7802_SPS_10': 0b000}
# Select between channel values
NAU7802_Channels = {'NAU7802_CHANNEL_1': 0,
'NAU7802_CHANNEL_2': 1}
# Calibration state
NAU7802_Cal_Status = {'NAU7802_CAL_SUCCESS': 0,
'NAU7802_CAL_IN_PROGRESS': 1,
'NAU7802_CAL_FAILURE': 2}
class NAU7802():
# Default constructor
def __init__(self, i2cPort = 1, deviceAddress = 0x2A, zeroOffset = 17759,
calibrationFactor = 53520.0):
self.bus = smbus.SMBus(i2cPort) # This stores the user's requested i2c port
self.deviceAddress = deviceAddress # Default unshifted 7-bit address of the NAU7802
# y = mx + b
self.zeroOffset = zeroOffset; # This is b
self.calibrationFactor = calibrationFactor # This is m. User provides this number so that we can output y when requested
# Returns true if Cycle Ready bit is set (conversion is complete)
def available(self): # Returns true if Cycle Ready bit is set (conversion is complete)
return self.getBit(PU_CTRL_Bits['NAU7802_PU_CTRL_CR'], Scale_Registers['NAU7802_PU_CTRL'])
# Check calibration status.
def calAFEStatus(self): # Check calibration status.
if self.getBit(CTRL2_Bits['NAU7802_CTRL2_CALS'], Scale_Registers['NAU7802_CTRL2']):
return NAU7802_Cal_Status['NAU7802_CAL_IN_PROGRESS']
if self.getBit(CTRL2_Bits['NAU7802_CTRL2_CAL_ERROR'], Scale_Registers['NAU7802_CTRL2']):
return NAU7802_Cal_Status['NAU7802_CAL_FAILURE']
# Calibration passed
return NAU7802_Cal_Status['NAU7802_CAL_SUCCESS']
# Call when scale is setup, level, at running temperature, with nothing on it
def calculateZeroOffset(self, averageAmount): # Also called taring. Call this with nothing on the scale
self.setZeroOffset(self.getAverage(averageAmount))
# Calibrate analog front end of system. Returns true if CAL_ERR bit is 0 (no error)
# Takes approximately 344ms to calibrate; wait up to 1000ms.
# It is recommended that the AFE be re-calibrated any time the gain, SPS, or channel number is changed.
def calibrateAFE(self): # Synchronous calibration of the analog front end of the NAU7802. Returns true if CAL_ERR bit is 0 (no error)
self.beginCalibrateAFE()
return self.waitForCalibrateAFE(1)
# Sets up the NAU7802 for basic function
# If initialize is true (or not specified), default init and calibration is performed
# If initialize is false, then it's up to the caller to initalize and calibrate
# Returns true upon completion
def begin(self, initialized = True): # Check communication and initialize sensor
# Check if the device ack's over I2C
if self.isConnected() == False:
# There are rare times when the sensor is occupied and doesn't ack. A 2nd try resolves this.
if self.isConnected() == False:
return False
result = True # Accumulate a result as we do the setup
if initialized:
result &= self.reset() # Reset all registers
result &= self.powerUp() # Power on analog and digital sections of the scale
result &= self.setLDO(NAU7802_LDO_Values['NAU7802_LDO_3V3']) # Set LDO to 3.3V
result &= self.setGain(NAU7802_Gain_Values['NAU7802_GAIN_128']) # Set gain to 128
result &= self.setSampleRate(NAU7802_SPS_Values['NAU7802_SPS_20']) # Set samples per second to 10
result &= self.setRegister(Scale_Registers['NAU7802_ADC'], 0x30) # Turn off CLK_CHP. From 9.1 power on sequencing.
result &= self.setBit(PGA_PWR_Bits['NAU7802_PGA_PWR_PGA_CAP_EN'], Scale_Registers['NAU7802_PGA_PWR']) # Enable 330pF decoupling cap on chan 2. From 9.14 application circuit note.
result &= self.calibrateAFE() # Re-cal analog front end when we change gain, sample rate, or channel
return result
# Begin asynchronous calibration of the analog front end.
# Poll for completion with calAFEStatus() or wait with waitForCalibrateAFE()
def beginCalibrateAFE(self): # Begin asynchronous calibration of the analog front end of the NAU7802. Poll for completion with calAFEStatus() or wait with waitForCalibrateAFE().
self.setBit(CTRL2_Bits['NAU7802_CTRL2_CALS'], Scale_Registers['NAU7802_CTRL2']);
# Call after zeroing. Provide the float weight sitting on scale. Units do not matter.
def calculateCalibrationFactor(self, weightOnScale, averageAmount): # Call this with the value of the thing on the scale. Sets the calibration factor based on the weight on scale and zero offset.
onScale = self.getAverage(averageAmount)
newCalFactor = (onScale - self.zeroOffset) / weightOnScale
self.setCalibrationFactor(newCalFactor)
# Mask & clear a given bit within a register
def clearBit(self, bitNumber, registerAddress): # Mask & clear a given bit within a register
value = self.getRegister(registerAddress)
value &= ~(1 << bitNumber) # Set this bit
return self.setRegister(registerAddress, value)
# Return the average of a given number of readings
# Gives up after 1000ms so don't call this function to average 8 samples setup at 1Hz output (requires 8s)
def getAverage(self, averageAmount): # Return the average of a given number of readings
total = 0
samplesAcquired = 0
startTime = time.time()
while True:
try:
total += self.getReading()
except:
return False
if samplesAcquired == averageAmount:
break # All done
if time.time() - startTime > 1:
return False # Timeout - Bail with error
samplesAcquired += 1
time.sleep(0.001)
total /= averageAmount;
return total
# Return a given bit within a register
def getBit(self, bitNumber, registerAddress): # Return a given bit within a register
value = self.getRegister(registerAddress)
# value &= (1 << bitNumber) # Clear all but this bit
value = value >> bitNumber & 1
return bool(value)
def getCalibrationFactor(self): # Ask library for this value. Useful for storing value into NVM.
return self.calibrationFactor
# Returns 24-bit reading
# Assumes CR Cycle Ready bit (ADC conversion complete) has been checked to be 1
def getReading(self): # Returns 24-bit reading. Assumes CR Cycle Ready bit (ADC conversion complete) has been checked by .available()
while not self.available():
pass
block = self.bus.read_i2c_block_data(self.deviceAddress, Scale_Registers['NAU7802_ADCO_B2'], 3)
valueRaw = block[0] << 16 # MSB
valueRaw |= block[1] << 8 #MidSB
valueRaw |= block[2] # LSB
# the raw value coming from the ADC is a 24-bit number, so the sign bit now
# resides on bit 23 (0 is LSB) of the container. By shifting the
# value to the left, I move the sign bit to the MSB of the container.
# By casting to a signed container I now have properly recovered
# the sign of the original value
valueShifted = valueRaw << 8
# shift the number back right to recover its intended magnitude
value = valueShifted >> 8
return value
# Get contents of a register
def getRegister(self, registerAddress): # Get contents of a register
try:
return self.bus.read_i2c_block_data(self.deviceAddress, registerAddress, 1)[0]
except:
return False # Error
# Get the revision code of this IC
def getRevisionCode(self): # Get the revision code of this IC. Always 0x0F.
revisionCode = self.getRegister(Scale_Registers['NAU7802_DEVICE_REV']);
return revisionCode & 0x0F
# Returns the y of y = mx + b using the current weight on scale, the cal factor, and the offset.
def getWeight(self, allowNegativeWeights = True, samplesToTake = 10): # Once you've set zero offset and cal factor, you can ask the library to do the calculations for you.
onScale = self.getAverage(samplesToTake)
# Prevent the current reading from being less than zero offset
# This happens when the scale is zero'd, unloaded, and the load cell reports a value slightly less than zero value
# causing the weight to be negative or jump to millions of pounds
if not allowNegativeWeights:
if onScale < self.zeroOffset:
onScale = self.zeroOffset # Force reading to zero
try:
weight = (onScale - self.zeroOffset) / self.calibrationFactor
return weight
except:
print('Needs calibrating')
return False
def getZeroOffset(self): # Ask library for this value. Useful for storing value into NVM.
return self.zeroOffset
# Returns true if device is present
# Tests for device ack to I2C address
def isConnected(self): # Returns true if device acks at the I2C address
try:
self.bus.read_byte(self.deviceAddress)
return True
except:
return False
# Puts scale into low-power mode
def powerDown(self): # Puts scale into low-power 200nA mode
self.clearBit(PU_CTRL_Bits['NAU7802_PU_CTRL_PUD'], Scale_Registers['NAU7802_PU_CTRL'])
return self.clearBit(PU_CTRL_Bits['NAU7802_PU_CTRL_PUA'], Scale_Registers['NAU7802_PU_CTRL'])
# Power up digital and analog sections of scale
def powerUp(self): # Power up digital and analog sections of scale, ~2mA
self.setBit(PU_CTRL_Bits['NAU7802_PU_CTRL_PUD'], Scale_Registers['NAU7802_PU_CTRL']);
self.setBit(PU_CTRL_Bits['NAU7802_PU_CTRL_PUA'], Scale_Registers['NAU7802_PU_CTRL']);
# Wait for Power Up bit to be set - takes approximately 200us
counter = 0;
while True:
if self.getBit(PU_CTRL_Bits['NAU7802_PU_CTRL_PUR'], Scale_Registers['NAU7802_PU_CTRL']) != 0:
break # Good to go
time.sleep(0.001)
if counter > 100:
return False # Error
counter += 1
return True
# Resets all registers to Power Of Defaults
def reset(self): # Resets all registers to Power Of Defaults
self.setBit(PU_CTRL_Bits['NAU7802_PU_CTRL_RR'], Scale_Registers['NAU7802_PU_CTRL']) # Set RR
time.sleep(0.001)
return self.clearBit(PU_CTRL_Bits['NAU7802_PU_CTRL_RR'], Scale_Registers['NAU7802_PU_CTRL']) # Clear RR to leave reset state
# Mask & set a given bit within a register
def setBit(self, bitNumber, registerAddress): # Mask & set a given bit within a register
value = self.getRegister(registerAddress)
value |= (1 << bitNumber) # Set this bit
return self.setRegister(registerAddress, value)
# Pass a known calibration factor into library. Helpful if users is loading settings from NVM.
# If you don't know your cal factor, call setZeroOffset(), then calculateCalibrationFactor() with a known weight
def setCalibrationFactor(self, newCalFactor): # Pass a known calibration factor into library. Helpful if users is loading settings from NVM.
self.calibrationFactor = newCalFactor
# Select between 1 and 2
def setChannel(self, channelNumber): # Select between 1 and 2
if channelNumber == NAU7802_Channels['NAU7802_CHANNEL_1']:
return self.clearBit(CTRL2_Bits['NAU7802_CTRL2_CHS'], Scale_Registers['NAU7802_CTRL2']) # Channel 1 (default)
else:
return self.setBit(CTRL2_Bits['NAU7802_CTRL2_CHS'], Scale_Registers['NAU7802_CTRL2']) # Channel 2
# Set the gain
# x1, 2, 4, 8, 16, 32, 64, 128 are available
def setGain(self, gainValue): # Set the gain. x1, 2, 4, 8, 16, 32, 64, 128 are available
if gainValue > 0b111:
gainValue = 0b111 # Error check
value = self.getRegister(Scale_Registers['NAU7802_CTRL1'])
value &= 0b11111000 # Clear gain bits
value |= gainValue # Mask in new bits
return self.setRegister(Scale_Registers['NAU7802_CTRL1'], value)
# Set Int pin to be high when data is ready (default)
def setIntPolarityHigh(self): # # Set Int pin to be high when data is ready (default)
return self.clearBit(CTRL1_Bits['NAU7802_CTRL1_CRP'], Scale_Registers['NAU7802_CTRL1']) # 0 = CRDY pin is high active (ready when 1)
# Set Int pin to be low when data is ready
def setIntPolarityLow(self): # Set Int pin to be low when data is ready
return self.setBit(CTRL1_Bits['NAU7802_CTRL1_CRP'], Scale_Registers['NAU7802_CTRL1']) # 1 = CRDY pin is low active (ready when 0)
# Set the onboard Low-Drop-Out voltage regulator to a given value
# 2.4, 2.7, 3.0, 3.3, 3.6, 3.9, 4.2, 4.5V are available
def setLDO(self, ldoValue): # Set the onboard Low-Drop-Out voltage regulator to a given value. 2.4, 2.7, 3.0, 3.3, 3.6, 3.9, 4.2, 4.5V are available
if ldoValue > 0b111:
ldoValue = 0b111 # Error check
# Set the value of the LDO
value = self.getRegister(Scale_Registers['NAU7802_CTRL1']);
value &= 0b11000111; # Clear LDO bits
value |= ldoValue << 3; # Mask in new LDO bits
self.setRegister(Scale_Registers['NAU7802_CTRL1'], value);
return self.setBit(PU_CTRL_Bits['NAU7802_PU_CTRL_AVDDS'], Scale_Registers['NAU7802_PU_CTRL']) # Enable the internal LDO
# Send a given value to be written to given address
# Return true if successful
def setRegister(self, registerAddress, value): # Send a given value to be written to given address. Return true if successful
try:
self.bus.write_word_data(self.deviceAddress, registerAddress, value)
except:
return False # Sensor did not ACK
return True
# Set the readings per second
# 10, 20, 40, 80, and 320 samples per second is available
def setSampleRate(self, rate): # Set the readings per second. 10, 20, 40, 80, and 320 samples per second is available
if rate > 0b111:
rate = 0b111 # Error check
value = self.getRegister(Scale_Registers['NAU7802_CTRL2'])
value &= 0b10001111 # Clear CRS bits
value |= rate << 4 # Mask in new CRS bits
return self.setRegister(Scale_Registers['NAU7802_CTRL2'], value)
# Sets the internal variable. Useful for users who are loading values from NVM.
def setZeroOffset(self, newZeroOffset):
self.zeroOffset = newZeroOffset # Sets the internal variable. Useful for users who are loading values from NVM.
# Wait for asynchronous AFE calibration to complete with optional timeout.
# If timeout is not specified (or set to 0), then wait indefinitely.
# Returns true if calibration completes succsfully, otherwise returns false.
def waitForCalibrateAFE(self, timeout = 0): # Wait for asynchronous AFE calibration to complete with optional timeout.
begin = time.time()
cal_ready = 0
while cal_ready == NAU7802_Cal_Status['NAU7802_CAL_IN_PROGRESS']:
if (timeout > 0) and ((time.time() - begin) > timeout):
break
time.sleep(0.001)
cal_ready = self.calAFEStatus()
if cal_ready == NAU7802_Cal_Status['NAU7802_CAL_SUCCESS']:
return True
return False