mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
Set up scale readings
This commit is contained in:
+49
-8
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user