from machine import Pin, I2C, ADC, DAC
import time, math
import ssd1306
# === Configuración ===
PUNTOS = 100 # Número de puntos para generar la onda
TIEMPO_PANTALLA = 0.1 # Tiempo de visualización en segundos
NOMBRES_ONDAS = ["SEN", "CUA", "TRI"] # Abreviaturas para los tipos de onda
# === Inicialización del hardware ===
oled = ssd1306.SSD1306_I2C(128, 64, I2C(0, scl=Pin(22), sda=Pin(21))) # Pantalla OLED 128x64
pot_amplitud = ADC(Pin(34)) # Potenciómetro para controlar amplitud
pot_amplitud.atten(ADC.ATTN_11DB) # Configurar para leer 0-3.3V
pot_amplitud.width(ADC.WIDTH_12BIT) # Resolución de 12 bits (0-4095)
pot_frecuencia = ADC(Pin(35)) # Potenciómetro para controlar frecuencia
pot_frecuencia.atten(ADC.ATTN_11DB) # Configurar para leer 0-3.3V
pot_frecuencia.width(ADC.WIDTH_12BIT) # Resolución de 12 bits (0-4095)
salida_analogica = DAC(Pin(25)) # Salida analógica del ESP32
boton = Pin(15, Pin.IN, Pin.PULL_UP) # Botón para cambiar forma de onda con resistencia pull-up
# === Variables de estado ===
tipo_onda = 1 # Tipo de onda inicial (1=Senoidal, 2=Cuadrada, 3=Triangular)
forma_onda = [] # Lista para almacenar los valores de la forma de onda
indice = 0 # Índice actual en la forma de onda
estado_anterior_boton = 1 # Estado anterior del botón para detectar cambios
lectura_anterior_amplitud = pot_amplitud.read() # Lectura anterior de amplitud
lectura_anterior_frecuencia = pot_frecuencia.read() # Lectura anterior de frecuencia
tiempo_anterior = time.ticks_ms() # Para el antirrebote del botón
tiempo_antirrebote = 200 # Tiempo en ms para evitar rebotes del botón
contador_actualizacion = 0 # Contador para actualizar la pantalla
# === Funciones auxiliares ===
def generar_onda(tipo):
"""
Genera una forma de onda completa basada en el tipo seleccionado.
tipo: 1=Senoidal, 2=Cuadrada, 3=Triangular
Devuelve una lista con los valores normalizados entre -1 y 1
"""
onda = []
for i in range(PUNTOS):
x = i / PUNTOS # Valor normalizado entre 0 y 1
if tipo == 1: # Onda senoidal
valor = math.sin(2 * math.pi * x)
elif tipo == 2: # Onda cuadrada
valor = 1.0 if x < 0.5 else -1.0
elif tipo == 3: # Onda triangular
valor = 2 * x - 1 if x < 0.5 else 1 - 2 * (x - 0.5)
else:
valor = 0
onda.append(valor)
return onda
def mapear_frecuencia(valor_crudo):
"""Convierte el valor del ADC (0-4095) a frecuencia (0.5-50 Hz)"""
return 0.5 + (valor_crudo / 4095) * (50 - 0.5)
def mapear_amplitud(valor_crudo):
"""Convierte el valor del ADC (0-4095) a voltaje (0-3.3V)"""
return (valor_crudo / 4095) * 3.3
def voltaje_a_y(voltaje):
"""Convierte un valor de voltaje (0.4-2.9V) a coordenada Y en la pantalla (8-62)"""
# Limita el voltaje entre 0.4V y 2.9V, luego lo mapea a coordenadas de pantalla
return int(62 - ((max(min(voltaje, 2.9), 0.4) - 0.4) / (2.9 - 0.4)) * 54)
# === Pantalla de inicio ===
oled.fill(0) # Limpia la pantalla
oled.text("Generador ON", 0, 0) # Mensaje de inicio
oled.show() # Actualiza la pantalla
forma_onda = generar_onda(tipo_onda) # Genera la forma de onda inicial
# === Bucle principal ===
while True:
# Leer y procesar botón con antirrebote
tiempo_actual = time.ticks_ms()
if (boton.value() == 0 and
estado_anterior_boton == 1 and
time.ticks_diff(tiempo_actual, tiempo_anterior) > tiempo_antirrebote):
# Si se presiona el botón, cambia el tipo de onda (1→2→3→1...)
tipo_onda = tipo_onda % 3 + 1
forma_onda = generar_onda(tipo_onda)
tiempo_anterior = tiempo_actual
estado_anterior_boton = boton.value()
# Leer potenciómetros para controlar amplitud y frecuencia
lectura_amplitud = pot_amplitud.read()
lectura_frecuencia = pot_frecuencia.read()
amplitud = mapear_amplitud(lectura_amplitud) # Amplitud en voltios
frecuencia = mapear_frecuencia(lectura_frecuencia) # Frecuencia en Hz
tiempo_retardo = 1 / (PUNTOS * frecuencia) # Cálculo de retardo entre puntos
# Escribir al DAC para generar la señal
# Convertimos desde el rango -1,1 al rango de voltaje adecuado
# 1.65V es el punto medio (equivale a 0 en la onda)
voltaje = 1.65 + forma_onda[indice] * amplitud / 2
salida_analogica.write(int((voltaje / 3.3) * 255)) # Escala a 0-255 para el DAC
# Actualizar la pantalla OLED periódicamente o al cambiar los controles
contador_actualizacion += 1
if (contador_actualizacion >= 10 or
abs(lectura_amplitud - lectura_anterior_amplitud) > 5 or
abs(lectura_frecuencia - lectura_anterior_frecuencia) > 5):
# Limpiar área de gráfico
oled.fill_rect(0, 8, 128, 56, 0)
# Calcular cuántos puntos de la onda mostrar según frecuencia
muestras = frecuencia * TIEMPO_PANTALLA * PUNTOS
# Dibujar forma de onda en pantalla
for x in range(128):
fase = (x / 128) * muestras
i1, i2 = int(fase) % PUNTOS, (int(fase) + 1) % PUNTOS
fraccion = fase - int(fase)
# Interpolación lineal para suavizar la visualización
valor = forma_onda[i1] * (1 - fraccion) + forma_onda[i2] * fraccion
voltaje = 1.65 + valor * amplitud / 2
oled.pixel(x, voltaje_a_y(voltaje), 1)
# Dibujar ejes y referencias
oled.vline(0, 8, 56, 1) # Eje vertical
oled.hline(0, voltaje_a_y(1.65), 128, 1) # Línea de 0V (1.65V real)
# Limpiar barra de estado y mostrar información
oled.fill_rect(0, 0, 128, 8, 0)
forma = NOMBRES_ONDAS[tipo_onda - 1]
oled.text("A={:.2f} F={:.1f}Hz".format(amplitud, frecuencia), 0, 0)
oled.text(forma, 90, 55) # Tipo de onda en esquina inferior
# Actualizar pantalla y guardar valores actuales
oled.show()
lectura_anterior_amplitud, lectura_anterior_frecuencia = lectura_amplitud, lectura_frecuencia
contador_actualizacion = 0
# Avanzar al siguiente punto de la onda
indice = (indice + 1) % PUNTOS
time.sleep(tiempo_retardo) # Esperar para mantener la frecuencia correcta