"""
==========================================================
4x4 Keypad Calculator — Raspberry Pi Pico (MicroPython)
LCD SDA -> GP4 SCL -> GP2 (I2C addr 0x27)
Keypad rows -> GP10-GP13 | cols -> GP14-GP17
Layout: 1 2 3 /
4 5 6 *
7 8 9 -
. 0 = +
==========================================================
"""
from machine import Pin, SoftI2C
import time
# ── helpers ────────────────────────────────────────────────────────────────
def pad(text, width=16):
"""MicroPython-safe left-justify: pad with spaces to `width`."""
s = str(text)[:width]
while len(s) < width:
s += " "
return s
# ── I2C LCD driver (PCF8574 backpack) ──────────────────────────────────────
class I2cLcd:
LCD_CHR = 1
LCD_CMD = 0
LCD_LINE_1 = 0x80
LCD_LINE_2 = 0xC0
LCD_BL = 0x08
ENABLE = 0b00000100
def __init__(self, i2c, addr=0x27):
self.i2c = i2c
self.addr = addr
self.buf = bytearray(1)
time.sleep_ms(50)
self._write4(0x03); time.sleep_ms(5)
self._write4(0x03); time.sleep_ms(1)
self._write4(0x03)
self._write4(0x02)
self.send(0x28, self.LCD_CMD) # 4-bit, 2 lines
self.send(0x0C, self.LCD_CMD) # display on, cursor off
self.send(0x06, self.LCD_CMD) # entry: increment
self.clear()
def _strobe(self, data):
self.buf[0] = data | self.ENABLE | self.LCD_BL
self.i2c.writeto(self.addr, self.buf)
time.sleep_us(500)
self.buf[0] = (data & ~self.ENABLE) | self.LCD_BL
self.i2c.writeto(self.addr, self.buf)
time.sleep_us(100)
def _write4(self, data):
b = (data << 4) | self.LCD_BL
self.buf[0] = b
self.i2c.writeto(self.addr, self.buf)
self._strobe(b)
def send(self, data, mode):
hi = mode | (data & 0xF0) | self.LCD_BL
lo = mode | ((data << 4) & 0xF0) | self.LCD_BL
self.i2c.writeto(self.addr, bytes([hi])); self._strobe(hi)
self.i2c.writeto(self.addr, bytes([lo])); self._strobe(lo)
def clear(self):
self.send(0x01, self.LCD_CMD)
time.sleep_ms(3)
def move(self, row, col):
self.send([self.LCD_LINE_1, self.LCD_LINE_2][row] + col, self.LCD_CMD)
def write(self, text):
for ch in str(text):
self.send(ord(ch), self.LCD_CHR)
def print_line(self, row, text):
"""Write exactly 16 chars on a row (no ljust needed)."""
self.move(row, 0)
self.write(pad(text, 16))
# ── Keypad driver ──────────────────────────────────────────────────────────
class Keypad:
def __init__(self, row_pins, col_pins, layout):
self.rows = [Pin(p, Pin.OUT) for p in row_pins]
self.cols = [Pin(p, Pin.IN, Pin.PULL_DOWN) for p in col_pins]
self.layout = layout
def scan(self):
for r, rp in enumerate(self.rows):
rp.high()
for c, cp in enumerate(self.cols):
if cp.value():
rp.low()
return self.layout[r][c]
rp.low()
return None
# ── Safe eval ──────────────────────────────────────────────────────────────
def safe_eval(expr):
allowed = set("0123456789.+-*/()")
if not all(ch in allowed for ch in expr):
return None, "Bad input"
try:
result = eval(expr)
if isinstance(result, float):
if result == int(result) and abs(result) < 1e12:
result = int(result)
else:
result = float('{:.6g}'.format(result))
return result, None
except ZeroDivisionError:
return None, "Div by 0!"
except Exception:
return None, "Syntax Err"
# ── Hardware init ──────────────────────────────────────────────────────────
i2c = SoftI2C(sda=Pin(4), scl=Pin(2), freq=100_000) # SDA=GP4, SCL=GP2
lcd = I2cLcd(i2c, addr=0x27)
LAYOUT = [
['1', '2', '3', '/'],
['4', '5', '6', '*'],
['7', '8', '9', '-'],
['.', '0', '=', '+'],
]
keypad = Keypad(
row_pins=[10, 11, 12, 13],
col_pins=[14, 15, 16, 17],
layout=LAYOUT
)
# ── State ──────────────────────────────────────────────────────────────────
expression = ""
last_key = None
just_evaled = False
MAX_EXPR = 32
# ── Boot splash ────────────────────────────────────────────────────────────
lcd.print_line(0, " Pi Calculator ")
lcd.print_line(1, " Ready... ")
time.sleep(1)
lcd.print_line(0, "")
lcd.print_line(1, "")
# ── Display helpers ────────────────────────────────────────────────────────
def refresh():
lcd.print_line(0, expression[-16:] if expression else "")
lcd.print_line(1, "")
def show_result(expr, result):
lcd.print_line(0, expr[-16:])
lcd.print_line(1, "= " + str(result))
def show_error(msg):
lcd.print_line(0, " ERROR! ")
lcd.print_line(1, msg)
# ── Main loop ──────────────────────────────────────────────────────────────
while True:
key = keypad.scan()
if key and key != last_key:
last_key = key
if key == '=':
if expression:
result, err = safe_eval(expression)
if err:
show_error(err)
expression = ""
just_evaled = False
else:
show_result(expression, result)
expression = str(result)
just_evaled = True
else:
if just_evaled:
expression = expression + key if key in '+-*/' else key
just_evaled = False
else:
if key in '+-*/' and expression and expression[-1] in '+-*/':
expression = expression[:-1] + key
else:
expression += key
if len(expression) > MAX_EXPR:
expression = expression[:MAX_EXPR]
refresh()
elif not key:
last_key = None
time.sleep_ms(50)