Construcción de un medidor de RPM con codificador de cuadratura usando Lattice ECP5
Objetivo y caso de uso
Qué construirás: Un medidor digital de Revoluciones Por Minuto (RPM) que lee un codificador mecánico de cuadratura utilizando una FPGA Lattice ECP5, calculando la velocidad de rotación y mostrándola en tiempo real con una latencia inferior a un milisegundo en una barra de LED externa.
Por qué es importante / Casos de uso
- Monitorización de cintas transportadoras de baja velocidad: Proporciona verificación independiente de las velocidades de la cinta en la línea de montaje con una latencia <1ms, actuando como un monitor fiable de ejes de baja velocidad.
- Pruebas de manivelas/cabrestantes manuales: Actúa como una ayuda de medición para asegurar que los operadores no excedan las velocidades seguras de enrollado manual (ej. 0-120 RPM) en polipastos mecánicos o tensores de laboratorio.
- Ajuste de motores paso a paso: Ofrece verificación física e independiente de la velocidad de rotación real durante el ajuste de baja frecuencia, omitiendo las estimaciones de software del microcontrolador.
- Procesamiento de señales por hardware: Demuestra conceptos cruciales de FPGA, incluyendo la sincronización de registros de desplazamiento, la mitigación de rebotes de interruptores mecánicos (filtro de ~5ms) y la medición de frecuencia en ventanas de tiempo fijas.
Resultado esperado
- Un módulo Verilog robusto que muestrea continuamente los canales A y B del codificador KY-040 a 50MHz sin metaestabilidad.
- Una máquina de estados digital que filtra el rebote mecánico y decodifica con precisión la dirección de los pasos de cuadratura.
- Un cálculo de RPM en tiempo real y una tubería (pipeline) de visualización en barra de LED que utiliza <1% de los recursos lógicos de la FPGA ECP5 (LUTs).
Audiencia: Ingenieros de hardware integrado y desarrolladores de FPGA; Nivel: Intermedio
Arquitectura/flujo: Codificador KY-040 → Sincronizador de Flip-Flop de 2 etapas → Filtro antirrebote → Decodificador de cuadratura → Contador de RPM de ventana fija → Controlador de visualización de barra de LED
Nota educativa de validación
Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. Para este perfil FPGA/ULX3S, los bloques de Verilog sintetizable se comprobaron con Yosys (read_verilog) y el conjunto Verilog de diseño/test se revisó con Verilator. El validador también comprobó la estructura de los bloques de código, que los comandos usen opciones copiables con guiones ASCII, que no aparezcan stacks no soportados y que esté disponible la toolchain ULX3S/ECP5 (yosys, nextpnr-ecp5, ecppack, openFPGALoader).
Evidencia de validación publicada
- Resultado automático: PASS.
- Estructura parseada: 3 apartados, 1 tablas y 3 bloques de código detectados antes de publicar.
- Código comprobado: 2 Verilog/Yosys-Verilator.
- Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y los stacks no soportados bloquean la publicación.
- Hallazgos del informe: sin hallazgos bloqueantes.
Esta validación confirma compatibilidad sintáctica y de herramientas para el código publicado, pero no sustituye la prueba física sobre tu revisión exacta de ULX3S, tu archivo de restricciones de pines y tu cableado real.
Nota educativa de seguridad
Este proyecto es un prototipo educativo, no un producto certificado. Antes de encender la configuración, verifica la disposición de pines de tu revisión exacta de la placa ULX3S, mantén las señales de E/S de la FPGA a 3.3 V, nunca conectes 5 V directamente a los pines de E/S, desconecta la alimentación antes de cambiar el cableado y utiliza fuentes externas adecuadas para cargas, motores o servos, compartiendo la tierra solo cuando el cableado lo requiera.
Diagrama de bloques conceptual
Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.
Arquitectura funcional
Flujo conceptual de señales y responsabilidades entre bloques del dispositivo.
Ruta de validación
Resumen conceptual de las herramientas usadas para comprobar el material publicado.
Configuración de hardware
Este tutorial está dirigido a la Radiona ULX3S con una FPGA Lattice ECP5-85F.
| Nombre de señal | Pin ULX3S | Estándar de E/S | Conexión de hardware |
|---|---|---|---|
clk_25mhz | G2 | LVCMOS33 | Oscilador en placa de 25MHz |
enc_a | B11 | LVCMOS33 (Pull-up) | Pin CLK de KY-040 (GPIO gp[0]) |
enc_b | C11 | LVCMOS33 (Pull-up) | Pin DT de KY-040 (GPIO gp[1]) |
led[0] | H3 | LVCMOS33 | LED 0 en placa |
led[1] | E1 | LVCMOS33 | LED 1 en placa |
led[2] | E2 | LVCMOS33 | LED 2 en placa |
led[3] | D1 | LVCMOS33 | LED 3 en placa |
led[4] | D2 | LVCMOS33 | LED 4 en placa |
led[5] | C1 | LVCMOS33 | LED 5 en placa |
led[6] | C2 | LVCMOS33 | LED 6 en placa |
led[7] | B2 | LVCMOS33 | LED 7 en placa |
Implementación en Verilog
El diseño utiliza una ventana de tiempo de 1 segundo generada a partir del reloj del sistema de 25MHz. Sincroniza las entradas asíncronas del codificador, mitiga los rebotes y cuenta los pulsos. Un codificador KY-040 estándar genera 20 pulsos por revolución.
Código fuente sintetizable
Guarda el siguiente código como top.v.
Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.
/* top.v */
module top #(
parameter CLK_FREQ = 25000000,
parameter DEBOUNCE_CYCLES = 50000
) (
input wire clk_25mhz,
input wire enc_a,
input wire enc_b,
output wire [7:0] led
);
// Synchronization registers to prevent metastability
reg [2:0] sync_a;
always @(posedge clk_25mhz) begin
sync_a <= {sync_a[1:0], enc_a};
end
// Debounce logic for Channel A
reg clean_a;
reg [15:0] debounce_cnt_a;
initial begin
clean_a = 1'b1;
debounce_cnt_a = 16'd0;
end
always @(posedge clk_25mhz) begin
if (sync_a[2] == clean_a) begin
debounce_cnt_a <= 16'd0;
end else begin
debounce_cnt_a <= debounce_cnt_a + 16'd1;
if (debounce_cnt_a == DEBOUNCE_CYCLES) begin
clean_a <= sync_a[2];
debounce_cnt_a <= 16'd0;
end
end
end
// Edge detection on debounced Channel A
reg clean_a_prev;
initial clean_a_prev = 1'b1;
always @(posedge clk_25mhz) begin
clean_a_prev <= clean_a;
end
// .../* top.v */
module top #(
parameter CLK_FREQ = 25000000,
parameter DEBOUNCE_CYCLES = 50000
) (
input wire clk_25mhz,
input wire enc_a,
input wire enc_b,
output wire [7:0] led
);
// Synchronization registers to prevent metastability
reg [2:0] sync_a;
always @(posedge clk_25mhz) begin
sync_a <= {sync_a[1:0], enc_a};
end
// Debounce logic for Channel A
reg clean_a;
reg [15:0] debounce_cnt_a;
initial begin
clean_a = 1'b1;
debounce_cnt_a = 16'd0;
end
always @(posedge clk_25mhz) begin
if (sync_a[2] == clean_a) begin
debounce_cnt_a <= 16'd0;
end else begin
debounce_cnt_a <= debounce_cnt_a + 16'd1;
if (debounce_cnt_a == DEBOUNCE_CYCLES) begin
clean_a <= sync_a[2];
debounce_cnt_a <= 16'd0;
end
end
end
// Edge detection on debounced Channel A
reg clean_a_prev;
initial clean_a_prev = 1'b1;
always @(posedge clk_25mhz) begin
clean_a_prev <= clean_a;
end
wire step_pulse = (clean_a == 1'b1 && clean_a_prev == 1'b0);
// 1-Second Timer and Pulse Counter
reg [24:0] timer;
reg [15:0] pulse_count;
reg [15:0] saved_pulses;
initial begin
timer = 25'd0;
pulse_count = 16'd0;
saved_pulses = 16'd0;
end
always @(posedge clk_25mhz) begin
if (timer == CLK_FREQ - 1) begin
timer <= 25'd0;
saved_pulses <= pulse_count;
pulse_count <= 16'd0;
end else begin
timer <= timer + 25'd1;
if (step_pulse) begin
pulse_count <= pulse_count + 16'd1;
end
end
end
// Calculate RPM: (Pulses per second * 60) / 20 pulses per revolution = Pulses * 3
wire [15:0] rpm = saved_pulses * 16'd3;
// LED Bar Graph Display (Thermometer Code)
reg [7:0] led_reg;
always @(*) begin
led_reg = 8'b00000000;
if (rpm >= 16'd15) led_reg[0] = 1'b1;
if (rpm >= 16'd30) led_reg[1] = 1'b1;
if (rpm >= 16'd45) led_reg[2] = 1'b1;
if (rpm >= 16'd60) led_reg[3] = 1'b1;
if (rpm >= 16'd75) led_reg[4] = 1'b1;
if (rpm >= 16'd90) led_reg[5] = 1'b1;
if (rpm >= 16'd105) led_reg[6] = 1'b1;
if (rpm >= 16'd120) led_reg[7] = 1'b1;
end
assign led = led_reg;
endmodule
Banco de pruebas
Guarda el siguiente código como tb_top.v. El banco de pruebas sobrescribe los parámetros del módulo para acortar drásticamente la ventana de conteo de 1 segundo a fin de obtener tiempos de simulación prácticos.
/* tb_top.v */
`timescale 1ns/1ps
module tb_top;
reg clk;
reg enc_a;
reg enc_b;
wire [7:0] led;
// Instantiate with simulation-friendly parameters
// CLK_FREQ = 2500 (100us window instead of 1s)
// DEBOUNCE_CYCLES = 5 (instead of 50000)
top #(
.CLK_FREQ(2500),
.DEBOUNCE_CYCLES(5)
) dut (
.clk_25mhz(clk),
.enc_a(enc_a),
.enc_b(enc_b),
.led(led)
);
initial begin
clk = 0;
forever #20 clk = ~clk; // 25MHz clock (40ns period)
end
initial begin
$dumpfile("tb_top.vcd");
$dumpvars(0, tb_top);
enc_a = 1;
enc_b = 1;
#1000;
// Simulate multiple encoder steps to register an RPM
repeat (10) begin
enc_a = 0; #400; // Exceeds debounce (5 * 40ns = 200ns)
enc_b = 0; #400;
enc_a = 1; #400;
enc_b = 1; #400;
end
// Wait for the timer window to expire (100us = 100,000ns)
#150000;
$finish;
end
endmodule
Restricciones de pines
Guarda el siguiente código como ulx3s.lpf.
# ulx3s.lpf
LOCATE COMP "clk_25mhz" SITE "G2";
IOBUF PORT "clk_25mhz" PULLMODE=NONE IO_TYPE=LVCMOS
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.




