from machine import Pin, I2C, Timer, ADC
from ssd1306 import SSD1306_I2C
import utime
import math
ANCHO_OLED = 128
ALTO_OLED = 64
# OLED Wokwi
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)
oled = SSD1306_I2C(ANCHO_OLED, ALTO_OLED, i2c)
# DAC R-2R 10 bits: GP6 a GP15
pines = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
pines_gpio = [Pin(pin, Pin.OUT) for pin in pines]
# Botones
btn_next = Pin(16, Pin.IN, Pin.PULL_UP)
btn_reset = Pin(17, Pin.IN, Pin.PULL_UP)
try:
adc_vsys = ADC(29)
VSYS_OK = True
except Exception:
adc_vsys = None
VSYS_OK = False
modo = 0
modo_solicitado = 0
flag_cambio_modo = True
flag_actualizar_barra = True
horas = 0
minutos = 0
segundos = 0
ultimo_next = 0
ultimo_reset = 0
TIEMPO_DEBOUNCE_MS = 200
T = 60
w0 = 2 * math.pi / T
x_actual = 0.0
narmonicos = 20
AUTO_ARMONICOS = True
ARMONICOS_MIN = 1
ARMONICOS_MAX = 30
TIEMPO_CAMBIO_ARMONICO_MS = 1000
ultimo_cambio_armonico = 0
PERIODO_DAC_MS = 10
ultimo_dac = 0
contador = 0
def GPIO_SALP(valor, listagpio):
valor = max(0, min(1023, valor))
ii = 1
saltxt = ""
for i in range(10):
if valor & ii:
pines_gpio[i].value(1)
saltxt += "1"
else:
pines_gpio[i].value(0)
saltxt += "0"
ii = ii << 1
return saltxt
def parametros_modo(modo_actual):
if modo_actual == 3:
return 0.0, 0.5, 0.005
return 0.0, 59.0, 0.5
def calcular_an_modo0(n):
numerador = (
n * math.sin(n * math.pi / 2)
+ 4 * math.cos(n * math.pi / 2)
- 2 * math.pow(-1, n)
- 2
)
denominador = math.pi * n * n
return -(numerador / denominador)
def calcular_bn_modo0(n):
signo = math.pow(-1, n)
numerador = (
4 * math.sin(math.pi * n / 2)
+ n * math.cos(math.pi * n / 2)
+ math.pi * n * signo
- n * signo
- math.pi * n
)
denominador = math.pi * n * n
return -(numerador / denominador)
def SerieFourier_2_muestra(x, nmax):
a0 = (1 + math.pi) / 2
suma = 0.0
for n in range(1, nmax + 1):
an = calcular_an_modo0(n)
bn = calcular_bn_modo0(n)
suma += (
an * math.cos(n * w0 * x)
+ bn * math.sin(n * w0 * x)
)
fx = a0 / 2 + suma
fx_display = int(fx * 20) + 512
fx_display = max(0, min(1023, fx_display))
return fx, fx_display
def calcular_an_modo1(n):
return -(1 / (n * math.pi)) * math.sin(n * math.pi / 2)
def calcular_bn_modo1(n):
return -(3 / (n * math.pi)) * (
1 - math.cos(n * math.pi / 2)
)
def SerieFourier_3_muestra(x, nmax):
a0 = -1 / 2
suma = 0.0
for n in range(1, nmax + 1):
an = calcular_an_modo1(n)
bn = calcular_bn_modo1(n)
suma += (
an * math.cos(n * w0 * x)
+ bn * math.sin(n * w0 * x)
)
fx = a0 / 2 + suma
fx_display = int(fx * 100) + 512
fx_display = max(0, min(1023, fx_display))
return fx, fx_display
def calcular_an_modo2(n):
return (2 / (n * math.pi)) * math.sin(n * math.pi / 2)
def calcular_bn_modo2(n):
return 0.0
def SerieFourier_4_muestra(x, nmax):
a0 = 1.0
suma = 0.0
for n in range(1, nmax + 1):
an = calcular_an_modo2(n)
suma += an * math.cos(n * w0 * x)
fx = a0 / 2 + suma
fx_display = int(fx * 100) + 512
fx_display = max(0, min(1023, fx_display))
return fx, fx_display
def calcular_an_modo3(n):
A = 1 - math.exp(-0.5)
denominador = 1 + 16 * math.pi * math.pi * n * n
return (4 * A) / denominador
def calcular_bn_modo3(n):
A = 1 - math.exp(-0.5)
denominador = 1 + 16 * math.pi * math.pi * n * n
return (16 * math.pi * n * A) / denominador
def SerieFourier_5_muestra(x, nmax):
A = 1 - math.exp(-0.5)
a0 = 4 * A
w0_local = 4 * math.pi
suma = 0.0
for n in range(1, nmax + 1):
an = calcular_an_modo3(n)
bn = calcular_bn_modo3(n)
suma += (
an * math.cos(n * w0_local * x)
+ bn * math.sin(n * w0_local * x)
)
fx = a0 / 2 + suma
fx_display = int(fx * 1023)
fx_display = max(0, min(1023, fx_display))
return fx, fx_display
def calcular_muestra_fourier(modo_actual, x, nmax):
if modo_actual == 0:
return SerieFourier_2_muestra(x, nmax)
elif modo_actual == 1:
return SerieFourier_3_muestra(x, nmax)
elif modo_actual == 2:
return SerieFourier_4_muestra(x, nmax)
else:
return SerieFourier_5_muestra(x, nmax)
def nombre_modo(modo_actual):
if modo_actual == 0:
return "Fourier F1"
elif modo_actual == 1:
return "Fourier F2"
elif modo_actual == 2:
return "Fourier F3"
else:
return "Fourier F4"
def leer_vsys():
if not VSYS_OK:
return 4.8
try:
lectura = adc_vsys.read_u16()
voltaje_adc = lectura * 3.3 / 65535
vsys = voltaje_adc * 3
if vsys < 1.0:
return 4.8
return vsys
except Exception:
return 4.8
def porcentaje_bateria(vsys):
porcentaje = int((vsys - 3.0) * 100 / (5.0 - 3.0))
porcentaje = max(0, min(100, porcentaje))
return porcentaje
# ============================================================
# OLED: SOLO RELOJ, BATERIA Y GRAFICA LIMPIA
# ============================================================
def limpiar_barra_estado():
oled.fill_rect(0, 0, 128, 11, 0)
def dibujar_barra_estado():
vsys = leer_vsys()
bat = porcentaje_bateria(vsys)
limpiar_barra_estado()
texto_hora = f"{horas:02d}:{minutos:02d}:{segundos:02d}"
oled.text(texto_hora, 0, 0)
oled.text(f"{bat}%", 96, 0)
oled.hline(0, 10, 128, 1)
def dibujar_grafica_limpia(modo_actual, nmax):
x0 = 0
y0 = 11
ancho = 128
alto = 53
oled.fill_rect(x0, y0, ancho, alto, 0)
xmin_graf, xmax_graf, delta_graf = parametros_modo(modo_actual)
muestras = 128
buffer_fx = []
for i in range(muestras):
x = xmin_graf + (
(xmax_graf - xmin_graf) * i / (muestras - 1)
)
fx, valor = calcular_muestra_fourier(
modo_actual,
x,
nmax
)
buffer_fx.append(fx)
fx_min = min(buffer_fx)
fx_max = max(buffer_fx)
if abs(fx_max - fx_min) < 0.0001:
fx_max = fx_min + 1
punto_anterior_x = x0
punto_anterior_y = y0 + alto // 2
for i in range(muestras):
fx = buffer_fx[i]
px = x0 + i
normalizado = (fx - fx_min) / (fx_max - fx_min)
py = (
y0 + alto - 2
- int(normalizado * (alto - 4))
)
if i > 0:
oled.line(
punto_anterior_x,
punto_anterior_y,
px,
py,
1
)
punto_anterior_x = px
punto_anterior_y = py
def actualizar_oled(fx_actual, valor_dac):
dibujar_barra_estado()
dibujar_grafica_limpia(modo, narmonicos)
oled.show()
def irq_next(pin):
global modo_solicitado, flag_cambio_modo, ultimo_next
ahora = utime.ticks_ms()
if utime.ticks_diff(ahora, ultimo_next) > TIEMPO_DEBOUNCE_MS:
modo_solicitado = (modo_solicitado + 1) % 4
flag_cambio_modo = True
ultimo_next = ahora
def irq_reset(pin):
global modo_solicitado, flag_cambio_modo, ultimo_reset
ahora = utime.ticks_ms()
if utime.ticks_diff(ahora, ultimo_reset) > TIEMPO_DEBOUNCE_MS:
modo_solicitado = 0
flag_cambio_modo = True
ultimo_reset = ahora
btn_next.irq(trigger=Pin.IRQ_FALLING, handler=irq_next)
btn_reset.irq(trigger=Pin.IRQ_FALLING, handler=irq_reset)
def timer_reloj_callback(timer):
global horas, minutos, segundos, flag_actualizar_barra
segundos += 1
if segundos >= 60:
segundos = 0
minutos += 1
if minutos >= 60:
minutos = 0
horas += 1
if horas >= 24:
horas = 0
flag_actualizar_barra = True
timer_reloj = Timer()
timer_reloj.init(
period=1000,
mode=Timer.PERIODIC,
callback=timer_reloj_callback
)
# ============================================================
# INICIO DIRECTO DEL SISTEMA
# ============================================================
oled.fill(0)
oled.show()
#-----
fx = 0.0
valor_dac = 512
binario = "0000000000"
while True:
ahora = utime.ticks_ms()
if flag_cambio_modo:
flag_cambio_modo = False
modo = modo_solicitado
xmin_modo, xmax_modo, delta_modo = parametros_modo(modo)
x_actual = xmin_modo
flag_actualizar_barra = True
if utime.ticks_diff(ahora, ultimo_dac) >= PERIODO_DAC_MS:
ultimo_dac = ahora
fx, valor_dac = calcular_muestra_fourier(
modo,
x_actual,
narmonicos
)
print(valor_dac)
binario = GPIO_SALP(valor_dac, pines)
xmin_modo, xmax_modo, delta_modo = parametros_modo(modo)
x_actual += delta_modo
if x_actual > xmax_modo:
x_actual = xmin_modo
contador += 1
if AUTO_ARMONICOS:
if utime.ticks_diff(
ahora,
ultimo_cambio_armonico
) >= TIEMPO_CAMBIO_ARMONICO_MS:
ultimo_cambio_armonico = ahora
narmonicos += 1
if narmonicos > ARMONICOS_MAX:
narmonicos = ARMONICOS_MIN
flag_actualizar_barra = True
if flag_actualizar_barra:
flag_actualizar_barra = False
actualizar_oled(fx, valor_dac)
print(
f"Modo={modo} "
f"{nombre_modo(modo)} "
f"x={x_actual:.3f} "
f"fx={fx:.5f} "
f"DAC={valor_dac} "
f"bits={binario}"
)
utime.sleep_ms(1)