Browse Source

Set up scale readings

maziggy 3 months ago
parent
commit
a71e5db2bc
2 changed files with 219 additions and 8 deletions
  1. 49 8
      spoolbuddy/README.md
  2. 170 0
      spoolbuddy/scale_diag.py

+ 49 - 8
spoolbuddy/README.md

@@ -27,13 +27,14 @@
 
 ### Setup Steps
 
-#### 1. Enable SPI
+#### 1. Enable SPI and I2C
 
-After a fresh Raspberry Pi OS install, SPI is disabled by default.
+After a fresh Raspberry Pi OS install, SPI and I2C are disabled by default.
 
 ```bash
 sudo raspi-config
 # Navigate to: Interface Options -> SPI -> Enable
+# Navigate to: Interface Options -> I2C -> Enable
 sudo reboot
 ```
 
@@ -42,40 +43,64 @@ Verify after reboot:
 ```bash
 ls /dev/spidev0.*
 # Should show: /dev/spidev0.0  /dev/spidev0.1
+
+ls /dev/i2c-*
+# Should include: /dev/i2c-1
 ```
 
-#### 2. Disable automatic CS (kernel SPI chip-select)
+#### 2. Configure `/boot/firmware/config.txt`
 
-Since we use manual CS on GPIO23, we need to tell the SPI driver not to
-drive any hardware CS pins. Add this to `/boot/firmware/config.txt`:
+Add the following lines under the `[all]` section:
 
 ```
+# SpoolBuddy: I2C bus 0 for NAU7802 scale (GPIO0/GPIO1)
+dtparam=i2c_vc=on
+
+# SpoolBuddy: Disable SPI auto CS (manual CS on GPIO23 for PN5180)
 dtoverlay=spi0-0cs
 ```
 
+- `i2c_vc=on` enables I2C bus 0 (GPIO0/GPIO1). The default `i2c_arm` only
+  enables bus 1 (GPIO2/GPIO3). The NAU7802 is wired to bus 0.
+- `spi0-0cs` disables the kernel SPI driver's automatic chip-select. We use
+  manual CS on GPIO23 because the driver's CS timing doesn't meet the PN5180's
+  requirements.
+
 Then reboot:
 
 ```bash
 sudo reboot
 ```
 
+Verify after reboot:
+
+```bash
+ls /dev/i2c-0
+# Should exist
+
+sudo i2cdetect -y 0
+# Should show 0x2A (NAU7802)
+```
+
 #### 3. Install system packages
 
 ```bash
-sudo apt install python3-spidev python3-libgpiod gpiod libgpiod3
+sudo apt install python3-spidev python3-libgpiod gpiod libgpiod3 i2c-tools
 ```
 
 - `python3-spidev` / `libgpiod3` — system libraries for SPI and GPIO access
 - `gpiod` — command-line GPIO tools (useful for debugging)
+- `i2c-tools` — I2C diagnostic tools (`i2cdetect`, `i2cget`, etc.)
 
 #### 4. Install Python dependencies (in venv)
 
 ```bash
-pip install spidev gpiod
+pip install spidev gpiod smbus2
 ```
 
-- `spidev` — Python SPI bindings
+- `spidev` — Python SPI bindings (PN5180 NFC reader)
 - `gpiod` — Python GPIO bindings via libgpiod (works on both RPi 4 and RPi 5)
+- `smbus2` — Python I2C bindings (NAU7802 scale)
 
 #### 5. Solder all connections
 
@@ -144,3 +169,19 @@ Place a tag on the reader. Supported tag types:
 | SDA         | Pin 27           | GPIO 0 | Yellow     |
 | SCL         | Pin 28           | GPIO 1 | White      |
 | GND         | Pin 30           | —      | Black      |
+
+> **I2C Bus:** Uses I2C bus 0 (GPIO0/GPIO1), enabled via `dtparam=i2c_vc=on`
+> in config.txt. Bus 1 (GPIO2/GPIO3) is the default but those pins are not
+> used here.
+
+### Verify
+
+```bash
+sudo i2cdetect -y 0
+# Should show 0x2A
+
+sudo python3 spoolbuddy/scale_diag.py
+```
+
+The diagnostic reads 10 samples at 10 SPS and shows raw ADC values, average,
+and spread. Typical idle readings are around ~500k with a spread under 20k.

+ 170 - 0
spoolbuddy/scale_diag.py

