nau7802.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. """NAU7802 24-bit ADC driver for load cell / scale applications.
  2. I2C address: 0x2A
  3. Bus: /dev/i2c-1 (GPIO2/GPIO3 on RPi)
  4. """
  5. import logging
  6. import os
  7. import struct
  8. import time
  9. import smbus2
  10. logger = logging.getLogger(__name__)
  11. def _env_int(name: str, default: int) -> int:
  12. value = os.environ.get(name)
  13. if value is None or value == "":
  14. return default
  15. try:
  16. return int(value)
  17. except ValueError:
  18. return default
  19. I2C_BUS = _env_int("SPOOLBUDDY_I2C_BUS", 1)
  20. NAU7802_ADDR = 0x2A
  21. # Register addresses
  22. REG_PU_CTRL = 0x00
  23. REG_CTRL1 = 0x01
  24. REG_CTRL2 = 0x02
  25. REG_ADCO_B2 = 0x12 # ADC output MSB
  26. REG_ADCO_B1 = 0x13
  27. REG_ADCO_B0 = 0x14 # ADC output LSB
  28. REG_ADC = 0x15
  29. REG_PGA = 0x1B
  30. REG_PWR_CTRL = 0x1C
  31. REG_REVISION = 0x1F
  32. # PU_CTRL bits
  33. PU_RR = 0x01 # Register reset
  34. PU_PUD = 0x02 # Power up digital
  35. PU_PUA = 0x04 # Power up analog
  36. PU_PUR = 0x08 # Power up ready (read-only)
  37. PU_CS = 0x10 # Cycle start
  38. PU_CR = 0x20 # Cycle ready (read-only)
  39. PU_OSCS = 0x40 # Oscillator select
  40. PU_AVDDS = 0x80 # AVDD source select
  41. class NAU7802:
  42. def __init__(self, bus: int = I2C_BUS, addr: int = NAU7802_ADDR):
  43. self._bus_num = bus
  44. self._bus = smbus2.SMBus(bus)
  45. self._addr = addr
  46. # CTRL2 bits for AFE calibration
  47. _CTRL2_CALS = 1 << 2
  48. _CTRL2_CAL_ERROR = 1 << 3
  49. def close(self):
  50. self._bus.close()
  51. def read_reg(self, reg: int) -> int:
  52. return self._bus.read_byte_data(self._addr, reg)
  53. def write_reg(self, reg: int, val: int):
  54. self._bus.write_byte_data(self._addr, reg, val & 0xFF)
  55. def _update_bits(self, reg: int, mask: int, value: int):
  56. cur = self.read_reg(reg)
  57. self.write_reg(reg, (cur & ~mask) | (value & mask))
  58. def _set_bit(self, reg: int, bit: int, enabled: bool):
  59. mask = 1 << bit
  60. self._update_bits(reg, mask, mask if enabled else 0)
  61. def _set_field(self, reg: int, shift: int, width: int, value: int):
  62. mask = ((1 << width) - 1) << shift
  63. self._update_bits(reg, mask, value << shift)
  64. def init(self):
  65. """Initialize NAU7802 per datasheet power-on sequencing (Section 8.1).
  66. Datasheet steps:
  67. 1. RR=1 (reset all registers)
  68. 2. RR=0, PUD=1 (enter normal operation; PUD auto-starts AD conversion)
  69. 3. Wait ~200µs for PUR=1
  70. 4. Configure (LDO, gain, rate, etc.)
  71. 5. Tuning (ADC chopper, PGA caps)
  72. 6. (Optional) calibration and flush transients
  73. """
  74. # Step 1: Reset (set RR=1, then RR=0)
  75. self._set_bit(REG_PU_CTRL, 0, True) # RR=1
  76. time.sleep(0.010)
  77. self._set_bit(REG_PU_CTRL, 0, False) # RR=0 exits reset
  78. # Datasheet says "about 200 microseconds" before PUR is set
  79. time.sleep(0.001)
  80. # Step 2: Power up digital (PUD=1 auto-starts AD conversion)
  81. self._set_bit(REG_PU_CTRL, 1, True) # PUD=1
  82. # Step 2b: Power up analog (PUA=1)
  83. self._set_bit(REG_PU_CTRL, 2, True) # PUA=1
  84. time.sleep(0.600) # Wait for LDO and analog section to stabilize
  85. # Step 3: Wait for power-up ready (PUR bit 3)
  86. for _ in range(100):
  87. status = self.read_reg(REG_PU_CTRL)
  88. if status & PU_PUR:
  89. logger.debug(" Power-up ready")
  90. break
  91. time.sleep(0.001)
  92. else:
  93. raise TimeoutError("NAU7802 power-up timeout (PUR bit not set)")
  94. # Check revision register low nibble (datasheet expects 0xF).
  95. revision = self.read_reg(REG_REVISION)
  96. logger.debug(f" Revision: 0x{revision:02X}")
  97. if (revision & 0x0F) != 0x0F:
  98. raise RuntimeError(f"Unexpected NAU7802 revision: 0x{revision:02X} (expected 0x_F)")
  99. # Step 4: Configure device
  100. # Internal LDO enable (AVDDS=1, bit 7) and set voltage to 3.0V
  101. self._set_bit(REG_PU_CTRL, 7, True) # AVDDS=1
  102. self._set_field(REG_CTRL1, shift=3, width=3, value=0b101) # VLDO=3.0V
  103. logger.debug(" LDO: 3.0V (internal)")
  104. # Set gain to 128x (CTRL1 bits 2:0 = 0b111)
  105. self._set_field(REG_CTRL1, shift=0, width=3, value=0b111)
  106. logger.debug(" Gain: 128x")
  107. # Set sample rate to 10 SPS (CTRL2 bits 6:4 = 0b000)
  108. # Note: At 10 SPS, each sample takes ~100ms; first 4 samples = ~400ms to settle
  109. self._set_field(REG_CTRL2, shift=4, width=3, value=0b000)
  110. logger.debug(" Sample rate: 10 SPS")
  111. # Step 5: Tuning per application notes
  112. # Disable ADC chopper clock (ADC bits 5:4 = 0b11)
  113. self._set_field(REG_ADC, shift=4, width=2, value=0b11)
  114. # Enable low-ESR caps on PGA (PGA bit 6 = 0 for improved accuracy)
  115. self._set_bit(REG_PGA, 6, False)
  116. # Step 6: Trigger fresh AD conversion and wait for first result
  117. # CS bit transition 0→1 starts fresh conversion; takes ~4-sample time for result
  118. self._set_bit(REG_PU_CTRL, 4, True) # CS=1
  119. logger.debug(" Conversion started")
  120. # Flush startup transients before calibration
  121. # At 10 SPS, initial 4 samples may contain settling artifacts
  122. self.flush_readings(count=4, timeout_s=1.5)
  123. # Run AFE calibration (internal mode), then flush result
  124. self.calibrate_afe(timeout_ms=1000, mode=0)
  125. self.flush_readings(count=2, timeout_s=1.0)
  126. logger.debug(" Initialization complete")
  127. def begin_calibrate_afe(self, mode: int = 0) -> None:
  128. """Start asynchronous AFE calibration.
  129. mode values match NAU7802 CALMOD: 0=internal, 1=offset, 2=gain.
  130. """
  131. ctrl2 = self.read_reg(REG_CTRL2)
  132. ctrl2 &= 0xFC # clear CALMOD bits[1:0]
  133. ctrl2 |= mode & 0x03
  134. self.write_reg(REG_CTRL2, ctrl2)
  135. # Set CALS (bit 2) to start calibration.
  136. self.write_reg(REG_CTRL2, self.read_reg(REG_CTRL2) | self._CTRL2_CALS)
  137. def wait_for_calibrate_afe(self, timeout_ms: int = 1000) -> bool:
  138. deadline = time.monotonic() + (timeout_ms / 1000.0) if timeout_ms > 0 else None
  139. while True:
  140. ctrl2 = self.read_reg(REG_CTRL2)
  141. if (ctrl2 & self._CTRL2_CALS) == 0:
  142. return (ctrl2 & self._CTRL2_CAL_ERROR) == 0
  143. if deadline is not None and time.monotonic() >= deadline:
  144. return False
  145. time.sleep(0.001)
  146. def calibrate_afe(self, timeout_ms: int = 1000, mode: int = 0) -> None:
  147. """Run AFE calibration per datasheet CTRL2[2] CALS bit sequence.
  148. Datasheet says:
  149. - Write 1 to CALS to start (mode in CALMOD bits [1:0])
  150. - CALS=1 during calibration, 0 when complete
  151. - Check CAL_ERR bit after completion
  152. """
  153. self.begin_calibrate_afe(mode=mode)
  154. if not self.wait_for_calibrate_afe(timeout_ms=timeout_ms):
  155. raise RuntimeError(f"NAU7802 AFE calibration timed out after {timeout_ms}ms")
  156. # Check CAL_ERR bit to ensure no error during calibration
  157. ctrl2 = self.read_reg(REG_CTRL2)
  158. if ctrl2 & self._CTRL2_CAL_ERROR:
  159. raise RuntimeError("NAU7802 AFE calibration completed with CAL_ERR set")
  160. logger.debug(" AFE calibration: OK")
  161. def wait_data_ready(self, timeout_s: float = 1.0) -> bool:
  162. deadline = time.monotonic() + timeout_s
  163. while time.monotonic() < deadline:
  164. if self.data_ready():
  165. return True
  166. time.sleep(0.001)
  167. return False
  168. def flush_readings(self, count: int = 4, timeout_s: float = 1.0) -> None:
  169. flushed = 0
  170. while flushed < count:
  171. if not self.wait_data_ready(timeout_s=timeout_s):
  172. raise TimeoutError("Timeout while flushing startup scale readings")
  173. _ = self.read_raw()
  174. flushed += 1
  175. def data_ready(self) -> bool:
  176. return bool(self.read_reg(REG_PU_CTRL) & PU_CR)
  177. def read_raw(self) -> int:
  178. """Read 24-bit signed ADC value."""
  179. b2 = self.read_reg(REG_ADCO_B2)
  180. b1 = self.read_reg(REG_ADCO_B1)
  181. b0 = self.read_reg(REG_ADCO_B0)
  182. raw = (b2 << 16) | (b1 << 8) | b0
  183. # Sign extend 24-bit to 32-bit
  184. if raw & 0x800000:
  185. raw |= 0xFF000000
  186. raw = struct.unpack("i", struct.pack("I", raw))[0]
  187. return raw