Caso práctico: medidor de RPM con encoder en ULX3S

Medidor de RPM con encoder en ULX3S

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

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

Flujo conceptual de señales y responsabilidades entre bloques del dispositivo.

Ruta de validación

Código fuente

Verilator

Yosys

Implementación hardware

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ñalPin ULX3SEstándar de E/SConexión de hardware
clk_25mhzG2LVCMOS33Oscilador en placa de 25MHz
enc_aB11LVCMOS33 (Pull-up)Pin CLK de KY-040 (GPIO gp[0])
enc_bC11LVCMOS33 (Pull-up)Pin DT de KY-040 (GPIO gp[1])
led[0]H3LVCMOS33LED 0 en placa
led[1]E1LVCMOS33LED 1 en placa
led[2]E2LVCMOS33LED 2 en placa
led[3]D1LVCMOS33LED 3 en placa
led[4]D2LVCMOS33LED 4 en placa
led[5]C1LVCMOS33LED 5 en placa
led[6]C2LVCMOS33LED 6 en placa
led[7]B2LVCMOS33LED 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
// ...

🔒 Este contenido es premium. Con la suscripción mensual (7 días gratis) podrás desbloquear el material didáctico completo y el pack PDF listo para imprimir.🔓 Desbloquéalo — 7 días gratis
/* 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

Ir a 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.

Quiz rápido

Pregunta 1: ¿Cuál es el objetivo principal del proyecto descrito en el texto?




Pregunta 2: ¿Qué tipo de codificador se utiliza en este proyecto para medir las revoluciones?




Pregunta 3: ¿Cuál es la latencia máxima esperada para mostrar la velocidad de rotación?




Pregunta 4: ¿Dónde se muestra la velocidad de rotación calculada en tiempo real?




Pregunta 5: ¿Cuál es uno de los casos de uso mencionados para este medidor de RPM?




Pregunta 6: En el contexto de pruebas de manivelas o cabrestantes manuales, ¿cuál es el rango de velocidad segura mencionado como ejemplo?




Pregunta 7: ¿Qué ventaja ofrece este sistema al ajustar motores paso a paso?




Pregunta 8: ¿Qué estimaciones permite omitir este medidor al ajustar motores paso a paso?




Pregunta 9: ¿Qué tipo de procesamiento se destaca como importante en el último punto del texto?




Pregunta 10: ¿Qué componente principal actúa como el 'cerebro' para calcular la velocidad en este proyecto?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
Scroll al inicio