@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+"""NAU7802 Scale Diagnostic — ported from SpoolBuddy Rust firmware.
+
+I2C address: 0x2A
+Bus: /dev/i2c-0 (GPIO0/GPIO1 on RPi)
+"""
+
+import struct
+import sys
+import time
+
+import smbus2
+
+I2C_BUS = 0
+NAU7802_ADDR = 0x2A
+
+# Register addresses
+REG_PU_CTRL = 0x00
+REG_CTRL1 = 0x01
+REG_CTRL2 = 0x02
+REG_ADCO_B2 = 0x12  # ADC output MSB
+REG_ADCO_B1 = 0x13
+REG_ADCO_B0 = 0x14  # ADC output LSB
+REG_ADC = 0x15
+REG_PGA = 0x1B
+REG_PWR_CTRL = 0x1C
+REG_REVISION = 0x1F
+
+# PU_CTRL bits
+PU_RR = 0x01  # Register reset
+PU_PUD = 0x02  # Power up digital
+PU_PUA = 0x04  # Power up analog
+PU_PUR = 0x08  # Power up ready (read-only)
+PU_CS = 0x10  # Cycle start
+PU_CR = 0x20  # Cycle ready (read-only)
+PU_OSCS = 0x40  # Oscillator select
+PU_AVDDS = 0x80  # AVDD source select
+
+
+class NAU7802:
+    def __init__(self, bus=I2C_BUS, addr=NAU7802_ADDR):
+        self._bus = smbus2.SMBus(bus)
+        self._addr = addr
+
+    def close(self):
+        self._bus.close()
+
+    def read_reg(self, reg: int) -> int:
+        return self._bus.read_byte_data(self._addr, reg)
+
+    def write_reg(self, reg: int, val: int):
+        self._bus.write_byte_data(self._addr, reg, val & 0xFF)
+
+    def init(self):
+        """Initialize NAU7802 — matches Rust firmware init sequence."""
+        revision = self.read_reg(REG_REVISION)
+        print(f"  Revision: 0x{revision:02X}")
+
+        # Reset
+        self.write_reg(REG_PU_CTRL, PU_RR)
+        time.sleep(0.010)
+        self.write_reg(REG_PU_CTRL, 0x00)
+
+        # Power up digital + analog
+        self.write_reg(REG_PU_CTRL, PU_PUD | PU_PUA)
+
+        # Wait for power-up ready
+        for _ in range(100):
+            status = self.read_reg(REG_PU_CTRL)
+            if status & PU_PUR:
+                print("  Power-up ready")
+                break
+            time.sleep(0.001)
+        else:
+            raise TimeoutError("NAU7802 power-up timeout")
+
+        # Sample rate: 10 SPS (bits 6:4 of CTRL2 = 0b000)
+        ctrl2 = self.read_reg(REG_CTRL2)
+        self.write_reg(REG_CTRL2, (ctrl2 & 0x8F) | (0 << 4))
+        print("  Sample rate: 10 SPS")
+
+        # Gain: 128x (bits 2:0 of CTRL1 = 0b111)
+        ctrl1 = self.read_reg(REG_CTRL1)
+        self.write_reg(REG_CTRL1, (ctrl1 & 0xF8) | 7)
+        print("  Gain: 128x")
+
+        # LDO: 3.3V (bits 5:3 of CTRL1 = 0b100)
+        ctrl1 = self.read_reg(REG_CTRL1)
+        self.write_reg(REG_CTRL1, (ctrl1 & 0xC7) | (0b100 << 3))
+
+        # Enable internal LDO (bit 7 of CTRL1)
+        ctrl1 = self.read_reg(REG_CTRL1)
+        self.write_reg(REG_CTRL1, ctrl1 | 0x80)
+        print("  LDO: 3.3V (internal)")
+
+        # Start conversion cycle
+        pu_ctrl = self.read_reg(REG_PU_CTRL)
+        self.write_reg(REG_PU_CTRL, pu_ctrl | PU_CS)
+        print("  Conversion started")
+
+    def data_ready(self) -> bool:
+        return bool(self.read_reg(REG_PU_CTRL) & PU_CR)
+
+    def read_raw(self) -> int:
+        """Read 24-bit signed ADC value."""
+        b2 = self.read_reg(REG_ADCO_B2)
+        b1 = self.read_reg(REG_ADCO_B1)
+        b0 = self.read_reg(REG_ADCO_B0)
+        raw = (b2 << 16) | (b1 << 8) | b0
+        # Sign extend 24-bit to 32-bit
+        if raw & 0x800000:
+            raw |= 0xFF000000
+            raw = struct.unpack("i", struct.pack("I", raw))[0]
+        return raw
+
+
+def main():
+    print("=" * 60)
+    print("NAU7802 Scale Diagnostic")
+    print("=" * 60)
+
+    scale = NAU7802()
+    try:
+        print("[1] Initializing...")
+        scale.init()
+
+        print("[2] Waiting for first reading...")
+        for _ in range(200):
+            if scale.data_ready():
+                break
+            time.sleep(0.010)
+        else:
+            print("    Timeout waiting for data ready")
+            sys.exit(1)
+
+        print("[3] Reading 10 samples (10 SPS = ~1 second)...")
+        readings = []
+        for i in range(10):
+            # Wait for data ready
+            for _ in range(200):
+                if scale.data_ready():
+                    break
+                time.sleep(0.010)
+            raw = scale.read_raw()
+            readings.append(raw)
+            print(f"    Sample {i + 1:2d}: {raw:>10d}")
+
+        avg = sum(readings) / len(readings)
+        spread = max(readings) - min(readings)
+        print(f"\n    Average: {avg:>10.0f}")
+        print(f"    Min:     {min(readings):>10d}")
+        print(f"    Max:     {max(readings):>10d}")
+        print(f"    Spread:  {spread:>10d}")
+
+        print("\n" + "=" * 60)
+        print("Diagnostic complete!")
+        print("=" * 60)
+
+    except Exception as e:
+        print(f"\nERROR: {e}")
+        import traceback
+
+        traceback.print_exc()
+        sys.exit(1)
+    finally:
+        scale.close()
+
+
+if __name__ == "__main__":
+    main()