# main.py - Calculadora Gráfica con Modo de Trazado
from machine import Pin, I2C, ADC
from ssd1306 import SSD1306_I2C
import math
import time
from detectortarjeta import get_board_config
WIDTH = 128
HEIGHT = 64
board_config = get_board_config()
if not board_config:
print("Error: No se pudo obtener la configuración de la tarjeta. Deteniendo.")
while True: pass
try:
i2c = I2C(board_config['i2c_id'], scl=Pin(board_config['scl_pin']), sda=Pin(board_config['sda_pin']), freq=400000)
oled = SSD1306_I2C(WIDTH, HEIGHT, i2c)
print("OLED inicializada correctamente.")
except Exception as e:
print(f"Error inicializando OLED: {e}. Revisa las conexiones.")
while True: pass
# --- NUEVO: Pines del Joystick ---
adc_x = ADC(Pin(26)) # VRx conectado a GP26 (ADC0)
sw = Pin(15, Pin.IN, Pin.PULL_UP) # SW conectado a GP15
TOKEN_NUMBER = 'NUMBER'; TOKEN_VARIABLE = 'VARIABLE'; TOKEN_OPERATOR = 'OPERATOR'; TOKEN_FUNCTION = 'FUNCTION'; TOKEN_LPAREN = 'LPAREN'; TOKEN_RPAREN = 'RPAREN'; TOKEN_CONSTANT = 'CONSTANT'
OPERATORS = '+-*/^'; FUNCTIONS = ['sin', 'cos', 'tan', 'sqrt', 'log']; CONSTANTS = {'pi': math.pi, 'e': math.e}
def lexer(expression):
tokens = []; i = 0
while i < len(expression):
char = expression[i]
if char.isspace(): i += 1; continue
if char.isdigit() or (char == '.' and i + 1 < len(expression) and expression[i+1].isdigit()):
num_str = '';
while i < len(expression) and (expression[i].isdigit() or expression[i] == '.'): num_str += expression[i]; i += 1
tokens.append((TOKEN_NUMBER, float(num_str))); continue
if char in OPERATORS: tokens.append((TOKEN_OPERATOR, char)); i += 1; continue
if char == '(': tokens.append((TOKEN_LPAREN, char)); i += 1; continue
if char == ')': tokens.append((TOKEN_RPAREN, char)); i += 1; continue
if char.isalpha():
word = '';
while i < len(expression) and expression[i].isalpha(): word += expression[i]; i += 1
if word.lower() == 'x': tokens.append((TOKEN_VARIABLE, 'x'))
elif word.lower() in FUNCTIONS: tokens.append((TOKEN_FUNCTION, word.lower()))
elif word.lower() in CONSTANTS: tokens.append((TOKEN_CONSTANT, word.lower()))
else: raise ValueError(f"Lexer: '{word}'?")
continue
raise ValueError(f"Lexer: '{char}'?")
return tokens
PRECEDENCE = {'+': 1, '-': 1, '*': 2, '/': 2, '^': 3}; ASSOCIATIVITY = {'+': 'L', '-': 'L', '*': 'L', '/': 'L', '^': 'R'}
def shunting_yard(tokens):
output_queue = []; operator_stack = []
for token_type, value in tokens:
if token_type in [TOKEN_NUMBER, TOKEN_VARIABLE, TOKEN_CONSTANT]: output_queue.append((token_type, value))
elif token_type == TOKEN_FUNCTION: operator_stack.append((token_type, value))
elif token_type == TOKEN_OPERATOR:
while (operator_stack and operator_stack[-1][0] == TOKEN_OPERATOR and ((ASSOCIATIVITY[value] == 'L' and PRECEDENCE[value] <= PRECEDENCE[operator_stack[-1][1]]) or (ASSOCIATIVITY[value] == 'R' and PRECEDENCE[value] < PRECEDENCE[operator_stack[-1][1]]))): output_queue.append(operator_stack.pop())
operator_stack.append((token_type, value))
elif token_type == TOKEN_LPAREN: operator_stack.append((token_type, value))
elif token_type == TOKEN_RPAREN:
while operator_stack and operator_stack[-1][0] != TOKEN_LPAREN: output_queue.append(operator_stack.pop())
if not operator_stack or operator_stack[-1][0] != TOKEN_LPAREN: raise ValueError("Sintaxis: ()?")
operator_stack.pop();
if operator_stack and operator_stack[-1][0] == TOKEN_FUNCTION: output_queue.append(operator_stack.pop())
while operator_stack:
if operator_stack[-1][0] in [TOKEN_LPAREN, TOKEN_RPAREN]: raise ValueError("Sintaxis: ()?")
output_queue.append(operator_stack.pop())
return output_queue
def evaluate_rpn(rpn_queue, x_value=0):
stack = []
for token_type, value in rpn_queue:
if token_type == TOKEN_NUMBER: stack.append(value)
elif token_type == TOKEN_VARIABLE: stack.append(x_value)
elif token_type == TOKEN_CONSTANT: stack.append(CONSTANTS[value])
elif token_type == TOKEN_OPERATOR:
if len(stack) < 2: raise ValueError("Sintaxis: op?")
op2 = stack.pop(); op1 = stack.pop()
if value == '+': stack.append(op1 + op2)
elif value == '-': stack.append(op1 - op2)
elif value == '*': stack.append(op1 * op2)
elif value == '/': stack.append(op1 / op2)
elif value == '^': stack.append(op1 ** op2)
elif token_type == TOKEN_FUNCTION:
if len(stack) < 1: raise ValueError("Sintaxis: f(op)?")
op = stack.pop()
if value == 'sin': stack.append(math.sin(op))
elif value == 'cos': stack.append(math.cos(op))
elif value == 'tan': stack.append(math.tan(op))
elif value == 'sqrt': stack.append(math.sqrt(op))
elif value == 'log': stack.append(math.log(op))
if len(stack) != 1: raise ValueError("Sintaxis: Expr malformada")
return stack[0]
def draw_axes():
oled.vline(WIDTH // 2, 0, HEIGHT, 1)
oled.hline(0, HEIGHT // 2, WIDTH, 1)
def graph_function(rpn_queue, x_min, x_max, manual_y_range=None):
oled.fill(0)
if manual_y_range:
y_min_val, y_max_val = manual_y_range
else:
y_values = []
for px in range(WIDTH):
x_val = x_min + (x_max - x_min) * (px / (WIDTH - 1))
try:
y_val = evaluate_rpn(rpn_queue, x_val)
if not (math.isinf(y_val) or math.isnan(y_val)):
y_values.append(y_val)
except Exception:
continue
if not y_values:
oled.text("No se puede", 0, 10); oled.text("graficar.", 0, 20)
oled.show()
return None, None
y_min_val = min(y_values)
y_max_val = max(y_values)
if y_min_val == y_max_val:
y_min_val -= 1
y_max_val += 1
draw_axes()
for px in range(WIDTH):
x_val = x_min + (x_max - x_min) * (px / (WIDTH - 1))
try:
y_val = evaluate_rpn(rpn_queue, x_val)
py = int((HEIGHT - 1) - ((y_val - y_min_val) / (y_max_val - y_min_val) * (HEIGHT - 1)))
if 0 <= py < HEIGHT:
oled.pixel(px, py, 1)
except Exception:
continue
# No mostramos aquí, lo hará el bucle principal
print(f"Graficado en X: [{x_min}, {x_max}], Y: [{y_min_val:.2f}, {y_max_val:.2f}]")
return y_min_val, y_max_val
def draw_splash_screen():
# ... (código del splash screen se mantiene igual)
oled.fill(0); text = "MCZ"; text_width = len(text) * 8; x_pos = (WIDTH - text_width) // 2; y_pos = (HEIGHT - 8) // 2
oled.text(text, x_pos, y_pos); oled.show(); time.sleep(2)
oled.fill(0)
oled.line(40, 18, 88, 18, 1); oled.line(35, 45, 93, 45, 1); oled.line(35, 45, 40, 18, 1); oled.line(93, 45, 88, 18, 1)
oled.line(40, 18, 48, 5, 1); oled.line(48, 5, 56, 18, 1); oled.line(88, 18, 80, 5, 1); oled.line(80, 5, 72, 18, 1)
oled.line(50, 25, 60, 25, 1); oled.line(50, 32, 60, 32, 1); oled.line(50, 25, 50, 32, 1); oled.line(60, 25, 60, 32, 1)
oled.line(68, 25, 78, 25, 1); oled.line(68, 32, 78, 32, 1); oled.line(68, 25, 68, 32, 1); oled.line(78, 25, 78, 32, 1)
oled.line(61, 36, 67, 36, 1); oled.line(64, 36, 64, 40, 1); oled.line(60, 42, 64, 40, 1); oled.line(68, 42, 64, 40, 1)
oled.line(25, 30, 45, 35, 1); oled.line(25, 38, 45, 38, 1); oled.line(103, 30, 83, 35, 1); oled.line(103, 38, 83, 38, 1)
oled.show(); time.sleep(3.5)
def main():
draw_splash_screen()
while True:
try:
expression = input("\ny = ")
if not expression: continue
x_min_str = input(f"x_min (default -10): ")
x_max_str = input(f"x_max (default 10): ")
x_min = float(x_min_str) if x_min_str else -10.0
x_max = float(x_max_str) if x_max_str else 10.0
if x_min >= x_max:
print("Error: x_min debe ser menor que x_max."); continue
oled.fill(0); oled.text("Calculando...", 0, 20); oled.show()
tokens = lexer(expression)
rpn_queue = shunting_yard(tokens)
y_min_val, y_max_val = graph_function(rpn_queue, x_min, x_max)
if y_min_val is None: continue # No se pudo graficar
# --- NUEVO: BUCLE DEL MODO DE TRAZADO ---
print("Entrando en modo de trazado. Presiona el botón del joystick para salir.")
while True:
# Salir del modo de trazado si se presiona el botón
if sw.value() == 0:
time.sleep(0.2) # Pequeño debounce
print("Saliendo del modo de trazado.")
oled.fill(0)
oled.text("Nueva Funcion...", 0, 20)
oled.show()
time.sleep(1)
break
# Leer valor del joystick y mapearlo a la coordenada de píxel X
adc_val = adc_x.read_u16()
px = int(adc_val * (WIDTH - 1) / 65535)
# Calcular las coordenadas matemáticas en ese punto
x_val = x_min + (x_max - x_min) * (px / (WIDTH - 1))
y_val = evaluate_rpn(rpn_queue, x_val)
# Redibujar la escena para actualizar el cursor
# 1. Redibujar la gráfica base
graph_function(rpn_queue, x_min, x_max, manual_y_range=(y_min_val, y_max_val))
# 2. Calcular la posición del cursor en píxeles y dibujarlo
if not (math.isinf(y_val) or math.isnan(y_val)):
py = int((HEIGHT - 1) - ((y_val - y_min_val) / (y_max_val - y_min_val) * (HEIGHT - 1)))
if 0 <= py < HEIGHT:
# Dibuja un pequeño círculo/cruz como cursor
oled.pixel(px, py-1, 0); oled.pixel(px, py+1, 0)
oled.pixel(px-1, py, 0); oled.pixel(px+1, py, 0)
oled.pixel(px, py, 1)
# 3. Mostrar las coordenadas en la parte superior
coord_text = f"X:{x_val:.1f} Y:{y_val:.1f}"
oled.fill_rect(0, 0, WIDTH, 8, 0) # Limpiar la barra superior
oled.text(coord_text, 0, 0, 1)
# 4. Actualizar la pantalla
oled.show()
time.sleep(0.02) # Pequeña pausa para no sobrecargar el procesador
except ValueError as e:
print(f"Error: {e}")
oled.fill(0); oled.text("Error:", 0, 10); error_msg = str(e)
if len(error_msg) > 16: oled.text(error_msg[:16], 0, 22)
if len(error_msg) > 32: oled.text(error_msg[16:32], 0, 34)
else: oled.text(error_msg[16:], 0, 34)
oled.show()
except Exception as e:
print(f"Error inesperado: {e}")
oled.fill(0); oled.text("Error sistema", 0, 20); oled.show()
if __name__ == "__main__":
main()