# ========= Multicore + OLED SSD1306 + Menú ADC + Botón (RP2040) =========
# Core 1: genera salida binaria por GP2,3,6,7,8,9,10,11,12,13 (10 bits) según opción seleccionada
# Core 0: UI SSD1306 (I2C0 en GP4=SDA, GP5=SCL), menú por ADC (GP26), botón en GP14
import _thread
import time
from machine import Pin, I2C, ADC
from ssd1306 import SSD1306_I2C
import framebuf
# ===================== Configuración de hardware =====================
# I2C del OLED: usar I2C0 en GP4= SDA, GP5= SCL (coincide con tu versión)
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=200000)
oled = SSD1306_I2C(128, 64, i2c)
# Salidas binarias (10 bits). Mantengo tu mapeo exacto:
gpio_pins = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
BIN_PINS = [Pin(p, Pin.OUT) for p in gpio_pins]
# ADC para menú (perilla analógica):
ADC_MENU_PIN = 26 # GP26 -> ADC0
adc_menu = ADC(ADC_MENU_PIN)
# Botón con pull-up (único botón para seleccionar y volver)
BTN_PIN = 14
btn = Pin(BTN_PIN, Pin.IN, Pin.PULL_UP)
# ADCs para instrumentos (ajústalos según tu cableado)
ADC_VOLT_PIN = 27 # GP27 -> ADC1
ADC_OHM_PIN = 28 # GP28 -> ADC2 (divisor con Rref)
adc_volt = ADC(ADC_VOLT_PIN)
adc_ohm = ADC(ADC_OHM_PIN)
# Parámetros de conversión ADC
VREF = 3.3 # referencia efectiva del ADC (ajusta si usas ref externa)
ADC_MAX = 65535 # read_u16()
# Instrumentos (ajusta a tu hardware real)
RREF = 10000.0 # ohmios, resistor de referencia para el ohmímetro (divisor)
RSHUNT = 1.0 # ohmios, resistor shunt del amperímetro
VSENSE_GAIN = 1.0 # ganancia del acondicionamiento para amperímetro (1 si directo)
# Parámetros del contador/salidas
COUNT_MAX = 1023 # 10 bits -> 0..1023
PERIOD_MS = 120 # periodo base para secuencias
STEP_DEFAULT = 1
# ===================== Estado compartido =====================
lock = _thread.allocate_lock()
counter = 0
# Estados de UI
MODE_MENU = 0
MODE_APP = 1
state = {
"ui_mode": MODE_MENU, # menú o aplicación
"option": 1, # 1..10
"step": STEP_DEFAULT, # incremento para series
"running": True, # para salir gracioso si quieres
}
# ===================== Utilitarios =====================
def to_binary_string(value, bits=10):
s = ""
for i in range(bits - 1, -1, -1):
s += "1" if (value >> i) & 1 else "0"
return s
def write_binary_to_pins(value):
# LSB en BIN_PINS[0]
for i, pin in enumerate(BIN_PINS):
pin.value((value >> i) & 1)
def map_adc_to_option(raw):
# raw: 0..65535 -> 1..10 (10 segmentos iguales)
idx = int((raw * 10) // (ADC_MAX + 1)) + 1
if idx < 1: idx = 1
if idx > 10: idx = 10
return idx
def draw_centered_text(oled, text, y, scale=1):
x = (oled.width - len(text) * 8 * scale) // 2
if x < 0: x = 0
for idx, ch in enumerate(text):
if scale == 1:
oled.text(ch, x + idx*8, y)
else:
bx = x + idx*8*scale
for dy in range(scale):
for dx in range(scale):
oled.text(ch, bx + dx, y + dy)
def draw_menu(curr_opt):
oled.fill(0)
oled.text("== MENU ==", 24, 0)
labels = [
"1 Serie 1",
"2 Serie 2",
"3 Serie 3",
"4 Serie 4",
"5 Serie 5",
"6 Voltimetro",
"7 Ohmetro",
"8 Amperimetro",
"9 About",
"10 Reset"
]
# Mostrar 6 líneas: 3 arriba y 3 abajo del seleccionado (ventana)
start = max(0, curr_opt - 4) # 0-based
end = min(10, start + 6)
if end - start < 6:
start = max(0, end - 6)
y = 12
for i in range(start, end):
prefix = ">" if (i + 1) == curr_opt else " "
line = prefix + labels[i]
oled.text(line, 0, y)
y += 9
oled.text("BTN: corto=OK", 0, 56)
oled.show()
def draw_series_screen(opt, value):
oled.fill(0)
oled.text("SERIE %d" % opt, 0, 0)
draw_centered_text(oled, str(value), 16, scale=2)
b = to_binary_string(value, 10)
oled.text("BIN:", 0, 50)
oled.text(b, 28, 50)
oled.show()
def draw_voltmeter(v):
oled.fill(0)
oled.text("VOLTIMETRO", 0, 0)
draw_centered_text(oled, "{:.3f}V".format(v), 18, scale=2)
oled.text("ADC: GP27", 0, 50)
oled.show()
def draw_ohmmeter(r):
oled.fill(0)
oled.text("OHMETRO", 0, 0)
if r < 0:
draw_centered_text(oled, "Fuera rango", 18, scale=1)
else:
draw_centered_text(oled, "{:.1f} Ohm".format(r), 18, scale=2)
oled.text("ADC: GP28 Rref={}R".format(int(RREF)), 0, 50)
oled.show()
def draw_ammeter(i):
oled.fill(0)
oled.text("AMPERIMETRO", 0, 0)
draw_centered_text(oled, "{:.3f}A".format(i), 18, scale=2)
oled.text("ADC: GP27 Rsh={:.2f}R".format(RSHUNT), 0, 50)
oled.show()
def draw_about():
oled.fill(0)
oled.text("About", 0, 0)
oled.text("Multicore RP2040", 0, 12)
oled.text("GPIO bin + OLED", 0, 22)
oled.text("Menu ADC + BTN", 0, 32)
oled.text("By: Tu proyecto", 0, 42)
oled.text("(Pulsa largo = atras)", 0, 54)
oled.show()
# ===================== Lecturas de instrumentos =====================
def adc_to_voltage(adc):
raw = adc.read_u16()
return (raw / ADC_MAX) * VREF
def read_voltmeter():
# Lectura directa de voltaje en ADC_VOLT_PIN (con acondicionamiento si hay)
return adc_to_voltage(adc_volt)
def read_ohmmeter():
# Divisor: Vin -> Runknown en serie con RREF -> GND, ADC mide en nodo intermedio entre Runknown y RREF (o como lo conectes).
# Modelo clásico: Vadc = Vin * (RREF / (Runknown + RREF))
# => Runknown = RREF * (Vin/Vadc - 1)
Vadc = adc_to_voltage(adc_ohm)
Vin = VREF # si alimentas el divisor desde 3.3V
if Vadc <= 0.01 or Vadc >= (Vin - 0.01): # evitar divisiones absurdas
return -1.0
r = RREF * (Vin / Vadc - 1.0)
return r
def read_ammeter():
# Shunt: Vshunt = I * RSHUNT, pero si hay amplificador: Vadc = VSENSE_GAIN * Vshunt
Vadc = adc_to_voltage(adc_volt)
Vsh = Vadc / VSENSE_GAIN
i = Vsh / RSHUNT
return i
# ===================== Secuencias (Series 1..5) =====================
def sequence_value(opt, k):
# k = contador (0..1023)
# Devuelve un entero 10-bit para escribir en pines
if opt == 1:
# Conteo binario ascendente
return k & COUNT_MAX
elif opt == 2:
# Conteo descendente
return ((COUNT_MAX - k) & COUNT_MAX)
elif opt == 3:
# Barrido 1-hot circular sobre 10 bits
pos = (k // 3) % 10
return (1 << pos)
elif opt == 4:
# Patrón espejo (cabeza-cola): 0000000001 -> ... -> 1000000000 -> ... -> 0000000001
span = (k // 5) % 18 # 10 + 8 de regreso
pos = span if span < 10 else (18 - span)
return (1 << pos)
elif opt == 5:
# Patrón de “ondas” (mezcla de Gray + shift)
gray = k ^ (k >> 1)
return gray & COUNT_MAX
else:
return 0
# ===================== Core 1 (salida binaria) =====================
def core1_task():
global counter
while True:
with lock:
opt = state["option"]
ui = state["ui_mode"]
stp = state["step"]
# Solo generar salidas para series (1..5). Instrumentos -> salidas en 0.
if ui == MODE_APP and 1 <= opt <= 5:
counter = (counter + stp) & COUNT_MAX
out = sequence_value(opt, counter)
write_binary_to_pins(out)
time.sleep_ms(PERIOD_MS)
else:
# Apaga salidas si no estamos en series
write_binary_to_pins(0)
time.sleep_ms(50)
# ===================== Botón (corto vs. largo) =====================
def read_button_event():
"""
Devuelve:
0 = sin evento
1 = pulsación corta (seleccionar)
2 = pulsación larga (volver)
"""
LONG_MS = 600
DEBOUNCE_MS = 25
if btn.value() == 0:
# presionado
t0 = time.ticks_ms()
while btn.value() == 0:
time.sleep_ms(5)
if time.ticks_diff(time.ticks_ms(), t0) > LONG_MS:
# esperar a soltar
while btn.value() == 0:
time.sleep_ms(5)
time.sleep_ms(DEBOUNCE_MS)
return 2 # largo
time.sleep_ms(DEBOUNCE_MS)
return 1 # corto
return 0
# ===================== Core 0 (UI y lógica de menú/app) =====================
def core0_task():
last_opt = -1
while True:
with lock:
ui = state["ui_mode"]
opt = state["option"]
if ui == MODE_MENU:
# Actualizar opción por ADC
raw = adc_menu.read_u16()
curr_opt = map_adc_to_option(raw)
if curr_opt != last_opt:
draw_menu(curr_opt)
last_opt = curr_opt
# Botón: corto = entrar; largo = (no-op)
ev = read_button_event()
if ev == 1: # corto
with lock:
state["option"] = curr_opt
state["ui_mode"] = MODE_APP
# UI inmediata de la app
if 1 <= curr_opt <= 5:
draw_series_screen(curr_opt, 0)
elif curr_opt == 6:
draw_voltmeter(0.0)
elif curr_opt == 7:
draw_ohmmeter(-1.0)
elif curr_opt == 8:
draw_ammeter(0.0)
elif curr_opt == 9:
draw_about()
elif curr_opt == 10:
# Reset suave
with lock:
state["option"] = 1
state["ui_mode"] = MODE_MENU
# limpiar salidas
write_binary_to_pins(0)
draw_menu(1)
else: # MODE_APP
with lock:
opt = state["option"]
if 1 <= opt <= 5:
# Mostrar valor actual de la serie
with lock:
val = counter
draw_series_screen(opt, val)
time.sleep_ms(80)
elif opt == 6:
v = read_voltmeter()
draw_voltmeter(v)
time.sleep_ms(120)
elif opt == 7:
r = read_ohmmeter()
draw_ohmmeter(r)
time.sleep_ms(120)
elif opt == 8:
i = read_ammeter()
draw_ammeter(i)
time.sleep_ms(120)
elif opt == 9:
draw_about()
time.sleep_ms(150)
elif opt == 10:
# Por si entró aquí, vuelve al menú
with lock:
state["ui_mode"] = MODE_MENU
draw_menu( map_adc_to_option(adc_menu.read_u16()) )
# Botón: corto = (no-op en instrumentos/series); largo = volver al menú
ev = read_button_event()
if ev == 2: # largo -> volver
with lock:
state["ui_mode"] = MODE_MENU
draw_menu( map_adc_to_option(adc_menu.read_u16()) )
# (Si quisieras que el corto haga otra acción dentro de la app, aquí va)
# ===================== Inicio =====================
def main():
# Mensaje inicial
oled.fill(0)
oled.text("Iniciando...", 0, 0)
oled.text("Core1: GPIO bin", 0, 16)
oled.text("Core0: OLED+Menu", 0, 26)
oled.text("ADC Menu: GP26", 0, 36)
oled.text("BTN: GP14", 0, 46)
oled.show()
time.sleep_ms(800)
# Lanzar Core 1
_thread.start_new_thread(core1_task, ())
# Core 0: UI
try:
core0_task()
except KeyboardInterrupt:
for p in BIN_PINS:
p.value(0)
oled.fill(0)
oled.text("Detenido", 0, 0)
oled.show()
main()