nau7802.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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. def close(self):
  47. self._bus.close()
  48. def read_reg(self, reg: int) -> int:
  49. return self._bus.read_byte_data(self._addr, reg)
  50. def write_reg(self, reg: int, val: int):
  51. self._bus.write_byte_data(self._addr, reg, val & 0xFF)
  52. def _update_bits(self, reg: int, mask: int, value: int):
  53. cur = self.read_reg(reg)
  54. self.write_reg(reg, (cur & ~mask) | (value & mask))
  55. def _set_bit(self, reg: int, bit: int, enabled: bool):
  56. mask = 1 << bit
  57. self._update_bits(reg, mask, mask if enabled else 0)
  58. def _set_field(self, reg: int, shift: int, width: int, value: int):
  59. mask = ((1 << width) - 1) << shift
  60. self._update_bits(reg, mask, value << shift)
  61. def init(self):
  62. """Initialize NAU7802 using the Adafruit library startup sequence."""
  63. # Reset
  64. self._set_bit(REG_PU_CTRL, 0, True) # RR=1
  65. time.sleep(0.010)
  66. self._set_bit(REG_PU_CTRL, 0, False) # RR=0
  67. self._set_bit(REG_PU_CTRL, 1, True) # PUD=1
  68. time.sleep(0.001)
  69. # Enable digital + analog and allow analog section to settle.
  70. self._set_bit(REG_PU_CTRL, 1, True) # PUD=1
  71. self._set_bit(REG_PU_CTRL, 2, True) # PUA=1
  72. time.sleep(0.600)
  73. # Start conversion cycle (PU_CS bit 4) after power-up.
  74. self._set_bit(REG_PU_CTRL, 4, True)
  75. # Wait for power-up ready (PU_PUR bit 3)
  76. for _ in range(100):
  77. status = self.read_reg(REG_PU_CTRL)
  78. if status & PU_PUR:
  79. logger.debug("Power-up ready")
  80. break
  81. time.sleep(0.001)
  82. else:
  83. raise TimeoutError("NAU7802 power-up timeout")
  84. # Check revision register low nibble (Adafruit expects 0xF).
  85. revision = self.read_reg(REG_REVISION)
  86. if (revision & 0x0F) != 0x0F:
  87. raise RuntimeError(f"Unexpected NAU7802 revision register: 0x{revision:02X}")
  88. logger.debug("Revision: 0x%02X", revision)
  89. # Internal LDO enable is PU_CTRL.AVDDS (bit 7); set LDO voltage to 3.0V.
  90. self._set_bit(REG_PU_CTRL, 7, True) # AVDDS=1 (internal LDO)
  91. self._set_field(REG_CTRL1, shift=3, width=3, value=0b101) # VLDO=3.0V
  92. logger.debug("LDO: 3.0V (internal)")
  93. # Gain: 128x (bits 2:0 of CTRL1 = 0b111)
  94. self._set_field(REG_CTRL1, shift=0, width=3, value=0b111)
  95. logger.debug("Gain: 128x")
  96. # Sample rate: 10 SPS (CTRL2 bits 6:4 = 0b000)
  97. self._set_field(REG_CTRL2, shift=4, width=3, value=0b000)
  98. logger.debug("Sample rate: 10 SPS")
  99. # Adafruit tuning: disable ADC chopper clock (ADC bits 5:4 = 0b11)
  100. self._set_field(REG_ADC, shift=4, width=2, value=0b11)
  101. # Adafruit tuning: use low ESR caps (PGA bit 6 = 0)
  102. self._set_bit(REG_PGA, 6, False)
  103. # Start conversion cycle
  104. self._set_bit(REG_PU_CTRL, 4, True)
  105. logger.debug("Conversion started")
  106. # Flush the first reading — the NAU7802 always returns a stale
  107. # max-scale value (0x7FFFFF) on the first conversion after power-up.
  108. for _ in range(200):
  109. if self.data_ready():
  110. self.read_raw() # discard
  111. logger.debug("First reading flushed")
  112. break
  113. time.sleep(0.010)
  114. def data_ready(self) -> bool:
  115. return bool(self.read_reg(REG_PU_CTRL) & PU_CR)
  116. def read_raw(self) -> int:
  117. """Read 24-bit signed ADC value."""
  118. b2 = self.read_reg(REG_ADCO_B2)
  119. b1 = self.read_reg(REG_ADCO_B1)
  120. b0 = self.read_reg(REG_ADCO_B0)
  121. raw = (b2 << 16) | (b1 << 8) | b0
  122. # Sign extend 24-bit to 32-bit
  123. if raw & 0x800000:
  124. raw |= 0xFF000000
  125. raw = struct.unpack("i", struct.pack("I", raw))[0]
  126. return raw