Objetivo y caso de uso
Qué construirás: Un generador de tonos audibles utilizando un zumbador pasivo conectado a una placa FPGA iCEBreaker.
Para qué sirve
- Generar diferentes tonos para alertas en sistemas de monitoreo.
- Crear melodías simples para proyectos de entretenimiento educativo.
- Emitir señales acústicas en sistemas de comunicación de corto alcance.
Resultado esperado
- Frecuencia de salida ajustable entre 1 kHz y 4 kHz, medible con un multímetro.
- Consumo de corriente máximo de 20 mA durante la operación.
- Latencia de respuesta del sistema menor a 10 ms al cambiar de tono.
Público objetivo: Estudiantes y entusiastas de la electrónica; Nivel: Básico
Arquitectura/flujo: Diseño en Verilog, implementación en FPGA iCEBreaker, salida de audio a través del zumbador pasivo.
Nivel: Básico
Prerrequisitos
- Sistema operativo:
- Linux (recomendado): Ubuntu 22.04 LTS o 24.04 LTS
- También funciona en Windows 10/11 y macOS, pero los comandos aquí se darán para Linux.
- Toolchain de FPGA:
- OSS CAD Suite (distribución que incluye todo lo necesario)
- Release: oss-cad-suite 2024-09-10 (binarios precompilados para Linux x86_64)
- Componentes y versiones (tal como reportan los ejecutables):
- yosys 0.40+ (YosysHQ)
- nextpnr-ice40 0.7+ (NextPNR para iCE40)
- icestorm (icepack/icetime) 0.0.0+20240901
- openFPGALoader 0.12.0
- Conocimientos previos:
- Nivel básico de Verilog (asignaciones, contadores, módulos).
- Uso básico de la terminal.
- Hardware:
- Placa FPGA exacta: iCEBreaker (Lattice iCE40UP5K-SG48)
- Cable USB micro-B
- Zumbador pasivo piezoeléctrico (3.3 V–5 V, preferible pasivo, no “activo”)
- Resistencia 100 Ω (serie con el zumbador, para limitar corriente)
- Jumpers/Male-Male para PMOD
- Multímetro con función de frecuencia o, idealmente, un osciloscopio (opcional para validación)
Nota sobre la elección de la toolchain: la familia iCE40 se trabaja con yosys + nextpnr-ice40 + icestorm, y se recomienda openFPGALoader para programar la iCEBreaker. Utilizaremos exactamente estas herramientas dentro de OSS CAD Suite 2024-09-10, asegurando compatibilidad y facilidad de instalación.
Materiales
- 1 × FPGA iCEBreaker (Lattice iCE40UP5K)
- 1 × Cable USB micro-B
- 1 × Zumbador pasivo (piezo)
- 1 × Resistencia 100 Ω (1/4 W)
- 2 × Cables Dupont macho-macho (para usar el conector PMOD)
- PC con Linux y OSS CAD Suite 2024-09-10 instalada
Objetivo del proyecto: construir un “tone-generator-pwm-buzzer” (generador de tono por PWM para zumbador pasivo), sintetizado y grabado sobre la iCEBreaker (Lattice iCE40UP5K).
Preparación y conexión
La iCEBreaker tiene conectores tipo PMOD. Usaremos el conector PMOD A y sacaremos una señal PWM desde el FPGA hacia el zumbador pasivo. Conectaremos el zumbador en serie con una resistencia de 100 Ω entre el pin A1 (señal) y GND del mismo PMOD.
Nota: La iCEBreaker integra un oscilador interno (SB_HFOSC) de 48 MHz en el iCE40UP5K. Lo usaremos para no depender del oscilador externo. No debes cablear ningún pin de reloj: el reloj es interno en el FPGA.
- Polaridad del zumbador:
- Un zumbador pasivo piezo no tiene polaridad estricta; aun así, muchos tienen marca “+”. Si lo tiene, conecta “+” al pin de señal a través de la resistencia y el otro terminal a GND.
- Precaución:
- No uses un zumbador activo (esos traen un oscilador interno y solo necesitan DC); nuestro diseño genera una señal AC con PWM.
Tabla de conexión recomendada:
| Elemento | Conector iCEBreaker | Señal FPGA (PCF) | Notas de conexión |
|---|---|---|---|
| Zumbador pasivo | PMOD A, pin 1 (A1) | buzzer_out (pin 23 del FPGA) | Señal PWM. Conectar en serie con 100 Ω hacia el zumbador. |
| Zumbador pasivo | PMOD A, GND | GND | Retorno a masa. |
| LED RGB (debug) | Integrado en placa | RGB0=39, RGB1=40, RGB2=41 | Opcional: útil para ver que el diseño “late” a baja frecuencia. |
- Esquema de cableado con texto:
1) Inserta un jumper desde PMOD A, pin 1 (A1) hacia una pata de la resistencia de 100 Ω.
2) La otra pata de la resistencia va al terminal “+” del zumbador (si lo tiene).
3) El terminal “-” del zumbador va a GND del PMOD A.
4) No conectes nada más al PMOD A para este ejemplo.
5) Conecta la iCEBreaker por USB al PC.
Importante:
– Los pines RGB0/1/2 son dedicados a un LED RGB integrado, mapeados a los pines del FPGA 39/40/41. Los usaremos solo de forma opcional para debug visual.
– Trabajaremos con niveles lógicos a 3.3 V. El zumbador pasivo típico es compatible.
Código completo (Verilog) y explicación
Vamos a generar un tono PCM mediante PWM. La idea es:
– Un oscilador interno a 48 MHz (SB_HFOSC).
– Un contador de PWM de 8 bits que genera una portadora de ~187.5 kHz (48 MHz / 256).
– Un acumulador de fase que sube con un “paso” calculado para obtener 440 Hz (nota La).
– A partir de la fase generamos una forma de onda triangular de 8 bits (duty), suavizando el timbre con respecto a un cuadrado puro.
– La salida PWM se conecta al zumbador.
Ventajas:
– PWM de alta frecuencia (inaudible), con envolvente a 440 Hz que el zumbador reproduce como tono.
– Sin tablas de seno necesarias (reducimos complejidad).
– Ajuste fácil de frecuencia cambiando un parámetro.
Archivo: src/tone_pwm_buzzer.v
// tone_pwm_buzzer.v
// Generador de tono por PWM para zumbador pasivo en iCEBreaker (iCE40UP5K)
// PWM carrier ~187.5 kHz (48 MHz / 256).
// Tono: 440 Hz (La4), generado por acumulador de fase y onda triangular de 8 bits.
module tone_pwm_buzzer (
output wire buzzer_out, // Señal PWM hacia zumbador (salida digital 3.3V)
output wire led_r, // Opcional: LED RGB rojo (debug)
output wire led_g, // Opcional: LED RGB verde (debug)
output wire led_b // Opcional: LED RGB azul (debug)
);
// -----------------------------
// 1) Reloj interno: SB_HFOSC
// -----------------------------
wire clk_48mhz;
SB_HFOSC #(
.CLKHF_DIV("0b00") // 00: 48 MHz, 01: 24 MHz, 10: 12 MHz, 11: 6 MHz
) hfosc (
.CLKHFEN(1'b1),
.CLKHFPU(1'b1),
.CLKHF(clk_48mhz)
);
// -----------------------------
// 2) Generador de PWM 8 bits
// -----------------------------
reg [7:0] pwm_cnt = 8'd0;
always @(posedge clk_48mhz) begin
pwm_cnt <= pwm_cnt + 8'd1;
end
// -----------------------------
// 3) Acumulador de fase para 440 Hz
// f_out = (PHASE_STEP * F_clk) / 2^24
// Para F_clk = 48 MHz y f_out ≈ 440 Hz:
// PHASE_STEP ≈ round(440 * 2^24 / 48e6) ≈ round(153.79) = 154
// -----------------------------
localparam [23:0] PHASE_STEP = 24'd154;
reg [23:0] phase = 24'd0;
always @(posedge clk_48mhz) begin
phase <= phase + PHASE_STEP;
end
// -----------------------------
// 4) Onda triangular de 8 bits
// tri = (MSB ? ~next8 : next8)
// next8 = phase[22:15]; MSB = phase[23]
// -----------------------------
wire msb = phase[23];
wire [7:0] next8 = phase[22:15];
wire [7:0] tri = msb ? ~next8 : next8;
// -----------------------------
// 5) Control de volumen (simple)
// volume_shift = 0 => 100% ; 1 => 50% ; 2 => 25% ...
// -----------------------------
localparam integer VOLUME_SHIFT = 0;
wire [7:0] duty = tri >> VOLUME_SHIFT;
// -----------------------------
// 6) Comparador PWM
// -----------------------------
assign buzzer_out = (pwm_cnt < duty);
// -----------------------------
// 7) LEDs RGB opcionales
// - led_g: "latido" a ~1 Hz para ver actividad
// - led_r/led_b: muestran duty en MSBs (efecto brillo)
// -----------------------------
reg [25:0] hb_cnt = 26'd0;
always @(posedge clk_48mhz) begin
hb_cnt <= hb_cnt + 26'd1; // 48e6 / 2^26 ~ 0.716 Hz (aprox observable)
end
assign led_g = hb_cnt[25]; // Parpadeo lento
assign led_r = duty[7]; // Brillo proporcional
assign led_b = duty[6]; // Brillo proporcional
endmodule
Breve explicación:
– SB_HFOSC: usa el reloj interno a 48 MHz, evitando depender del pin de reloj externo. Esto simplifica el cableado y es 100% compatible con iCE40UP5K de la iCEBreaker.
– pwm_cnt: contador de 8 bits a 48 MHz -> portadora PWM ≈ 48e6/256 = 187.5 kHz (inaudible).
– phase y PHASE_STEP: un acumulador de 24 bits produce una rampa que “avanza” a una tasa ajustada para 440 Hz. Tomamos los bits altos para crear una forma triangular de 8 bits (duty).
– duty y buzzer_out: el duty es la “amplitud instantánea” que alimenta el comparador PWM para construir la onda PWM. El zumbador, al ser resonante, reproduce el tono derivado por la modulación del duty.
– LEDs RGB: opcionales, útiles para verificar visualmente que el diseño está vivo (led_g) y que duty varía (led_r, led_b).
Archivo de constraints: icebreaker.pcf (mapea pines del FPGA a señales del top). En este caso, usaremos:
– buzzer_out en un pin del PMOD A, pin 1 (A1), que referenciamos en el PCF como el pin físico del FPGA. Para este ejemplo, emplearemos el pin 23 del chip (coherente con el ruteo al PMOD A1 en este caso práctico).
– RGB0/1/2 para LED integrado: pines 39/40/41 del FPGA.
# icebreaker.pcf
# iCEBreaker (Lattice iCE40UP5K-SG48)
# Asignaciones de pines para nuestro proyecto "tone-generator-pwm-buzzer".
# Salida PWM hacia zumbador en PMOD A, pin 1 (A1)
# Nota: si tu iCEBreaker tiene un pinout distinto, ajusta este valor según la hoja de datos de tu placa.
set_io buzzer_out 23
# LED RGB integrado (opcional, para debug visual)
# Pines dedicados RGB del iCE40UP5K SG48:
set_io led_r 39
set_io led_g 40
set_io led_b 41
Sugerencia: Si tienes un archivo PCF oficial de iCEBreaker, puedes reutilizarlo y añadir solo la línea “set_io buzzer_out XX” cambiando XX al pin físico del FPGA correspondiente al PMOD A1 de tu placa.
Compilación, grabación y ejecución
Asumimos que descargaste y descomprimiste OSS CAD Suite 2024-09-10 en $HOME/oss-cad-suite, y que añadiste su bin a tu PATH.
- Verificación de versiones:
- yosys -V -> debe mostrar 0.40+ (o similar)
- nextpnr-ice40 –version -> 0.7+ (o similar)
- openFPGALoader –version -> 0.12.0
Estructura de directorios sugerida:
– proyecto/
– src/tone_pwm_buzzer.v
– icebreaker.pcf
– build/ (se creará)
Comandos paso a paso:
# 0) Preparar entorno (ajusta la ruta si instalaste OSS CAD Suite en otra carpeta)
export PATH="$HOME/oss-cad-suite/bin:$PATH"
# 1) Sintetizar con yosys (genera tone.json)
mkdir -p build
yosys -p "read_verilog src/tone_pwm_buzzer.v; synth_ice40 -top tone_pwm_buzzer -json build/tone.json"
# 2) Place & Route con nextpnr-ice40 (UP5K, paquete sg48)
nextpnr-ice40 --up5k --package sg48 --json build/tone.json --pcf icebreaker.pcf --asc build/tone.asc --freq 48
# 3) (Opcional) Análisis de tiempo/recursos (icetime)
icetime -d up5k -p icebreaker.pcf build/tone.asc
# 4) Empaquetar bitstream (icepack)
icepack build/tone.asc build/tone.bin
# 5) Programar la iCEBreaker (con openFPGALoader)
# -b icebreaker selecciona la configuración del programador FTDI de la placa
openFPGALoader -b icebreaker -f build/tone.bin
Si prefieres usar iceprog (parte de icestorm) en lugar de openFPGALoader:
iceprog build/tone.bin
Notas:
– –freq 48 en nextpnr-ice40 es una guía para el PnR respecto a la frecuencia objetivo del reloj.
– Si openFPGALoader solicita permisos, revisa udev rules (ver Troubleshooting).
– Tras programar, el diseño se ejecuta inmediatamente: deberías escuchar el tono en el zumbador.
Makefile opcional (para automatizar):
# Makefile básico para iCEBreaker - tone-generator-pwm-buzzer
TOP := tone_pwm_buzzer
DEVICE := up5k
PACKAGE := sg48
PCF := icebreaker.pcf
SRC := src/$(TOP).v
BUILD := build
all: $(BUILD)/$(TOP).bin
$(BUILD)/$(TOP).json: $(SRC)
mkdir -p $(BUILD)
yosys -p "read_verilog $(SRC); synth_ice40 -top $(TOP) -json $(BUILD)/$(TOP).json"
$(BUILD)/$(TOP).asc: $(BUILD)/$(TOP).json $(PCF)
nextpnr-ice40 --$(DEVICE) --package $(PACKAGE) --json $(BUILD)/$(TOP).json --pcf $(PCF) --asc $(BUILD)/$(TOP).asc --freq 48
$(BUILD)/$(TOP).bin: $(BUILD)/$(TOP).asc
icepack $(BUILD)/$(TOP).asc $(BUILD)/$(TOP).bin
prog: $(BUILD)/$(TOP).bin
openFPGALoader -b icebreaker -f $(BUILD)/$(TOP).bin
time: $(BUILD)/$(TOP).asc
icetime -d $(DEVICE) -p $(PCF) $(BUILD)/$(TOP).asc
clean:
rm -rf $(BUILD)
.PHONY: all prog time clean
Uso:
– make para construir
– make prog para programar
– make time para informe de timing
– make clean para limpiar
Validación paso a paso
1) Sonido audible:
– Al finalizar la programación, deberías escuchar un tono constante (aprox. 440 Hz) en el zumbador.
– La intensidad dependerá del zumbador y del acoplamiento mecánico; si es muy bajo, acércalo al oído o apóyalo sobre la mesa para mejorar la transmisión acústica.
2) LED RGB de debug:
– led_g debe parpadear lentamente (aprox. 0.7 Hz), indicando que el reloj interno y la lógica corren.
– led_r/led_b deben mostrar un brillo que varía suavemente (proporcional al duty en MSBs).
3) Medición con multímetro (si tiene modo frecuencia):
– Coloca el terminal positivo en A1 (a la salida de la resistencia de 100 Ω) y el negativo en GND del PMOD A.
– Muchos multímetros medirán algo cercano a 440 Hz (aunque la señal es PWM, algunos medidores la interpretan como frecuencia fundamental).
4) Medición con osciloscopio (recomendado):
– Sonda en el pin A1 (señal PWM). Masa en GND.
– Debes observar:
– Una portadora PWM de ~187.5 kHz (48 MHz / 256) modulada en duty.
– En modo promedio/FFT, la componente principal cerca de 440 Hz.
– Si usas un filtro pasa-bajo virtual o promediado de la señal, verás una onda triangular a ~440 Hz.
5) Consumo y temperatura:
– El diseño es muy ligero; no debería calentar perceptiblemente la placa. Si hay calentamiento, revisa conexiones (posible cortocircuito).
6) Persistencia:
– Este flujo programa en la SRAM del FPGA; al desconectar USB, el diseño se perderá (salvo que configures boot desde SPI flash con iceprog -S o via openFPGALoader con opciones de SPI). Para este caso básico, reprogramar cada vez es suficiente.
Troubleshooting
1) No se escucha nada:
– Verifica que el zumbador sea pasivo, no activo. Los activos no responden apropiadamente a PWM audio; generarían solo “clics” o nada.
– Revisa la conexión: A1 → resistencia 100 Ω → zumbador → GND.
– Asegura buen contacto en el PMOD y que realmente usas PMOD A (y no otro).
– Sube el volumen (reduce VOLUME_SHIFT a 0 si se cambió a 1 o 2).
2) Volumen muy bajo:
– Algunos piezos necesitan montaje sobre superficie para resonar mejor. Apoya el zumbador sobre la mesa.
– Usa conducción “diferencial” (mejora avanzada; ver sección de mejoras) para aumentar la excursión de tensión.
– Asegúrate de que el zumbador está diseñado para 3.3 V–5 V. Si es de 12 V, sonará débil sin etapa de potencia.
3) El LED verde no parpadea:
– Puede indicar que el diseño no corre. Reprograma la FPGA.
– Revisa que el reloj interno (SB_HFOSC) esté habilitado (CLKHFEN=1, CLKHFPU=1) y que .CLKHF_DIV sea “0b00”.
– Verifica errores de compilación/síntesis (repetir yosys y nextpnr y leer logs).
4) Error al programar con openFPGALoader (permisos/udev):
– En Linux, puede requerir reglas udev. Ejecuta con sudo como prueba temporal:
– sudo openFPGALoader -b icebreaker -f build/tone.bin
– Agrega reglas udev específicas para FTDI del iCEBreaker según documentación de openFPGALoader y vuelve a conectar el USB.
5) nextpnr falla por pines no válidos:
– Asegúrate de que el pin en el PCF corresponde a un pin GPIO válido del iCE40UP5K (paquete SG48).
– Corrige el mapeo en icebreaker.pcf según el pinout de tu placa iCEBreaker. En este caso práctico usamos 23 para PMOD A1.
– No uses pines reservados (por ejemplo, pines de configuración, VREF, SPI Flash si no corresponde).
6) Distorsión o tono “raro”:
– Es normal oír armónicos, ya que el zumbador piezo no es un altavoz Hi-Fi. El PWM a alta frecuencia puede introducir zumbidos débiles adicionales.
– Prueba otra forma de onda (cuadrada pura) para máximo volumen: asigna duty = {8{phase[23]}} ? 8’hFF : 8’h00; y compara.
– Ajusta PHASE_STEP para buscar una frecuencia que “resuene” mejor con tu piezo.
7) Frecuencia no coincide con 440 Hz:
– La aproximación de PHASE_STEP usa aritmética entera; 154 es la aproximación a 440. Puedes afinar:
– PHASE_STEP = round(2^24 * f / 48e6). Para f=440 exacto, 153.79 => 154 (error < 0.14%).
– Si requieres precisión, usa un oscilador externo estable o corrige por medición.
8) Conflicto con otros periféricos:
– Asegúrate de no tener conectado nada más al PMOD A1 que pueda cargar la línea.
– Desconecta otros módulos/alas (wings) durante este ejercicio.
Mejoras/variantes
- Cambio de nota en tiempo real:
- Expone PHASE_STEP vía interruptores (DIP) o botones, y mapea varias notas (A4=440 Hz, C5≈523 Hz, E5≈659 Hz, etc.).
-
Implementa una pequeña LUT de PHASE_STEP por nota.
-
Modo cuadrado y modo triangular:
- Añade una señal para seleccionar entre duty triangular (suave) y cuadrado (máximo volumen).
-
Incluso añade una LUT de seno de 8 bits para timbre más limpio (aunque el piezo no filtra mucho, servirá como práctica).
-
Control de volumen:
-
Cambia VOLUME_SHIFT de forma dinámica con un botón para 100%, 50%, 25%, 12.5%.
-
Conducción diferencial:
-
Usa dos pines de salida en oposición de fase (out_p = pwm, out_n = ~pwm) y conecta el zumbador entre ambos a través de dos resistencias (ej. 100 Ω en cada línea). Duplica la tensión eficaz y el nivel acústico.
-
Múltiples tonos o melodía:
-
Implementa un secuenciador que cambie PHASE_STEP con temporizador, reproduciendo una melodía corta.
-
Mezcla de voces (polifonía simple):
-
Suma dos ondas (clipeando a 8 bits) y usa ese resultado como duty; escucharás acordes en el piezo.
-
Arranque desde SPI flash:
- Graba el bitstream en la flash SPI de la iCEBreaker para que el diseño cargue solo al encender (iceprog -S, o openFPGALoader con opción para flash según guía).
Checklist de verificación
- [ ] He instalado OSS CAD Suite 2024-09-10 y tengo en PATH: yosys, nextpnr-ice40, icepack, openFPGALoader.
- [ ] Verifiqué versiones: yosys ~0.40+, nextpnr-ice40 ~0.7+, openFPGALoader 0.12.0.
- [ ] He creado la estructura de proyecto con src/tone_pwm_buzzer.v e icebreaker.pcf.
- [ ] El PCF mapea buzzer_out al pin correcto del PMOD A1 de mi iCEBreaker (en este caso práctico, pin 23).
- [ ] He conectado el zumbador pasivo: A1 → 100 Ω → zumbador → GND (PMOD A).
- [ ] He sintetizado, enrutado y generado el bitstream sin errores.
- [ ] He programado la iCEBreaker con openFPGALoader -b icebreaker -f build/tone.bin.
- [ ] Escucho un tono (aprox. 440 Hz) en el zumbador.
- [ ] El LED verde integrado parpadea lentamente; los LED rojo/azul muestran variación de brillo.
- [ ] He probado PHASE_STEP alternativo u otros modos (opcional) y entiendo cómo cambia el tono.
Con este caso práctico, has completado un flujo básico de desarrollo con la iCEBreaker (Lattice iCE40UP5K) empleando la toolchain libre yosys + nextpnr-ice40 + icestorm + openFPGALoader, y has implementado un generador de tono por PWM para un zumbador pasivo. El mismo patrón de diseño (oscilador interno + NCO + PWM) es versátil para más aplicaciones de audio simple y señalización acústica en sistemas embebidos con FPGA.
Encuentra este producto y/o libros sobre este tema en Amazon
Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.



