Objetivo y caso de uso
Qué construirás: Implementar un sistema de antirrebotes para botones y control de LED en la placa Lattice iCEBreaker utilizando Verilog.
Para qué sirve
- Controlar el encendido y apagado de un LED en respuesta a la pulsación de un botón.
- Evitar lecturas erróneas debido a rebotes mecánicos en botones físicos.
- Integrar el sistema con protocolos de comunicación como MQTT para reportar el estado del LED.
- Utilizar el bus TWAI para la comunicación entre dispositivos en un entorno de IoT.
Resultado esperado
- Latencia de respuesta del sistema de antirrebotes inferior a 50 ms.
- Reducción de errores de lectura de botones a menos del 1% en condiciones de uso normal.
- Capacidad de enviar mensajes de estado del LED a través de MQTT con un tiempo de entrega de menos de 100 ms.
- Consumo de energía del sistema inferior a 100 mW durante la operación.
Público objetivo: Ingenieros y estudiantes de electrónica; Nivel: Básico
Arquitectura/flujo: Diseño en Verilog, síntesis con Yosys, implementación con nextpnr-ice40, y programación con openFPGALoader.
Nivel: Básico
Prerrequisitos
Sistema operativo y versiones probadas
- GNU/Linux x86_64 (recomendado). Validado en:
- Ubuntu 24.04 LTS (64 bits)
- Debian 12 (Bookworm, 64 bits)
- También viable en macOS 13/14 (Apple Silicon o Intel) y Windows 10/11 (vía WSL2), pero los comandos mostrados a continuación están escritos y comprobados para GNU/Linux.
Toolchain exacta (iCE40)
Usaremos la cadena de herramientas de código abierto recomendada para iCE40:
– Yosys (síntesis): 0.40
– nextpnr-ice40 (place & route): 0.7
– Project IceStorm (empacado): icestorm 0.0+20240107 (icepack/iceunpack/icetime)
– openFPGALoader (programación): 0.13.0
Para simplificar la instalación, se recomienda usar la distribución “OSS CAD Suite” que incluye los binarios anteriores en una sola descarga.
- OSS CAD Suite (build): 2024-10-20
- yosys 0.40 (oss-cad-suite 20241020)
- nextpnr-ice40 0.7 (oss-cad-suite 20241020)
- icestorm 0.0+20240107 (oss-cad-suite 20241020)
- openFPGALoader 0.13.0 (oss-cad-suite 20241020)
Comandos de verificación de versiones (ejecútalos tras instalar y exportar el PATH del paquete):
yosys -V
nextpnr-ice40 --version
icepack -h
openFPGALoader --version
Ejemplo de salida esperada (puede variar ligeramente):
– yosys 0.40 (git sha…, oss-cad-suite 20241020)
– nextpnr-ice40 0.7
– icepack (Project IceStorm) usage… (no siempre muestra versión directa, pero proviene del bundle 20241020)
– openFPGALoader v0.13.0
Materiales
- 1 x FPGA board: Lattice iCEBreaker (iCE40UP5K, paquete SG48, oscilador de 12 MHz integrado)
- 1 x Cable USB (USB-C o micro‑USB según revisión del iCEBreaker; usa el cable oficial del kit si lo tienes)
- 1 x PC con GNU/Linux (Ubuntu 24.04 recomendado)
- Toolchain instalada (OSS CAD Suite 2024-10-20)
- Opcional:
- Multímetro (para comprobar 5 V/3.3 V y tierra en el USB si hiciera falta)
- Lupa o buena iluminación (para identificar serigrafía de LED y botón en la placa)
Notas específicas del iCEBreaker:
– El iCEBreaker integra:
– Un oscilador de 12 MHz
– Al menos un botón de usuario (BTN1)
– Un LED de usuario
– En este caso práctico usaremos exclusivamente el botón BTN1 y el LED de usuario integrados en la placa, sin hardware adicional.
Preparación y conexión
Instalación de la toolchain (OSS CAD Suite 2024-10-20)
- Crea un directorio de herramientas en tu HOME (opcional):
- mkdir -p ~/tools
- Descarga el paquete (Linux x64):
- wget https://github.com/YosysHQ/oss-cad-suite-build/releases/download/2024-10-20/oss-cad-suite-linux-x64-20241020.tgz -O ~/tools/oss-cad-suite-20241020.tgz
- Descomprime:
- tar -xzf ~/tools/oss-cad-suite-20241020.tgz -C ~/tools
- Exporta el PATH (temporal para la sesión actual):
- export PATH=~/tools/oss-cad-suite/bin:$PATH
- Verifica versiones con los comandos mostrados en la sección de prerrequisitos.
Sugerencia: añade la línea de export PATH a tu ~/.bashrc o ~/.zshrc para no tener que repetirla.
Conexión física del iCEBreaker
- Conecta la placa iCEBreaker al PC con el cable USB. El LED de “power” (si presente) debería encenderse.
- No conectes ningún otro periférico. Usaremos el botón y el LED integrados.
Mapeo de pines (señales usadas)
Para la Lattice iCEBreaker (iCE40UP5K, SG48), en este proyecto usaremos:
– Reloj principal: oscilador de 12 MHz
– Un botón de usuario (BTN1)
– Un LED de usuario (LED)
Tabla de pines/puertos del diseño:
| Señal lógica (port) | Función | Pin FPGA (SG48) | Notas importantes |
|---|---|---|---|
| clk12 | Reloj de sistema 12 MHz | 35 | Entrada global de reloj (oscilador onboard) |
| btn_n | Botón de usuario (activo-bajo) | 10 | BTN1: pulsado = 0 (pull-up) |
| led_n | LED de usuario (activo-bajo) | 39 | LED ON cuando señal = 0 |
- btn_n: la “n” indica activo-bajo. Lo normal en iCEBreaker es que el botón use pull-up y al pulsar vaya a GND.
- led_n: el LED suele estar cableado para encenderse cuando el pin se pone a 0 (activo-bajo).
Importante: Mantén estos nombres exactos en el archivo de constraints (.pcf) y en el código Verilog para coherencia.
Código completo
En este caso práctico implementaremos:
– Un sincronizador de dos etapas para la entrada del botón (mitigación de metastabilidad).
– Un filtro antirrebotes basado en contador saturado (tiempo de estabilización configurable; usaremos ~10 ms a 12 MHz).
– El LED refleja el estado del botón “limpio” (LED encendido mientras el botón está presionado), teniendo en cuenta que el LED es activo-bajo.
Estructura de archivos propuesta (puedes crearla en tu carpeta de trabajo):
– src/top.v (módulo principal con antirrebotes)
– constraints/icebreaker.pcf (mapeo de pines para iCEBreaker)
– build/ (carpeta de salida; se creará automáticamente)
Archivo de constraints: constraints/icebreaker.pcf
# iCEBreaker (iCE40UP5K-SG48) - Mapeo mínimo para este proyecto
# Reloj de 12 MHz
set_io clk12 35
# Botón de usuario (BTN1) - activo-bajo
# Habilitamos pull-up interno como refuerzo (la placa ya suele tener pull-up).
set_io -pullup yes btn_n 10
# LED de usuario - activo-bajo (LED encendido con '0')
set_io led_n 39
Código Verilog: src/top.v
/*
* Proyecto: button-debouncing-and-led en iCEBreaker (iCE40UP5K)
* Toolchain: yosys 0.40 + nextpnr-ice40 0.7 + icestorm (20240107) + openFPGALoader 0.13.0
* Reloj: 12 MHz
* Descripción:
* - Sincroniza y des-rebota un botón activo-bajo (btn_n).
* - Controla un LED activo-bajo (led_n) que refleja el estado limpio del botón.
*/
module top (
input wire clk12, // 12 MHz
input wire btn_n, // botón activo-bajo
output wire led_n // LED activo-bajo
);
// 1) Sincronizador de dos etapas para la señal del botón (evita metastabilidad)
reg [1:0] btn_sync;
always @(posedge clk12) begin
// Invertimos btn_n aquí para trabajar con '1' = pulsado, '0' = no pulsado
btn_sync <= { btn_sync[0], ~btn_n };
end
wire btn_sample = btn_sync[1];
// 2) Filtro antirrebotes por contador saturado
// - Consideramos "estable" cuando la muestra coincide durante al menos DEBOUNCE_MS
// - A 12 MHz, 10 ms ≈ 120_000 ciclos
localparam integer CLK_HZ = 12_000_000;
localparam integer DEBOUNCE_MS = 10; // puedes ajustar 5..20 ms
localparam integer CNTR_MAX = (CLK_HZ/1000)*DEBOUNCE_MS;
localparam integer CNTR_WIDTH = $clog2(CNTR_MAX+1);
reg [CNTR_WIDTH-1:0] cnt = {CNTR_WIDTH{1'b0}};
reg btn_stable = 1'b0; // estado "limpio" del botón (1 = pulsado)
always @(posedge clk12) begin
// Si la muestra concuerda con el estado estable actual, reseteamos el contador
// porque no hay "transición" sostenida que debamos confirmar.
if (btn_sample == btn_stable) begin
cnt <= {CNTR_WIDTH{1'b0}};
end else begin
// La muestra difiere del estado estable; incrementamos el contador hasta CNTR_MAX
if (cnt == CNTR_MAX[CNTR_WIDTH-1:0]) begin
// Suficiente tiempo con la nueva muestra: confirmamos el cambio de estado
btn_stable <= btn_sample;
cnt <= {CNTR_WIDTH{1'b0}};
end else begin
cnt <= cnt + 1'b1;
end
end
end
// 3) LED activo-bajo: encender LED cuando btn_stable = 1 (botón pulsado)
assign led_n = ~btn_stable;
endmodule
Puntos clave del código:
– Sincronizador de doble flip-flop (btn_sync) para reducir riesgos de metastabilidad en la entrada asíncrona del botón.
– Antirrebotes por tiempo: el estado solo cambia si la lectura distinta se mantiene N ciclos (10 ms a 12 MHz).
– Manejo de activos-bajo:
– btn_n se invierte al entrar (btn_sample = 1 cuando se pulsa).
– led_n se asigna a la negación de btn_stable (para encender el LED cuando btn_stable = 1).
Alternativas:
– Si prefieres que el LED “togglee” con cada pulsación (en vez de reflejar el estado sostenido), puedes agregar detección de flanco en btn_stable y conmutar un registro de LED. En este caso básico mantenemos la relación directa para centrar el foco en el antirrebotes.
Compilación, grabación y ejecución
Estructura de directorios (ejemplo):
– Tu carpeta de proyecto: ~/proyectos/icebreaker-debounce-led
– src/top.v
– constraints/icebreaker.pcf
– build/ (se creará para outputs)
Desde la carpeta raíz del proyecto (donde está la carpeta src/ y constraints/), ejecuta:
1) Síntesis (Yosys 0.40):
yosys -p "read_verilog src/top.v; synth_ice40 -top top -json build/top.json"
2) Place & Route (nextpnr-ice40 0.7) para iCE40UP5K, paquete SG48, frecuencia objetivo 12 MHz:
nextpnr-ice40 --up5k --package sg48 --json build/top.json --pcf constraints/icebreaker.pcf --asc build/top.asc --freq 12
3) Empaquetado (IceStorm / icepack):
icepack build/top.asc build/top.bin
4) Programación (openFPGALoader 0.13.0) para iCEBreaker:
openFPGALoader -b iCEBreaker build/top.bin
Notas y opciones:
– openFPGALoader detecta el cable FTDI y programa la FPGA del iCEBreaker. Si tienes permisos de udev restringidos, puede requerir sudo:
– sudo openFPGALoader -b iCEBreaker build/top.bin
– Para verificar la conexión antes de programar:
– openFPGALoader -c
– Para limpiar la carpeta de builds (opcional):
– rm -rf build && mkdir build
Salida esperada:
– Yosys: informe de síntesis con recuento de celdas y topología.
– nextpnr: informe de enrutamiento, uso de recursos y “Max frequency”.
– icepack: genera el binario (.bin) sin errores.
– openFPGALoader: mensaje “Done” o “Success” tras programación.
Validación paso a paso
1) Preparación visual:
– Identifica el LED de usuario (serigrafiado como “LED” o “USR LED”) y el botón BTN1 en la placa iCEBreaker.
– Asegúrate de que la placa está alimentada por el USB (LED de power encendido).
2) Estado inicial:
– Sin pulsar el botón, el LED debería estar apagado. Recuerda que led_n es activo-bajo y nuestro diseño lo apaga cuando btn_stable = 0 (no pulsado).
3) Prueba de pulsación lenta:
– Pulsa y mantén presionado BTN1 durante 1–2 segundos: el LED debe encenderse de forma estable, sin parpadeos.
– Suelta el botón: el LED debe apagarse sin parpadeos.
4) Prueba de pulsaciones rápidas:
– Haz pulsaciones rápidas (pero reales, no “golpes” mecánicos): el LED debería encenderse y apagarse limpiamente sin destellos.
– Si la pulsación es tan corta que dura menos de ~10 ms, es posible que el LED no llegue a encenderse. Esto es normal dadas las constantes del antirrebotes: DEBOUNCE_MS = 10 ms.
5) Observación del retardo:
– Notarás un pequeño retardo (hasta ~10 ms) entre el acto de pulsar/soltar y el encendido/apagado del LED. Ese es el efecto del filtro: evita que rebotes de varios milisegundos causen falsos cambios.
6) Verificación con temporización (opcional):
– Puedes reducir DEBOUNCE_MS a 5 ms (edita src/top.v) para notar menor retardo, recompila y reprograma.
– También puedes subirlo a 20 ms para endurecer el filtro.
7) Confirmación final:
– La funcionalidad queda validada si:
– El LED sigue el estado “real” del botón sin parpadeos espurios.
– No se ven transiciones falsas al pulsar/soltar.
Troubleshooting
1) El LED se ve “invertido” (encendido sin pulsar, apagado al pulsar)
– Causa: Inversión de lógica no acorde a activo-bajo.
– Solución: Verifica que led_n se asigne como led_n = ~btn_stable. Si tu LED estuviera cableado activo-alto (no es el caso típico en iCEBreaker), usa led = btn_stable y ajusta el pcf al nombre correcto de puerto.
2) No se enciende nunca el LED al pulsar
– Causas posibles:
– Cable USB o alimentación inadecuadas.
– Asignación de pines incorrecta en constraints/icebreaker.pcf.
– Botón no mapeado (nombre del port distinto en Verilog y PCF).
– DEBOUNCE_MS demasiado grande para tus pruebas (pulsas menos tiempo).
– Soluciones:
– Revisa la tabla de pines y que tus nombres coinciden: clk12, btn_n, led_n.
– Vuelve a sintetizar y programar.
– Prueba manteniendo 1–2 s el botón.
– Baja DEBOUNCE_MS a 5 ms para pruebas.
3) openFPGALoader no detecta la placa
– Causas:
– Falta de permisos (udev).
– Cable USB solo carga (sin datos) o dañado.
– Puerto USB en mal estado.
– Soluciones:
– Ejecuta con sudo: sudo openFPGALoader -b iCEBreaker build/top.bin
– Prueba otro puerto USB y/o otro cable.
– Verifica con openFPGALoader -c que aparece el FTDI.
4) nextpnr-ice40 falla por target/device
– Causa: Parámetros de dispositivo/paquete erróneos.
– Solución: Usa exactamente: –up5k –package sg48. Si usas otra variante de placa iCE40, adapta device y package, pero para iCEBreaker debes mantener estos.
5) Errores de nombres de puertos en Yosys/nextpnr
– Causa: Diferencia entre los nombres en top.v y en el .pcf.
– Solución: Asegúrate de que los puertos del módulo top son clk12, btn_n, led_n y que el .pcf usa esos mismos nombres.
6) El LED parpadea ligeramente al pulsar/soltar
– Causa: DEBOUNCE_MS demasiado bajo para el botón o jitter en la muestra.
– Solución: Aumenta DEBOUNCE_MS a 10–20 ms. Vuelve a sintetizar y cargar.
7) Violaciones de timing en nextpnr
– Causa: Configuración de –freq demasiado exigente o lógica adicional añadida.
– Solución: Para este diseño simple a 12 MHz no deberían aparecer. Verifica que usas –freq 12 y no has agregado lógica costosa en el camino del reloj.
8) Advertencias de pull-up o pines reservados
– Causa: Algunas herramientas emiten advertencias si el pin ya tiene pull-up externo o si hay restricciones de bancos.
– Solución: Las pull-up internas son “extra” y en general no molestan. Si la advertencia molesta, puedes quitar “-pullup yes” del botón en el .pcf (la placa ya tiene pull-up).
Mejoras/variantes
- Toggle por pulsación:
-
En lugar de reflejar el estado debounced, detecta el flanco de subida de btn_stable y conmuta un registro led_state. Asigna led_n = ~led_state. Así cada pulsación alterna encendido/apagado.
-
Indicador de “pulsación larga”:
-
Implementa un temporizador adicional: si btn_stable permanece 1 durante >1 s, cambia el modo del LED (por ejemplo, parpadeo lento con un divisor de reloj).
-
Multiplicidad de botones:
-
Instancia el antirrebotes por cada botón (reutiliza el módulo o vectoriza el filtro) y controla múltiples salidas (más LEDs, pines PMOD, etc.).
-
Debouncer por muestreo periódico:
-
En lugar de evaluar en cada ciclo a 12 MHz, crea un “tick” a 1 kHz y muestrea el botón N veces consecutivas iguales para confirmar el estado. Ahorra recursos si escalas a muchos botones.
-
PWM en el LED:
-
Aplica un modulador PWM simple para variar el brillo cuando el botón está pulsado, ilustrando conceptos de temporización.
-
Uso del oscilador interno:
-
iCE40UP5K dispone de SB_HFOSC (oscilador interno). Para proyectos sin dependencia del reloj externo, podrías activarlo y prescindir del pin de reloj. Para iCEBreaker usamos el de 12 MHz integrado, que es estable y simple.
-
Verificación formal:
- Con SymbiYosys puedes definir propiedades (por ejemplo: “btn_stable solo cambia si la entrada permanece distinta por N ciclos”) y comprobarlas, reforzando calidad del diseño.
Checklist de verificación
- [ ] Sistema operativo: GNU/Linux (Ubuntu 24.04 o similar).
- [ ] Toolchain instalada: OSS CAD Suite 2024-10-20 en PATH.
- [ ] Versiones comprobadas:
- [ ] yosys -V → 0.40
- [ ] nextpnr-ice40 –version → 0.7
- [ ] openFPGALoader –version → 0.13.0
- [ ] Estructura de proyecto creada: src/, constraints/, build/.
- [ ] Archivo constraints/icebreaker.pcf con pines:
- [ ] clk12 → 35
- [ ] btn_n → 10 (pull-up yes)
- [ ] led_n → 39
- [ ] Código Verilog en src/top.v con:
- [ ] Sincronizador de 2 etapas
- [ ] Antirrebotes con CNTR_MAX ≈ 120000 (10 ms a 12 MHz)
- [ ] led_n = ~btn_stable
- [ ] Síntesis (Yosys) ejecutada sin errores.
- [ ] Place & Route (nextpnr-ice40) sin errores, device up5k y package sg48.
- [ ] Empaquetado (icepack) generó build/top.bin.
- [ ] Programación con openFPGALoader -b iCEBreaker exitosa.
- [ ] Validación:
- [ ] LED apagado sin pulsar.
- [ ] LED encendido al pulsar, sin parpadeos.
- [ ] Al soltar, LED se apaga sin rebotes visibles.
- [ ] Ajuste de DEBOUNCE_MS probado (opcional), recompilación y reprogramación OK.
- [ ] Comprensión de al menos una mejora/variante (opcional).
Apéndice: Comandos agrupados (copiar/pegar)
1) Preparar build:
mkdir -p build
2) Síntesis:
yosys -p "read_verilog src/top.v; synth_ice40 -top top -json build/top.json"
3) Place & Route:
nextpnr-ice40 --up5k --package sg48 --json build/top.json --pcf constraints/icebreaker.pcf --asc build/top.asc --freq 12
4) Empaquetado:
icepack build/top.asc build/top.bin
5) Programación:
openFPGALoader -b iCEBreaker build/top.bin
Con esto, tienes un flujo completo y reproducible para el caso práctico “button-debouncing-and-led” en Lattice iCEBreaker (iCE40UP5K), usando la toolchain abierta yosys + nextpnr-ice40 + icestorm + openFPGALoader, con versiones concretas y comandos exactos.
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.



