Objetivo y caso de uso
Qué construirás: Controlar el brillo de los LED utilizando modulación por ancho de pulso (PWM) en la placa DE10-Lite FPGA.
Para qué sirve
- Regular el brillo de múltiples LED en un proyecto de iluminación.
- Implementar efectos visuales en un sistema de señalización LED.
- Controlar la intensidad de luz en aplicaciones de domótica.
Resultado esperado
- Brillo ajustable de LED con un rango de 0 a 100% mediante PWM.
- Latencia de respuesta de menos de 10 ms al cambiar el brillo.
- Consumo de energía optimizado, con mediciones de corriente en tiempo real.
Público objetivo: Estudiantes y entusiastas de la electrónica; Nivel: Básico
Arquitectura/flujo: Señales de reloj a 50 MHz, control de PWM en la FPGA DE10-Lite, salida a los pines de los LED.
Nivel: Básico
Prerrequisitos
Sistema operativo (probado)
- Windows 10/11 (64 bits) o
- Ubuntu 22.04 LTS (64 bits)
Toolchain exacta (Intel/Altera)
- Quartus Prime Lite Edition 23.1.0 Build 991 10/25/2023 SJ Lite Edition
- Incluye compilación (sintetizador, fitter, assembler) y programador (quartus_pgm)
- Controlador/firmware de programador: USB‑Blaster II (oficial de Intel/Altera)
- Opcional (solo si desea simular): Questa Intel FPGA Starter Edition 23.1.0
Notas de instalación:
– Windows: instalar “Quartus Prime Lite 23.1.0” y seleccionar “MAX 10 device support” durante la instalación.
– Linux (Ubuntu 22.04): tras instalar Quartus, configurar reglas udev para USB‑Blaster II; ver sección “Compilación/flash/ejecución”.
Materiales
- Placa FPGA: DE10‑Lite (Intel MAX 10, dispositivo 10M50DAF484C7G)
- Cable micro‑USB para alimentación y programación (USB‑Blaster II integrado en la placa)
- PC con los prerrequisitos instalados
- Opcional para validación avanzada: multímetro con función de duty o un osciloscopio
Elementos del propio kit DE10‑Lite que usaremos:
– Reloj a 50 MHz integrado (CLOCK_50)
– 10 interruptores deslizantes SW[9:0]
– 2 pulsadores KEY[1:0] (activos en bajo)
– 10 LEDs rojos LEDR[9:0]
No se requieren componentes externos: usaremos el LEDR[0] de la placa para visualizar el PWM de brillo.
Preparación y conexión
- Conectar la DE10‑Lite al PC con el cable micro‑USB al puerto USB‑Blaster II integrado (Power + JTAG por el mismo conector).
- Verificar que el LED de alimentación de la placa enciende.
- Asegurarse de que Windows reconoce “USB‑Blaster II” en el Administrador de dispositivos (drivers de Intel instalados). En Linux, más abajo se incluye la regla udev.
- Familiarizarse con los elementos de la placa que usaremos:
Tabla de puertos lógicos y elementos de la placa
| Puerto lógico (Top HDL) | Etiqueta en la placa | Descripción | Notas de uso |
|---|---|---|---|
| CLOCK_50 | CLOCK_50 | Reloj de 50 MHz integrado | Fuente de tiempo para el PWM |
| KEY[0] | KEY0 | Pulsador activo en bajo (Reset) | Lo interpretaremos como reset síncrono activo alto tras invertir la señal |
| KEY[1] | KEY1 | Pulsador activo en bajo (no usado en este caso) | Opcional para mejoras |
| SW[9:0] | SW9..SW0 | Interruptores deslizantes | Definen el “duty” del PWM (resolución base 10 bits; se escalan a 12 bits) |
| LEDR[0] | LEDR0 | LED rojo 0 | Ilumina con brillo proporcional al duty PWM |
| LEDR[9:1] | LEDR9..LEDR1 | LEDs rojos 9..1 | Los usaremos como monitor del valor de SW para depurar visualmente |
Importante:
– KEY es activo en bajo (presionado=0). En el HDL lo invertiremos para generar un reset activo alto.
– Los interruptores SW son activos en alto: ON=1, OFF=0.
Código completo
Objetivo: generar una señal PWM de ~12,2 kHz a partir del reloj de 50 MHz, con resolución de 12 bits (4096 niveles). Dado que la placa ofrece 10 switches, escalaremos los 10 bits de SW a 12 bits para el comparador PWM (desplazamiento a la izquierda de 2 bits).
Arquitectura:
– Módulo “pwm” genérico (ancho parametrizable).
– Módulo “de10lite_pwm_top” que conecta con los pines de la placa, escala el duty y publica el PWM en LEDR[0].
Módulo PWM (Verilog)
// rtl/pwm.v
// PWM genérico de tipo "counter < duty"
// - COUNTER_WIDTH define resolución (12 => 4096 pasos)
// - Frecuencia PWM = Fclk / 2^COUNTER_WIDTH
module pwm #(
parameter integer COUNTER_WIDTH = 12
)(
input wire clk,
input wire reset, // activo en alto
input wire [COUNTER_WIDTH-1:0] duty, // 0 .. 2^N-1
output reg pwm
);
reg [COUNTER_WIDTH-1:0] counter;
always @(posedge clk) begin
if (reset)
counter <= {COUNTER_WIDTH{1'b0}};
else
counter <= counter + 1'b1;
end
always @(posedge clk) begin
if (reset)
pwm <= 1'b0;
else
pwm <= (counter < duty);
end
endmodule
Claves:
– Con COUNTER_WIDTH=12 y Fclk=50 MHz, la frecuencia PWM es 50e6/4096 ≈ 12,207 kHz.
– duty=0 apaga el LED; duty=4095 lo enciende al 100%; valores intermedios ajustan el ciclo de trabajo.
Top‑level para DE10‑Lite (Verilog)
// rtl/de10lite_pwm_top.v
// Top coherente con puertos lógicos habituales en DE10-Lite:
// CLOCK_50, KEY[1:0], SW[9:0], LEDR[9:0]
// - KEY[0] es reset (activo en bajo en la placa; lo invertimos)
// - SW[9:0] se escala a 12 bits concatenando 2 ceros (<< 2)
// - LEDR[0] muestra el PWM, LEDR[9:1] reflejan SW para depurar
module de10lite_pwm_top (
input wire CLOCK_50, // 50 MHz
input wire [1:0] KEY, // activo en bajo
input wire [9:0] SW, // 10 interruptores
output wire [9:0] LEDR // 10 LEDs
);
// Invertimos KEY[0] para reset activo en alto
wire reset = ~KEY[0];
// Escalamos 10 bits de SW a 12 bits para el comparador PWM
// duty12 = {SW[9:0], 2'b00} => rango [0..4092] (pasos de 4)
wire [11:0] duty12 = {SW[9:0], 2'b00};
wire pwm_out;
pwm #(
.COUNTER_WIDTH(12)
) u_pwm (
.clk (CLOCK_50),
.reset (reset),
.duty (duty12),
.pwm (pwm_out)
);
// LEDR[0] muestra el PWM, los demás ayudan a ver el valor SW
assign LEDR[0] = pwm_out;
assign LEDR[9:1] = SW[9:1];
endmodule
Notas:
– Con SW=0, el LEDR[0] está apagado. Con SW=1023 (todos ON), el LEDR[0] luce casi al 100%.
– La frecuencia PWM (~12 kHz) evita parpadeo visible y ruidos audibles típicos por modulación.
Constraints (SDC) — reloj y márgenes básicos
# constraints/de10lite_pwm.sdc
# Define el reloj de 50 MHz de la DE10-Lite y pequeños delays de I/O
create_clock -name CLOCK_50 -period 20.000 [get_ports {CLOCK_50}]
# Márgenes simples de entrada/salida (opcional pero recomendable)
set_input_delay -clock CLOCK_50 2.0 [get_ports {SW[*]}]
set_input_delay -clock CLOCK_50 2.0 [get_ports {KEY[*]}]
set_output_delay -clock CLOCK_50 2.0 [get_ports {LEDR[*]}]
Archivo de proyecto/assignments (QSF) — base
Este QSF mínimo coloca la familia/dispositivo exactos de la placa y añade los archivos HDL y SDC. Los pines físicos los importaremos desde el QSF oficial de Terasic (ver siguiente sección), manteniendo coherencia con el modelo DE10‑Lite.
# de10lite_pwm.qsf (base, sin pines aún)
set_global_assignment -name FAMILY "MAX 10"
set_global_assignment -name DEVICE 10M50DAF484C7G
set_global_assignment -name TOP_LEVEL_ENTITY de10lite_pwm_top
# Archivos del proyecto
set_global_assignment -name SDC_FILE constraints/de10lite_pwm.sdc
set_global_assignment -name VERILOG_FILE rtl/pwm.v
set_global_assignment -name VERILOG_FILE rtl/de10lite_pwm_top.v
# Recomendable en placas: reservar pines no usados como entrada tri-state
set_global_assignment -name RESERVE_ALL_UNUSED_PINS "AS INPUT TRI-STATED"
Preparación del proyecto, compilación, programación y ejecución
A continuación se muestran pasos y comandos para reproducir el flujo completo con Quartus Prime Lite 23.1.0.
Estructura de carpetas sugerida
- de10lite_pwm/
- rtl/
- pwm.v
- de10lite_pwm_top.v
- constraints/
- de10lite_pwm.sdc
- project/
- de10lite_pwm.qsf (se generará/editará)
- scripts/
- output_files/ (Quartus la llenará con .sof, .rpt, etc.)
1) Crear el proyecto (CLI)
En Linux (bash) o Windows (PowerShell) con rutas equivalentes:
# Crear estructura
mkdir -p de10lite_pwm/{rtl,constraints,project,scripts}
cd de10lite_pwm
# Guardar los HDL y SDC en sus carpetas (copiar los contenidos de arriba)
# Ejemplo en Linux:
cat > rtl/pwm.v <<'EOF'
[PEGAR AQUÍ EL CONTENIDO DE rtl/pwm.v]
EOF
cat > rtl/de10lite_pwm_top.v <<'EOF'
[PEGAR AQUÍ EL CONTENIDO DE rtl/de10lite_pwm_top.v]
EOF
cat > constraints/de10lite_pwm.sdc <<'EOF'
[PEGAR AQUÍ EL CONTENIDO DE constraints/de10lite_pwm.sdc]
EOF
# Crear un QSF base (sin pines) en project/
cat > project/de10lite_pwm.qsf <<'EOF'
[PEGAR AQUÍ EL CONTENIDO DE de10lite_pwm.qsf]
EOF
Alternativa con script Tcl (opcional): si prefiere que Quartus genere el .qpf/.qsf automáticamente, puede usar:
# scripts/create_project.tcl
# Script para Quartus Prime Lite 23.1.0
project_new de10lite_pwm -overwrite -revision de10lite_pwm -family "MAX 10" -device 10M50DAF484C7G
set_global_assignment -name TOP_LEVEL_ENTITY de10lite_pwm_top
set_global_assignment -name SDC_FILE constraints/de10lite_pwm.sdc
set_global_assignment -name VERILOG_FILE rtl/pwm.v
set_global_assignment -name VERILOG_FILE rtl/de10lite_pwm_top.v
set_global_assignment -name RESERVE_ALL_UNUSED_PINS "AS INPUT TRI-STATED"
project_close
Ejecutarlo con:
quartus_sh -t scripts/create_project.tcl
Esto generará de10lite_pwm.qpf y de10lite_pwm.qsf. Si usa este método, copie luego el SDC y HDL en las rutas adecuadas (o modifique el script para crearlos).
2) Importar asignaciones de pines de la DE10‑Lite
Los pines físicos varían entre placas; para la DE10‑Lite use el archivo de asignaciones oficial de Terasic “DE10_Lite_golden_top.qsf”, que contiene las ubicaciones correctas para CLOCK_50, LEDR, SW, KEY, etc. Haremos dos variantes: GUI y CLI.
- Variante GUI (simple y robusta):
- Abra Quartus Prime Lite 23.1.0.
- File -> Open Project -> seleccione de10lite_pwm/de10lite_pwm.qpf.
- Assignments -> Import Assignments…
- Seleccione el archivo “DE10_Lite_golden_top.qsf” provisto por Terasic (viene en el System CD del kit). Habitualmente se encuentra en el paquete de referencia de la placa: “DE10_Lite_golden_top/DE10_Lite_golden_top.qsf”.
-
Acepte la importación. Verifique en Assignments -> Pin Planner que los puertos:
- CLOCK_50, KEY[1:0], SW[9:0], LEDR[9:0]
queden con pins asignados. No cambie los nombres de los puertos en el HDL.
- CLOCK_50, KEY[1:0], SW[9:0], LEDR[9:0]
-
Variante CLI (fusionando QSF):
- Copie el golden QSF a la carpeta project/ y extraiga solo las líneas de pines/instancias, para apénderselas al QSF del proyecto:
«`sh
# Asumiendo que tiene el archivo de Terasic en:
# thirdparty/DE10_Lite_golden_top/DE10_Lite_golden_top.qsf
mkdir -p thirdparty
# Copie/coloque el QSF oficial en thirdparty/ antes de ejecutar lo siguiente:
# Extraer asignaciones relevantes y añadirlas al QSF del proyecto:
grep -E «set_location_assignment|set_instance_assignment» thirdparty/DE10_Lite_golden_top.qsf >> project/de10lite_pwm.qsf
«`
– Esto mantiene el dispositivo y top propios, y añade solo los pines físicos oficiales de la placa, coherentes con los nombres CLOCK_50, KEY, SW, LEDR ya usados en el HDL.
Sugerencia: abra luego el Pin Planner y verifique visualmente que:
– CLOCK_50 tiene una sola ubicación asignada para “CLOCK_50”.
– LEDR[0]..LEDR[9], SW[0]..SW[9], KEY[0]..KEY[1] están mapeados.
3) Compilar el proyecto (sintetizar, place&route, ensamblar)
Con el proyecto en la carpeta raíz de de10lite_pwm (donde está el .qpf):
# Sitúese en la carpeta del proyecto
cd de10lite_pwm
# Compilación completa (synth + fit + asm)
quartus_sh --flow compile de10lite_pwm
# Si todo va bien, el bitstream/sram object file se generará en:
# output_files/de10lite_pwm.sof
Archivos de interés:
– output_files/de10lite_pwm.sof (para programar SRAM de la FPGA)
– output_files/de10lite_pwm.fit.rpt y .sta.rpt (informes de ajuste y temporización)
4) Programar la DE10‑Lite (USB‑Blaster II)
- Linux: preparar udev para acceso sin root (una vez):
# Regla típica para USB-Blaster II
sudo bash -c 'cat >/etc/udev/rules.d/51-usbblaster.rules <<EOF
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6010", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6810", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6811", MODE="0666"
EOF'
sudo udevadm control --reload-rules
sudo udevadm trigger
# Desconecte y reconecte la DE10-Lite
- Programación (Windows o Linux):
# Enumerar programadores detectados
quartus_pgm -l
# Salida esperada (ejemplo):
# 1) USB-BlasterII [USB-1]
# Programar el .sof en la FPGA (usando el primer cable detectado)
quartus_pgm -m jtag -o "p;output_files/de10lite_pwm.sof@1"
# Si tiene varios programadores, especifique el cable:
# quartus_pgm -c "USB-BlasterII [USB-1]" -m jtag -o "p;output_files/de10lite_pwm.sof@1"
La FPGA se configurará en SRAM (no es persistente tras power‑off). Para hacerlo persistente en la MAX 10 (con configuración interna), se emplearía .pof y memoria interna; para este caso básico, el .sof es suficiente.
Validación paso a paso
Objetivo: comprobar que el brillo de LEDR[0] varía con el valor binario de SW[9:0] y que KEY0 provoca reset.
- Verificación rápida al programar:
- LEDR[9:1] deben reflejar el estado de SW[9:1]. Si SW9=1, debería encender LEDR9; etc. Esto confirma que los pines y el top concuerdan con la placa.
- Control de brillo:
- Ponga todos los SW en OFF (0). LEDR[0] debe permanecer apagado (duty=0).
- Ponga solo SW0 en ON (1). LEDR[0] debe encender muy tenue (duty=4/4096 ≈ 0,1%).
- Active más bits de SW (por ejemplo SW3..SW0 = 1111 → valor 15). LEDR[0] debe verse más brillante.
- Active todos los SW (SW[9:0]=1111111111 → 1023). LEDR[0] debe verse prácticamente al máximo (duty≈1023<<2=4092).
- Reset:
- Mantenga algunos SW en ON (p. ej., 0101010101). Observe LEDR[0].
- Presione KEY0 (activo en bajo): mientras mantiene presionado, el reset se activa y el LEDR[0] debe ir a 0 (apagado). Al soltar, vuelve a funcionar el PWM.
- Frecuencia PWM (validación cualitativa):
- No debería apreciarse parpadeo a simple vista (12 kHz).
- Si apunta una cámara de smartphone en modo video, puede aparecer un patrón sutil a shutter alto; es normal a altas frecuencias.
- Medida opcional (osciloscopio):
- Si dispone de header accesible al LEDR0 o un punto de prueba, mida la señal y confirme:
- Frecuencia ~12,2 kHz (periodo ≈ 81,92 μs) midiendo periodos entre flancos.
- Ciclo de trabajo proporcional al valor de SW (duty ≈ SW*4/4096).
- Alternativa: algunos multímetros permiten medir duty en señales digitales.
Resultados esperados:
– Relación monotónica entre el número binario en SW y el brillo de LEDR[0].
– Sin flicker visible.
– KEY0 fuerza apagado temporal (reset).
Troubleshooting (errores típicos y resolución)
- No aparece el “USB‑Blaster II” al programar
- Causa: drivers no instalados (Windows) o falta de regla udev (Linux).
-
Solución: instalar el driver de Intel (Windows) o aplicar la regla udev mostrada; reconectar la placa; ejecutar “quartus_pgm -l” de nuevo.
-
La compilación falla por falta de asignaciones de pines
- Causa: el QSF no contiene ubicaciones físicas (PIN_…) para CLOCK_50, LEDR, SW, KEY.
-
Solución: importar el archivo “DE10_Lite_golden_top.qsf” mediante Assignments -> Import Assignments, o fusionarlo por CLI como se indicó. Verificar en Pin Planner que los puertos tienen pin asignado correcto.
-
LEDR[0] no cambia de brillo (permanece fijo ON u OFF)
- Causa: pin de LEDR[0] no mapeado o nombre de puerto diferente en el HDL versus QSF.
-
Solución: mantener exactamente los nombres de puertos “LEDR”, “SW”, “KEY”, “CLOCK_50” en el top. Revisar en el Pin Planner que “LEDR[0]” está asignado.
-
El reset con KEY0 no funciona
- Causa: no se invirtió la polaridad (KEY es activo en bajo).
-
Solución: confirmar que la línea “wire reset = ~KEY[0];” existe y que KEY[0] está asignado al pin correcto.
-
Parpadeo visible o brillo no lineal
- Causa: frecuencia PWM insuficiente o percepción visual no lineal.
-
Solución: suba COUNTER_WIDTH a 13 (frecuencia ≈ 6,1 kHz) o bájelo a 11 (≈ 24,4 kHz) si desea aún menos flicker. Para linealidad visual, aplique corrección gamma (ver mejoras).
-
Error de temporización en STA (timing fail)
- Causa: restricciones temporales incompletas o QSF conflictivo.
-
Solución: ver el SDC proporcionado; agregue “create_clock” correcto; revise que el dispositivo sea 10M50DAF484C7G; limpie el proyecto y recompílelo.
-
Quartus no encuentra el archivo .sof al programar
- Causa: no se compiló o se está en otra carpeta.
-
Solución: verificar que “output_files/de10lite_pwm.sof” existe tras “quartus_sh –flow compile de10lite_pwm”; ejecutar programación desde la raíz del proyecto o dar la ruta completa.
-
LEDR[9:1] no reflejan SW[9:1]
- Causa: error de asignación de pines o mezcla de nombres (LEDR/LED).
- Solución: abra el Pin Planner y confirme nombre de buses exactamente como “LEDR[9..0]” y “SW[9..0]”; reimporte el QSF oficial.
Mejoras/variantes
- Multiplicar salidas PWM:
- Instanciar varios módulos “pwm” para LEDR[0..3], cada uno con su “duty” tomado de distintos grupos de SW o con un mismo duty para encender varios LEDs con idéntico brillo.
- Corrección gamma (percepción visual linealizada):
- La relación percepción‑luz no es lineal; aplicar una tabla LUT para mapear el valor de SW a un duty gamma‑corregido mejora la uniformidad de pasos visuales. Ej.: duty = floor((SW/1023)^γ * 4095) con γ≈2.2.
- Fade automático (breathing):
- Sustituir el “duty12” por un acumulador que sube y baja en el tiempo para generar un efecto respiración; útil para comprender contadores y comparadores.
- Ajuste de frecuencia PWM:
- COUNTER_WIDTH=10 → 48,8 kHz (reduce interferencias visuales, buen margen EMC).
- COUNTER_WIDTH=16 → 763 Hz (en algunos entornos puede notarse flicker; no recomendable).
- Entrada por pulsadores:
- Usar KEY1 para incrementar duty en pequeños pasos, con un pequeño filtro antirrebote, evitando usar SW. Permite una UX más “analógica”.
- Medición más precisa:
- Usar el monitor de señales de Quartus (SignalTap II) para observar el valor del contador y duty en tiempo real.
Checklist de verificación
Marque cada punto al completarlo:
- [ ] Instalé Quartus Prime Lite 23.1.0 (MAX 10 soportado) y el driver USB‑Blaster II.
- [ ] Conecté DE10‑Lite por micro‑USB y el programador aparece en “quartus_pgm -l”.
- [ ] Creé la estructura del proyecto y copié los archivos Verilog y SDC.
- [ ] Configuré el proyecto para dispositivo 10M50DAF484C7G y top “de10lite_pwm_top”.
- [ ] Importé las asignaciones de pines desde “DE10_Lite_golden_top.qsf” (GUI o CLI).
- [ ] Compilé con “quartus_sh –flow compile de10lite_pwm” sin errores críticos.
- [ ] Programé con “quartus_pgm -m jtag -o ‘p;output_files/de10lite_pwm.sof@1’”.
- [ ] Verifiqué que LEDR[9:1] siguen a SW[9:1].
- [ ] Observé el brillo de LEDR[0] variar con SW[9:0].
- [ ] KEY0 provoca apagado temporal (reset) del PWM.
- [ ] Opcional: medí en un instrumento ~12,2 kHz de frecuencia PWM.
Resumen técnico (coherencia con el objetivo)
- Dispositivo exacto: DE10‑Lite (Intel MAX 10 10M50DAF484C7G).
- Toolchain exacta: Quartus Prime Lite Edition 23.1.0 Build 991.
- Reloj: 50 MHz, SDC con create_clock 20.000 ns.
- HDL: Verilog conciso; módulo PWM N‑bit con comparador.
- Mapeo: LEDR[0] recibe pwm_out; SW[9:0] determinan duty (escalado a 12 bits); KEY0 como reset.
- Flujo reproducible: comandos CLI para compilar y programar; importación de pines desde QSF oficial de Terasic.
- Validación: comportamiento visible del brillo y respuesta al reset.
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.



