This commit is contained in:
2026-06-16 13:02:06 +02:00
commit 9e45f1f199
29 changed files with 2531 additions and 0 deletions

116
lib/hx710b.py Normal file
View File

@@ -0,0 +1,116 @@
from machine import Pin
import time
import json
import os
CALIB_FILE = "calibration.cfg"
# ---------------------------------------------------------
# Calibration File Helpers
# ---------------------------------------------------------
def save_calibration(zero_offset, scale_factor):
"""Save calibration values to internal flash."""
data = {
"zero_offset": zero_offset,
"scale_factor": scale_factor
}
with open(CALIB_FILE, "w") as f:
json.dump(data, f)
print("✔ Calibration saved.")
def load_calibration():
"""Load existing calibration or return defaults."""
if CALIB_FILE not in os.listdir():
print("⚠ No calibration file found, using defaults.")
return 0, 1
try:
with open(CALIB_FILE, "r") as f:
data = json.load(f)
print("✔ Calibration loaded.")
return data.get("zero_offset", 0), data.get("scale_factor", 1)
except Exception as e:
print("⚠ Calibration load error:", e)
return 0, 1
# ---------------------------------------------------------
# HX710B Class
# ---------------------------------------------------------
class HX710B:
def __init__(self, dout_pin, sck_pin):
self.dout = Pin(dout_pin, Pin.IN)
self.sck = Pin(sck_pin, Pin.OUT)
self.sck.value(0)
# Load previous calibration (or defaults)
self.zero_offset, self.scale_factor = load_calibration()
# -----------------------------------------------------
# Low-level raw ADC read
# -----------------------------------------------------
def read_raw(self):
"""Read the 24-bit raw ADC value."""
while self.dout.value():
pass # wait for data ready
count = 0
for _ in range(24):
self.sck.value(1)
count = (count << 1) | self.dout.value()
self.sck.value(0)
# 25th pulse to set gain for next reading
self.sck.value(1)
self.sck.value(0)
# Signed 24-bit conversion
if count & 0x800000:
count -= 0x1000000
return count
# -----------------------------------------------------
# Zero Calibration
# -----------------------------------------------------
def calibrate_zero(self, samples=20):
"""Measure 0 kPa and determine zero offset."""
total = 0
for _ in range(samples):
total += self.read_raw()
time.sleep_ms(5)
self.zero_offset = total / samples
save_calibration(self.zero_offset, self.scale_factor)
print("✔ Zero calibrated:", self.zero_offset)
return self.zero_offset
# -----------------------------------------------------
# Full-scale / Known Pressure Calibration
# -----------------------------------------------------
def calibrate_full(self, known_kpa, samples=20):
"""Measure raw ADC at known pressure and compute scaling."""
total = 0
for _ in range(samples):
total += self.read_raw()
time.sleep_ms(5)
raw_avg = total / samples
self.scale_factor = known_kpa / (raw_avg - self.zero_offset)
save_calibration(self.zero_offset, self.scale_factor)
print("✔ Scale calibrated:", self.scale_factor)
return self.scale_factor
# -----------------------------------------------------
# Convert raw → kPa using saved calibration
# -----------------------------------------------------
def read_kpa(self):
raw = self.read_raw()
return (raw - self.zero_offset) * self.scale_factor

89
lib/ssd1306.py Normal file
View File

@@ -0,0 +1,89 @@
# ssd1306.py
# MicroPython SSD1306 OLED driver (I2C)
# Compatible with 128x64 and 128x32 displays
from machine import I2C, Pin
import framebuf
import time
# SSD1306 commands
SET_CONTRAST = 0x81
DISPLAY_ALL_ON_RESUME = 0xA4
DISPLAY_ALL_ON = 0xA5
NORMAL_DISPLAY = 0xA6
INVERT_DISPLAY = 0xA7
DISPLAY_OFF = 0xAE
DISPLAY_ON = 0xAF
SET_DISPLAY_OFFSET = 0xD3
SET_COM_PINS = 0xDA
SET_VCOM_DETECT = 0xDB
SET_DISPLAY_CLOCK_DIV = 0xD5
SET_PRECHARGE = 0xD9
SET_MULTIPLEX = 0xA8
SET_LOW_COLUMN = 0x00
SET_HIGH_COLUMN = 0x10
SET_START_LINE = 0x40
MEMORY_MODE = 0x20
COLUMN_ADDR = 0x21
PAGE_ADDR = 0x22
COM_SCAN_INC = 0xC0
COM_SCAN_DEC = 0xC8
SEG_REMAP = 0xA0
CHARGE_PUMP = 0x8D
EXTERNAL_VCC = 0x1
SWITCH_CAP_VCC = 0x2
class SSD1306_I2C(framebuf.FrameBuffer):
def __init__(self, width, height, i2c, addr=0x3c):
self.width = width
self.height = height
self.i2c = i2c
self.addr = addr
self.pages = self.height // 8
self.buffer = bytearray(self.pages * self.width)
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
self.init_display()
def write_cmd(self, cmd):
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
def init_display(self):
for cmd in (
DISPLAY_OFF,
SET_DISPLAY_CLOCK_DIV, 0x80,
SET_MULTIPLEX, self.height - 1,
SET_DISPLAY_OFFSET, 0x00,
SET_START_LINE | 0x00,
CHARGE_PUMP, 0x14,
MEMORY_MODE, 0x00,
SEG_REMAP | 0x1,
COM_SCAN_DEC,
SET_COM_PINS, 0x12 if self.height == 64 else 0x02,
SET_CONTRAST, 0xCF,
SET_PRECHARGE, 0xF1,
SET_VCOM_DETECT, 0x40,
DISPLAY_ALL_ON_RESUME,
NORMAL_DISPLAY,
DISPLAY_ON
):
self.write_cmd(cmd)
self.fill(0)
self.show()
def show(self):
for page in range(self.pages):
self.write_cmd(0xB0 + page)
self.write_cmd(SET_LOW_COLUMN)
self.write_cmd(SET_HIGH_COLUMN)
start = self.width * page
end = start + self.width
self.i2c.writeto(self.addr, b'\x40' + self.buffer[start:end])
def poweroff(self):
self.write_cmd(DISPLAY_OFF)
def poweron(self):
self.write_cmd(DISPLAY_ON)
def invert(self, invert):
self.write_cmd(INVERT_DISPLAY if invert else NORMAL_DISPLAY)