from machine import Pin, I2C
from time import sleep
import utime
# === LCD Setup ===
try:
from lcd_api import LcdApi
from i2c_lcd import I2cLcd
except ImportError:
print("LCD libraries not found. Please install lcd_api and i2c_lcd.")
raise
i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
try:
lcd = I2cLcd(i2c, 0x27, 2, 16)
except Exception as e:
print("LCD initialization failed. Check I2C address and connections.")
raise e
# === Keypad Setup ===
ROWS = [Pin(i, Pin.OUT) for i in (2, 3, 4, 5)]
COLS = [Pin(i, Pin.IN, Pin.PULL_DOWN) for i in (6, 7, 8, 9)]
KEYS = [
['1', '2', '3', '+'],
['4', '5', '6', '-'],
['7', '8', '9', '*'],
['=', '0', '.', '/']
]
def scan_keypad():
for row_num, row in enumerate(ROWS):
row.high()
for col_num, col in enumerate(COLS):
if col.value():
row.low()
return KEYS[row_num][col_num]
row.low()
return None
# === Calculator State ===
first = ""
second = ""
operator = ""
result = None
mode = "input_first" # Modes: input_first, input_second, result_shown
def update_display():
lcd.clear()
line = first
if operator:
line += f" {operator}"
if second:
line += f" {second}"
# Truncate or scroll long expressions
if len(line) > 16:
line = line[-16:] # Show the last 16 characters
lcd.move_to(0, 0)
lcd.putstr(line)
if mode == "result_shown" and result is not None:
lcd.move_to(0, 1)
lcd.putstr(f"= {result}"[:16])
def reset_state():
global first, second, operator, result, mode
first = ""
second = ""
operator = ""
result = None
mode = "input_first"
lcd.clear()
lcd.putstr("Goose Calc Ready")
sleep(0.5)
#lcd.clear()
def safe_calculate(first, operator, second):
try:
a = float(first) if '.' in first else int(first)
b = float(second) if '.' in second else int(second)
if operator == '+':
return a + b
elif operator == '-':
return a - b
elif operator == '*':
return a * b
elif operator == '/':
if b == 0:
return "Div by 0"
return a / b
except ValueError:
return "Error"
def calculate():
global first, second, operator, result, mode
# print(f"Calculating: {first}{operator}{second}") # Debug calculation
if not first or not operator or not second:
result = "Error"
mode = "result_shown"
update_display()
return
# Validate numbers
try:
float(first) # Ensure first is a valid number
float(second) # Ensure second is a valid number
except ValueError:
result = "Error"
mode = "result_shown"
update_display()
return
# Perform calculation
value = safe_calculate(first, operator, second)
# Format result
if isinstance(value, (int, float)):
if isinstance(value, float) and value == int(value): # MicroPython fix
result = str(int(value))
else:
result = str(round(value, 8)) # 8 digits precision
else:
result = str(value) # Error message like "Div by 0"
mode = "result_shown"
update_display()
def handle_key(key, press_duration):
global first, second, operator, result, mode
# print(f"Key: {key}, Duration: {press_duration}") # Debug keypress
# Long press '=' for clear (1 second)
if key == '=' and press_duration > 1000:
reset_state()
return
if key.isdigit() or key == ".":
if mode == "result_shown":
first = ""
second = ""
operator = ""
result = None
mode = "input_first"
if mode == "input_first":
if key == "." and "." in first:
return
if key == "." and not first:
first = "0"
if len(first) < 10: # Limit input length
first += key
elif mode == "input_second":
if key == "." and "." in second:
return
if key == "." and not second:
second = "0"
if len(second) < 10: # Limit input length
second += key
update_display()
elif key in "+-*/":
if mode == "result_shown" and result != "Error" and result != "Div by 0":
first = result
second = ""
result = None
mode = "input_second"
elif first and mode != "input_second":
mode = "input_second"
else:
return # Ignore operator if no first number
operator = key
update_display()
elif key == "=":
if operator and second:
calculate()
# === Start Program ===
#lcd.clear()
#lcd.putstr("Calculator Ready")
#sleep(1)
reset_state()
last_key = None
last_key_time = 0
press_start = 0
debounce_ms = 200 # Debounce time in milliseconds
while True:
key = scan_keypad()
current_time = utime.ticks_ms()
if key and key != last_key and (current_time - last_key_time) > debounce_ms:
# Key pressed, start timing
press_start = current_time
last_key = key
last_key_time = current_time
elif not key and last_key:
# Key released, handle based on duration
press_duration = current_time - press_start
handle_key(last_key, press_duration)
last_key = None
last_key_time = current_time
sleep(0.05)