# Smart Parking System — separate TRIGs & ECHOs (pins match your Wokwi JSON)
# Displays only "Free" / "Full" on I2C 16x2 LCD. Threshold = 100 cm for all sensors.
from machine import Pin, I2C, time_pulse_us
import utime
# -------- LCD driver (self-contained) --------
class I2cLcd:
def __init__(self, i2c, addr, rows=2, cols=16):
self.i2c = i2c
self.addr = addr
self.rows = rows
self.cols = cols
self._init_lcd()
def _write_byte(self, b):
self.i2c.writeto(self.addr, bytes([b]))
def _pulse(self, data):
# EN high then low, keep backlight bit on (0x08 or 0x04 combos vary)
self._write_byte(data | 0x0C)
utime.sleep_us(150)
self._write_byte(data & ~0x04)
utime.sleep_us(150)
def _send(self, value, mode=0):
hi = value & 0xF0
lo = (value << 4) & 0xF0
self._pulse(hi | mode)
self._pulse(lo | mode)
def _init_lcd(self):
utime.sleep_ms(50)
self._pulse(0x30)
utime.sleep_ms(5)
self._pulse(0x30)
utime.sleep_us(200)
self._pulse(0x30)
self._pulse(0x20) # 4-bit mode
self._send(0x28) # function set
self._send(0x08) # display off
self._send(0x01) # clear
utime.sleep_ms(2)
self._send(0x06) # entry mode
self._send(0x0C) # display on (no cursor)
def clear(self):
self._send(0x01)
utime.sleep_ms(2)
def move_to(self, col, row):
offsets = (0x00, 0x40, 0x14, 0x54)
if row >= self.rows:
row = self.rows - 1
self._send(0x80 | (col + offsets[row]))
def putstr(self, text):
for ch in text:
self._send(ord(ch), 0x01)
# -------- Hardware (match your Wokwi JSON) --------
# I2C LCD pins: SDA -> GP0, SCL -> GP1
i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
lcd = I2cLcd(i2c, 0x27, 2, 16) # if your I2C address is 0x3F, change 0x27 -> 0x3F
# Separate TRIG pins (one per sensor)
TRIG_PINS = [13, 17, 15] # ultrasonic1.TRIG -> GP13, ultrasonic2.TRIG -> GP17, ultrasonic3.TRIG -> GP15
ECHO_PINS = [12, 16, 14] # ultrasonic1.ECHO -> GP12, ultrasonic2.ECHO -> GP16, ultrasonic3.ECHO -> GP14
TRIGS = [Pin(p, Pin.OUT) for p in TRIG_PINS]
ECHOS = [Pin(p, Pin.IN) for p in ECHO_PINS]
# Threshold for "Full" (cm) for all sensors
THRESH_CM = 100.0
# small pause between sensor reads to reduce cross-talk
SENSOR_DELAY_MS = 80
# -------- Measurement helper (separate TRIG) --------
def measure_single(trig_pin, echo_pin, timeout_us=35000):
# ensure trig low then send a 10us pulse on that trig pin
trig_pin.low()
utime.sleep_us(2)
trig_pin.high()
utime.sleep_us(10)
trig_pin.low()
# measure echo pulse width (returns <=0 on timeout or error)
try:
dur = time_pulse_us(echo_pin, 1, timeout_us)
except Exception:
return None
if dur <= 0:
return None
return (dur * 0.0343) / 2 # cm
# -------- Display helper (only "Free"/"Full") --------
def update_lcd(statuses):
# statuses: list of 3 entries ("Free" or "Full")
line0 = f"1:{statuses[0]} 2:{statuses[1]}"
if len(line0) > 16:
line0 = line0[:16]
line1 = f"3:{statuses[2]}"
if len(line1) > 16:
line1 = line1[:16]
lcd.clear()
lcd.move_to(0,0)
lcd.putstr(line0)
lcd.move_to(0,1)
lcd.putstr(line1)
# -------- Main loop --------
try:
while True:
statuses = []
# measure each sensor using its own TRIG/ECHO
for trig_pin, echo_pin in zip(TRIGS, ECHOS):
d = measure_single(trig_pin, echo_pin)
if d is None:
state = "Free" # safe default if no echo
else:
state = "Full" if d < THRESH_CM else "Free"
statuses.append(state)
utime.sleep_ms(SENSOR_DELAY_MS)
# update LCD (only Free/Full)
update_lcd(statuses)
utime.sleep(1.0)
except KeyboardInterrupt:
lcd.clear()
lcd.putstr("Stopped")