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