From b713a3f6f35bde4aaa68068d63b3d3dc56cd7176 Mon Sep 17 00:00:00 2001 From: kolossos Date: Sun, 12 Jul 2020 20:03:23 +0000 Subject: [PATCH] Create nau7802py.py --- nau7802py.py | 407 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 nau7802py.py diff --git a/nau7802py.py b/nau7802py.py new file mode 100644 index 0000000..ada3a14 --- /dev/null +++ b/nau7802py.py @@ -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 +