# ESP32 MicroPython - Generador senoidal por DAC con control de amplitud y frecuencia por 2 ADC
# DAC: GPIO25
# ADC AMP: GPIO34 | ADC FREQ: GPIO35
# DDS con tabla de 256 puntos y Timer a fs fija
from machine import Pin, DAC, ADC, Timer
import micropython
# ---------- Configuración de pines ----------
DAC_PIN = 25 # DAC1
ADC_AMP = 34 # control amplitud
ADC_FREQ = 35 # control frecuencia
# ---------- Parámetros del generador ----------
FS_HZ = 40000 # frecuencia de muestreo del DAC (Hz)
TABLE_LEN = 256 # tamaño de tabla senoidal
MIN_FREQ_HZ = 5 # f mínima
MAX_FREQ_HZ = 5000 # f máxima
AMP_MAX_SCALE = 255 # 0..255 (0%..100%)
# ---------- Tabla senoidal 0..255 ----------
# 128 + 127*sin(2*pi*n/N), n=0..N-1
import math
sine_table = bytearray(
int(128 + 127*math.sin(2*math.pi*n/TABLE_LEN)) & 0xFF
for n in range(TABLE_LEN)
)
# ---------- Periféricos ----------
dac = DAC(Pin(DAC_PIN))
adc_amp = ADC(Pin(ADC_AMP))
adc_freq = ADC(Pin(ADC_FREQ))
# ADC a 12 bits (0..4095), atenuación 11dB para 0..~3.3V
adc_amp.atten(ADC.ATTN_11DB)
adc_freq.atten(ADC.ATTN_11DB)
adc_amp.width(ADC.WIDTH_12BIT)
adc_freq.width(ADC.WIDTH_12BIT)
# ---------- DDS (acumulador de fase fijo Q16) ----------
# fase: 32 bits, usamos Q16.16 para simplicidad (índice = fase>>16 & 0xFF)
phase = 0
phase_inc = 1 << 8 # valor por defecto (se ajusta más abajo)
amp_scale = 200 # 0..255 (≈ 78% por defecto)
# Suavizado simple de potenciómetros (IIR)
amp_lpf = 0
freq_lpf = 0
ALPHA = 0.1 # 0..1 (más alto = responde más rápido)
# Timer para la ISR de salida
tim = Timer(0)
running = False
micropython.alloc_emergency_exception_buf(100)
def map_lin(x, in0, in1, out0, out1):
# Mapea linealmente x del rango [in0,in1] a [out0,out1]
if x < in0: x = in0
if x > in1: x = in1
return out0 + (out1 - out0) * (x - in0) / (in1 - in0)
def compute_phase_inc(freq_hz):
# phase_inc = freq * (TABLE_LEN << 16) / FS_HZ (Q16 con tabla de 256)
return int(freq_hz * (TABLE_LEN << 16) / FS_HZ)
def isr_cb(t):
# Usa variables globales (rápido, sin float)
global phase
# índice de tabla
idx = (phase >> 16) & (TABLE_LEN - 1)
s = sine_table[idx] # 0..255
# Escalado de amplitud alrededor de 128: y = 128 + (s-128)*amp/255
# Todo en enteros
y = 128 + ((s - 128) * amp_scale) // 255
if y < 0: y = 0
elif y > 255: y = 255
dac.write(y)
# Avance de fase
# Nota: phase_inc se actualiza en el lazo principal
phase = (phase + phase_inc) & 0xFFFFFFFF
def start():
global running
if running:
return
tim.init(freq=FS_HZ, mode=Timer.PERIODIC, callback=isr_cb)
running = True
def stop():
global running
if not running:
return
tim.deinit()
running = False
# ---------- Arranque ----------
start()
# ---------- Lazo principal: lee potenciómetros y actualiza controles ----------
# - adc_amp -> amp_scale (0..255)
# - adc_freq -> phase_inc (map a 5..5000 Hz)
try:
while True:
# Leer ADCs (0..4095)
ra = adc_amp.read()
rf = adc_freq.read()
# Suavizado IIR
amp_lpf = (1 - ALPHA) * amp_lpf + ALPHA * ra
freq_lpf = (1 - ALPHA) * freq_lpf + ALPHA * rf
print("Freq=",freq_lpf," Amp=",amp_lpf)
# Mapear a amplitud 0..255 (puedes fijar un mínimo si quieres)
new_amp = int(map_lin(amp_lpf, 0, 4095, 0, AMP_MAX_SCALE))
# Mapear a frecuencia MIN..MAX y convertir a phase_inc
freq_hz = map_lin(freq_lpf, 0, 4095, MIN_FREQ_HZ, MAX_FREQ_HZ)
new_inc = compute_phase_inc(freq_hz)
# Actualizar globals (rápido)
amp_scale = new_amp
phase_inc = new_inc
# Pequeño respiro al lazo (no afecta la tasa del DAC)
# Ajusta si necesitas respuesta de UI más rápida
import time
time.sleep_ms(10)
except KeyboardInterrupt:
stop()