You dont have javascript enabled! Please enable it!

Caso práctico: monitor GPS NMEA con ULX3S

Caso práctico: monitor GPS NMEA con ULX3S — hero

Objetivo y caso de uso

Qué construirás: Un monitor GPS práctico basado en FPGA usando la Radiona ULX3S (Lattice ECP5-85F), un módulo GPS u-blox NEO-6M y cableado UART de 3.3 V. Recibirá datos NMEA a 9600 baudios, analizará sentencias de tiempo y posición con latencia de actualización inferior a un segundo, y mostrará actividad UART, estado de fix y cambios clave de estado en los LED de la ULX3S.

Por qué importa / Casos de uso

  • Verificación en banco del módulo GPS: Confirma rápidamente que un NEO-6M está alimentado, transmite sentencias NMEA válidas y responde a 9600 baudios sin abrir un terminal serie en una PC.
  • Diagnóstico portátil de instalación: Usa alimentación USB para comprobar el progreso del fix, el tráfico UART en vivo y las coordenadas cambiantes en campo antes de conectar el sistema anfitrión final; la actualización visible típica de estado es de 1 Hz en línea con la salida NMEA común.
  • Formación en diseño digital: Demuestra el manejo real en FPGA de recepción UART asíncrona, análisis de flujo ASCII y validación de sentencias en lugar de una simple demostración de loopback.
  • Prototipo de monitor serie autónomo: Crea un gps-nmea-position-time-monitor compacto para puesta en marcha de temporización, seguimiento y navegación con una carga de FPGA muy baja, típicamente muy por debajo del 5% de lógica y efectivamente 0% de uso de GPU.

Resultado esperado

  • Un diseño funcional para ULX3S que reciba de forma confiable datos NMEA UART de 3.3 V desde el NEO-6M a 9600 baudios.
  • Tiempo UTC analizado y campos básicos de posición a partir de sentencias comunes como GPRMC o GPGGA, con respuesta visible en LED dentro de un período de sentencia.
  • Indicación de estado para ausencia de datos, tráfico serie activo, recepción de sentencias y presencia de fix GPS, útil para pruebas rápidas en banco.
  • Una referencia reutilizable en FPGA para cargas de trabajo de análisis serie de bajo ancho de banda donde el rendimiento es mínimo pero el comportamiento determinista del hardware importa.

Audiencia: Aprendices de FPGA, desarrolladores embebidos y técnicos que validan hardware GPS; Nivel: Principiante a intermedio

Arquitectura/flujo: El NEO-6M entrega NMEA por UART de 3.3 V → el receptor UART de la ULX3S muestrea bytes serie con lógica temporizada por bit → el analizador extrae los campos de tiempo, fix y coordenadas de sentencias ASCII → la lógica de estado actualiza los LED a una cadencia de sentencias de aproximadamente 1 Hz con latencia interna de procesamiento del orden de milisegundos.

Diagrama de bloques conceptual

Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.

Arquitectura funcional

El NEO-6M entrega NMEA por UART de 3.3 V

el receptor UART de la ULX3S muestrea byt…

el analizador extrae los campos de tiempo…

la lógica de estado actualiza los LED a u…

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.

Requisitos previos

Antes de comenzar, deberías sentirte cómodo con:

  • Flujo de trabajo básico de FPGA desde la línea de comandos
  • Módulos Verilog simples y diseño síncrono
  • Conceptos de UART:
  • tasa de baudios
  • bit de inicio
  • bit de parada
  • formato 8N1
  • Edición de archivos de texto y ejecución de comandos de shell en Linux

Entorno anfitrión recomendado:

  • PC o portátil con Linux
  • Cable USB para programación/alimentación de la ULX3S
  • Adaptador USB-UART opcional si deseas inspeccionar la salida del GPS de forma independiente antes de conectarlo a la FPGA

Herramientas de software requeridas:

  • yosys
  • nextpnr-ecp5
  • ecppack
  • openFPGALoader
  • verilator

Materiales

Usa exactamente estos elementos de hardware:

ElementoModelo exactoPropósito
Placa FPGARadiona ULX3S (Lattice ECP5-85F)Plataforma FPGA principal
Módulo GPSu-blox NEO-6M GPS moduleFuente de datos NMEA por UART
Nivel de voltaje serie3.3 V UART wiringConexión directa segura a nivel lógico
Cable USBMicro-USB o USB-C según la revisión de ULX3SAlimentación y programación
Cables jumperHembra-hembra o mixtos según sea necesarioConexiones entre ULX3S y NEO-6M
ComputadoraHost LinuxCompilación, programación y comprobaciones serie opcionales

Nota importante específica del modelo

Muchas placas breakout NEO-6M se alimentan con 5 V pero aun así exponen una TX de nivel lógico de 3.3 V. Debes verificar tu módulo específico. Este tutorial asume:

  • El VCC del módulo GPS se alimenta de acuerdo con el requisito de la placa breakout
  • La salida TX del GPS presentada a la FPGA es compatible con 3.3 V
  • La conexión UART directa se realiza solo mediante cableado UART de 3.3 V

Configuración/Conexión

Aquí no se usa un diagrama del circuito; sigue el texto exactamente.

Plan de señales

Este proyecto necesita solo tres conexiones eléctricas esenciales:

  1. Tierra común
  2. GPS TX -> entrada FPGA de la ULX3S
  3. Alimentación para el módulo GPS

Esquema práctico de conexión recomendado

  • Conecta NEO-6M GND a ULX3S GND
  • Conecta NEO-6M TX a un pin GPIO de entrada elegido en la ULX3S
  • Alimenta el módulo GPS desde una fuente adecuada:
  • Si tu breakout NEO-6M acepta 5 V en VCC, puedes alimentarlo desde una fuente segura de 5 V, asegurando aun así que la TX vista por la FPGA sea lógica de 3.3 V
  • Si tu breakout requiere 3.3 V en VCC, aliméntalo desde una línea regulada de 3.3 V
  • No conectes GPS RX a menos que específicamente quieras enviar comandos de configuración más adelante; no es necesario para este monitor

Selección de pines usada en este tutorial

Para mantener el diseño concreto, el nivel superior de la FPGA usa:

  • clk_25mhz como reloj del sistema
  • gps_rx_i como entrada UART desde el módulo GPS
  • led[7:0] como indicadores de salida

Para la ULX3S, los nombres reales de pines del encapsulado varían según el conjunto de restricciones de la placa. El flujo de trabajo más seguro es:

  1. Empezar desde la plantilla de restricciones conocida y funcional de tu placa ULX3S
  2. Reemplazar solo las señales usadas aquí
  3. Mantener el oscilador y los pines LED coincidiendo con la revisión de tu placa

En el ejemplo validado a continuación, se proporciona un archivo de restricciones en el estilo esperado por nextpnr-ecp5. Si tu revisión exacta de ULX3S tiene alias diferentes, ajusta solo los nombres de pin LOCATE COMP usando el pinout oficial de la ULX3S.

Significado de los LED usado por este proyecto

  • led[0]: latido, demuestra que la FPGA está funcionando
  • led[1]: pulso de actividad de caracteres UART
  • led[2]: línea NMEA válida completada
  • led[3]: sentencia RMC válida detectada
  • led[4]: estado RMC = A (fix activo)
  • led[5]: conmuta cuando se actualiza el campo de tiempo
  • led[6]: conmuta cuando se actualiza el campo de latitud
  • led[7]: conmuta cuando se actualiza el campo de longitud

Esto proporciona evidencia útil en campo sin necesidad de una pantalla.

Código validado

gps_uart_rx.v

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

module gps_uart_rx #(
    parameter integer CLK_HZ = 25000000,
    parameter integer BAUD   = 9600
) (
    input  wire clk,
    input  wire rst,
    input  wire rx,
    output reg  data_valid,
    output reg [7:0] data_byte
);

    localparam integer CLKS_PER_BIT = CLK_HZ / BAUD;
    localparam integer HALF_BIT     = CLKS_PER_BIT / 2;

    reg [15:0] clk_count = 0;
    reg [3:0]  bit_index = 0;
    reg [7:0]  rx_shift  = 8'h00;
    reg [2:0]  state     = 0;
    reg        rx_meta   = 1'b1;
    reg        rx_sync   = 1'b1;

    localparam S_IDLE  = 3'd0;
    localparam S_START = 3'd1;
    localparam S_DATA  = 3'd2;
    localparam S_STOP  = 3'd3;

    always @(posedge clk) begin
        rx_meta <= rx;
        rx_sync <= rx_meta;
    end

    always @(posedge clk) begin
        if (rst) begin
            state      <= S_IDLE;
            clk_count  <= 0;
            bit_index  <= 0;
            rx_shift   <= 8'h00;
            data_byte  <= 8'h00;
            data_valid <= 1'b0;
        end else begin
            data_valid <= 1'b0;

            case (state)
                S_IDLE: begin
                    clk_count <= 0;
                    bit_index <= 0;
                    if (rx_sync == 1'b0) begin
                        state <= S_START;
                    end
                end
// ... continúa para miembros en el código completo validado ...

🔒 Parte del código validado es premium. Con el pase de 7 días o la suscripción mensual podrás consultar el archivo completo validado.

module gps_uart_rx #(
    parameter integer CLK_HZ = 25000000,
    parameter integer BAUD   = 9600
) (
    input  wire clk,
    input  wire rst,
    input  wire rx,
    output reg  data_valid,
    output reg [7:0] data_byte
);

    localparam integer CLKS_PER_BIT = CLK_HZ / BAUD;
    localparam integer HALF_BIT     = CLKS_PER_BIT / 2;

    reg [15:0] clk_count = 0;
    reg [3:0]  bit_index = 0;
    reg [7:0]  rx_shift  = 8'h00;
    reg [2:0]  state     = 0;
    reg        rx_meta   = 1'b1;
    reg        rx_sync   = 1'b1;

    localparam S_IDLE  = 3'd0;
    localparam S_START = 3'd1;
    localparam S_DATA  = 3'd2;
    localparam S_STOP  = 3'd3;

    always @(posedge clk) begin
        rx_meta <= rx;
        rx_sync <= rx_meta;
    end

    always @(posedge clk) begin
        if (rst) begin
            state      <= S_IDLE;
            clk_count  <= 0;
            bit_index  <= 0;
            rx_shift   <= 8'h00;
            data_byte  <= 8'h00;
            data_valid <= 1'b0;
        end else begin
            data_valid <= 1'b0;

            case (state)
                S_IDLE: begin
                    clk_count <= 0;
                    bit_index <= 0;
                    if (rx_sync == 1'b0) begin
                        state <= S_START;
                    end
                end

                S_START: begin
                    if (clk_count == HALF_BIT) begin
                        if (rx_sync == 1'b0) begin
                            clk_count <= 0;
                            state <= S_DATA;
                        end else begin
                            state <= S_IDLE;
                        end
                    end else begin
                        clk_count <= clk_count + 16'd1;
                    end
                end

                S_DATA: begin
                    if (clk_count == CLKS_PER_BIT - 1) begin
                        clk_count <= 0;
                        rx_shift[bit_index] <= rx_sync;
                        if (bit_index == 4'd7) begin
                            bit_index <= 0;
                            state <= S_STOP;
                        end else begin
                            bit_index <= bit_index + 4'd1;
                        end
                    end else begin
                        clk_count <= clk_count + 16'd1;
                    end
                end

                S_STOP: begin
                    if (clk_count == CLKS_PER_BIT - 1) begin
                        clk_count <= 0;
                        if (rx_sync == 1'b1) begin
                            data_byte <= rx_shift;
                            data_valid <= 1'b1;
                        end
                        state <= S_IDLE;
                    end else begin
                        clk_count <= clk_count + 16'd1;
                    end
                end

                default: begin
                    state <= S_IDLE;
                end
            endcase
        end
    end
endmodule

gps_nmea_monitor.v

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

module gps_nmea_monitor (
    input  wire clk_25mhz,
    input  wire gps_rx_i,
    output wire [7:0] led
);

    wire rx_valid;
    wire [7:0] rx_byte;

    reg rst = 1'b0;

    gps_uart_rx #(
        .CLK_HZ(25000000),
        .BAUD(9600)
    ) u_rx (
        .clk(clk_25mhz),
        .rst(rst),
        .rx(gps_rx_i),
        .data_valid(rx_valid),
        .data_byte(rx_byte)
    );

    reg [23:0] hb_counter = 24'd0;
    reg hb_led = 1'b0;

    reg [19:0] pulse_activity = 20'd0;
    reg [19:0] pulse_line     = 20'd0;
    reg [19:0] pulse_rmc      = 20'd0;

    reg fix_active = 1'b0;
    reg time_toggle = 1'b0;
    reg lat_toggle  = 1'b0;
    reg lon_toggle  = 1'b0;

    reg [7:0] line_pos = 8'd0;
    reg [7:0] field_pos = 8'd0;

    reg in_line = 1'b0;
    reg candidate_rmc = 1'b0;
    reg rmc_seen_this_line = 1'b0;

    reg [7:0] id_buf [0:4];
    reg [7:0] field_buf [0:15];
    reg [4:0] field_len = 5'd0;

    integer i;

    always @(posedge clk_25mhz) begin
        hb_counter <= hb_counter + 24'd1;
        hb_led <= hb_counter[23];

        if (pulse_activity != 0) pulse_activity <= pulse_activity - 20'd1;
        if (pulse_line != 0)     pulse_line     <= pulse_line - 20'd1;
        if (pulse_rmc != 0)      pulse_rmc      <= pulse_rmc - 20'd1;

        if (rx_valid) begin
            pulse_activity <= 20'd500000;

            if (rx_byte == "$") begin
                in_line <= 1'b1;
                line_pos <= 8'd0;
                field_pos <= 8'd0;
                field_len <= 5'd0;
                candidate_rmc <= 1'b0;
                rmc_seen_this_line <= 1'b0;
                fix_active <= fix_active;
            end else if (in_line) begin
                if (rx_byte == 8'h0D) begin
                    in_line <= 1'b1;
                end else if (rx_byte == 8'h0A) begin
                    pulse_line <= 20'd500000;
                    if (rmc_seen_this_line) begin
                        pulse_rmc <= 20'd500000;
                    end
                    in_line <= 1'b0;
// ... continúa para miembros en el código completo validado ...

🔒 Parte del código validado es premium. Con el pase de 7 días o la suscripción mensual podrás consultar el archivo completo validado.

module gps_nmea_monitor (
    input  wire clk_25mhz,
    input  wire gps_rx_i,
    output wire [7:0] led
);

    wire rx_valid;
    wire [7:0] rx_byte;

    reg rst = 1'b0;

    gps_uart_rx #(
        .CLK_HZ(25000000),
        .BAUD(9600)
    ) u_rx (
        .clk(clk_25mhz),
        .rst(rst),
        .rx(gps_rx_i),
        .data_valid(rx_valid),
        .data_byte(rx_byte)
    );

    reg [23:0] hb_counter = 24'd0;
    reg hb_led = 1'b0;

    reg [19:0] pulse_activity = 20'd0;
    reg [19:0] pulse_line     = 20'd0;
    reg [19:0] pulse_rmc      = 20'd0;

    reg fix_active = 1'b0;
    reg time_toggle = 1'b0;
    reg lat_toggle  = 1'b0;
    reg lon_toggle  = 1'b0;

    reg [7:0] line_pos = 8'd0;
    reg [7:0] field_pos = 8'd0;

    reg in_line = 1'b0;
    reg candidate_rmc = 1'b0;
    reg rmc_seen_this_line = 1'b0;

    reg [7:0] id_buf [0:4];
    reg [7:0] field_buf [0:15];
    reg [4:0] field_len = 5'd0;

    integer i;

    always @(posedge clk_25mhz) begin
        hb_counter <= hb_counter + 24'd1;
        hb_led <= hb_counter[23];

        if (pulse_activity != 0) pulse_activity <= pulse_activity - 20'd1;
        if (pulse_line != 0)     pulse_line     <= pulse_line - 20'd1;
        if (pulse_rmc != 0)      pulse_rmc      <= pulse_rmc - 20'd1;

        if (rx_valid) begin
            pulse_activity <= 20'd500000;

            if (rx_byte == "$") begin
                in_line <= 1'b1;
                line_pos <= 8'd0;
                field_pos <= 8'd0;
                field_len <= 5'd0;
                candidate_rmc <= 1'b0;
                rmc_seen_this_line <= 1'b0;
                fix_active <= fix_active;
            end else if (in_line) begin
                if (rx_byte == 8'h0D) begin
                    in_line <= 1'b1;
                end else if (rx_byte == 8'h0A) begin
                    pulse_line <= 20'd500000;
                    if (rmc_seen_this_line) begin
                        pulse_rmc <= 20'd500000;
                    end
                    in_line <= 1'b0;
                end else if (rx_byte == ",") begin
                    if (field_pos == 8'd0) begin
                        if ((id_buf[0] == "G") &&
                            (id_buf[1] == "P" || id_buf[1] == "N") &&
                            (id_buf[2] == "R") &&
                            (id_buf[3] == "M") &&
                            (id_buf[4] == "C")) begin
                            candidate_rmc <= 1'b1;
                            rmc_seen_this_line <= 1'b1;
                        end
                    end else if (candidate_rmc) begin
                        if (field_pos == 8'd1 && field_len != 0) begin
                            time_toggle <= ~time_toggle;
                        end
                        if (field_pos == 8'd2 && field_len != 0) begin
                            if (field_buf[0] == "A")
                                fix_active <= 1'b1;
                            else
                                fix_active <= 1'b0;
                        end
                        if (field_pos == 8'd3 && field_len != 0) begin
                            lat_toggle <= ~lat_toggle;
                        end
                        if (field_pos == 8'd5 && field_len != 0) begin
                            lon_toggle <= ~lon_toggle;
                        end
                    end

                    field_pos <= field_pos + 8'd1;
                    field_len <= 5'd0;
                end else if (rx_byte == "*") begin
                    if (candidate_rmc) begin
                        if (field_pos == 8'd1 && field_len != 0) begin
                            time_toggle <= ~time_toggle;
                        end
                        if (field_pos == 8'd2 && field_len != 0) begin
                            if (field_buf[0] == "A")
                                fix_active <= 1'b1;
                            else
                                fix_active <= 1'b0;
                        end
                        if (field_pos == 8'd3 && field_len != 0) begin
                            lat_toggle <= ~lat_toggle;
                        end
                        if (field_pos == 8'd5 && field_len != 0) begin
                            lon_toggle <= ~lon_toggle;
                        end
                    end
                end else begin
                    if (field_pos == 8'd0) begin
                        if (line_pos < 8'd5) begin
                            id_buf[line_pos] <= rx_byte;
                        end
                        line_pos <= line_pos + 8'd1;
                    end else begin
                        if (field_len < 5'd16) begin
                            field_buf[field_len] <= rx_byte;
                            field_len <= field_len + 5'd1;
                        end
                    end
                end
            end
        end
    end

    assign led[0] = hb_led;
    assign led[1] = (pulse_activity != 0);
    assign led[2] = (pulse_line != 0);
    assign led[3] = (pulse_rmc != 0);
    assign led[4] = fix_active;
    assign led[5] = time_toggle;
    assign led[6] = lat_toggle;
    assign led[7] = lon_toggle;

endmodule

tb_gps_nmea_monitor.v

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

`timescale 1ns/1ps

module tb_gps_nmea_monitor;

    reg clk = 1'b0;
    reg gps_rx_i = 1'b1;
    wire [7:0] led;

    gps_nmea_monitor dut (
        .clk_25mhz(clk),
        .gps_rx_i(gps_rx_i),
        .led(led)
    );

    always #20 clk = ~clk; // 25 MHz

    localparam integer BIT_NS = 104166; // approx 9600 baud

    task uart_send_byte;
        input [7:0] b;
        integer i;
        begin
            gps_rx_i = 1'b0;
            #(BIT_NS);
            for (i = 0; i < 8; i = i + 1) begin
                gps_rx_i = b[i];
                #(BIT_NS);
            end
            gps_rx_i = 1'b1;
            #(BIT_NS);
        end
    endtask

    task uart_send_string;
        input [8*96-1:0] s;
        integer i;
        reg [7:0] ch;
// ... continúa para miembros en el código completo validado ...

🔒 Parte del código validado es premium. Con el pase de 7 días o la suscripción mensual podrás consultar el archivo completo validado.

`timescale 1ns/1ps

module tb_gps_nmea_monitor;

    reg clk = 1'b0;
    reg gps_rx_i = 1'b1;
    wire [7:0] led;

    gps_nmea_monitor dut (
        .clk_25mhz(clk),
        .gps_rx_i(gps_rx_i),
        .led(led)
    );

    always #20 clk = ~clk; // 25 MHz

    localparam integer BIT_NS = 104166; // approx 9600 baud

    task uart_send_byte;
        input [7:0] b;
        integer i;
        begin
            gps_rx_i = 1'b0;
            #(BIT_NS);
            for (i = 0; i < 8; i = i + 1) begin
                gps_rx_i = b[i];
                #(BIT_NS);
            end
            gps_rx_i = 1'b1;
            #(BIT_NS);
        end
    endtask

    task uart_send_string;
        input [8*96-1:0] s;
        integer i;
        reg [7:0] ch;
        begin
            for (i = 95; i >= 0; i = i - 1) begin
                ch = s[i*8 +: 8];
                if (ch != 8'h00)
                    uart_send_byte(ch);
            end
        end
    endtask

    initial begin
        #(1000000);

        uart_send_string({
            "$GPRMC,123519,V,4807.038,N,01131.000,E,0.0,0.0,230394,003.1,W*53",
            8'h0D, 8'h0A
        });

        #(2000000);

        uart_send_string({
            "$GPRMC,123520,A,4807.038,N,01131.000,E,0.1,0.0,230394,003.1,W*52",
            8'h0D, 8'h0A
        });

        #(5000000);

        $display("LED state = %b", led);
        if (led[4] !== 1'b1) begin
            $display("ERROR: fix_active LED did not assert");
            $fatal;
        end

        $display("PASS: RMC monitor parsed active fix.");
        $finish;
    end

endmodule

ulx3s_gps_nmea.lpf

Ajusta los nombres exactos de pin LOCATE COMP si tu revisión de ULX3S es diferente. Mantén los nombres de señal sin cambios.

BLOCK RESETPATHS;
BLOCK ASYNCPATHS;

FREQUENCY PORT "clk_25mhz" 25 MHZ;

LOCATE COMP "clk_25mhz" SITE "G2";

LOCATE COMP "gps_rx_i" SITE "P17";
IOBUF PORT "gps_rx_i" IO_TYPE=LVCMOS33 PULLMODE=UP;

LOCATE COMP "led[0]" SITE "B2";
LOCATE COMP "led[1]" SITE "C2";
LOCATE COMP "led[2]" SITE "C1";
LOCATE COMP "led[3]" SITE "D2";
LOCATE COMP "led[4]" SITE "D1";
LOCATE COMP "led[5]" SITE "E2";
LOCATE COMP "led[6]" SITE "E1";
LOCATE COMP "led[7]" SITE "F2";

IOBUF PORT "led[0]" IO_TYPE=LVCMOS33;
IOBUF PORT "led[1]" IO_TYPE=LVCMOS33;
IOBUF PORT "led[2]" IO_TYPE=LVCMOS33;
IOBUF PORT "led[3]" IO_TYPE=LVCMOS33;
IOBUF PORT "led[4]" IO_TYPE=LVCMOS33;
IOBUF PORT "led[5]" IO_TYPE=LVCMOS33;
IOBUF PORT "led[6]" IO_TYPE=LVCMOS33;
IOBUF PORT "led[7]" IO_TYPE=LVCMOS33;

Comandos de compilación/grabación/ejecución

Crea un directorio de trabajo y coloca allí los cuatro archivos.

1) Lint con Verilator

verilator --lint-only -Wall -Wno-DECLFILENAME gps_uart_rx.v gps_nmea_monitor.v tb_gps_nmea_monitor.v

2) Ejecutar simulación

verilator -Wall -Wno-DECLFILENAME --binary gps_uart_rx.v gps_nmea_monitor.v tb_gps_nmea_monitor.v
./obj_dir/Vtb_gps_nmea_monitor

La línea final esperada en consola debe incluir:

PASS: RMC monitor parsed active fix.

3) Sintetizar para ECP5-85F

Importante: la síntesis debe usar solo archivos sintetizables.

yosys -p "read_verilog gps_uart_rx.v gps_nmea_monitor.v; synth_ecp5 -top gps_nmea_monitor -json gps_nmea_monitor.json"

4) Place and route

Usa el encapsulado ULX3S correcto para la revisión de tu placa. Un objetivo común de ULX3S ECP5-85F es CABGA381.

nextpnr-ecp5 --85k --package CABGA381 --json gps_nmea_monitor.json --lpf ulx3s_gps_nmea.lpf --textcfg gps_nmea_monitor.config

5) Empaquetar bitstream

ecppack gps_nmea_monitor.config gps_nmea_monitor.bit

6) Programar la ULX3S

openFPGALoader -b ulx3s gps_nmea_monitor.bit

7) Ejecutar en hardware

  • Alimenta la ULX3S por USB
  • Alimenta correctamente el NEO-6M
  • Conecta:
  • GPS GND -> ULX3S GND
  • GPS TX -> pin gps_rx_i de la ULX3S usado en el LPF
  • Coloca el GPS donde sea posible recibir satélites:
  • al aire libre es lo mejor
  • cerca de una ventana despejada puede funcionar
  • Observa los LED durante 10 a 60 segundos

Validación paso a paso

1) Validar el módulo GPS de forma independiente si es necesario

Antes de involucrar la FPGA, suele ser útil confirmar que el GPS está emitiendo datos NMEA:

  • Conecta la TX del NEO-6M a la entrada de un adaptador USB-UART conocido y funcional
  • Abre un terminal serie a 9600
  • Busca líneas como:
  • $GPRMC,...
  • $GPGGA,...

Si no ves texto NMEA legible, corrige eso primero.

2) Validar el comportamiento de la simulación

Después de ejecutar la simulación con Verilator:

  • Confirma que la prueba termina con PASS
  • Confirma que no aparecen errores fatales
  • La simulación inyecta:
  • una línea RMC con estado no válido (V)
  • una línea RMC con estado activo (A)
  • El resultado esperado es que:
  • la lógica UART reciba bytes
  • el analizador detecte RMC
  • led[4] pase a 1

3) Validar la configuración de la FPGA

Después de openFPGALoader:

  • Confirma que la herramienta informa que se encontró el dispositivo ULX3S
  • Confirma que no se muestra ningún error de carga del bitstream
  • Después de programar:
  • led[0] debería parpadear como latido
  • Si el latido no parpadea, la imagen de FPGA no se está ejecutando correctamente

4) Validar la actividad UART en hardware

Con el GPS conectado y alimentado:

  • led[1] debería generar pulsos o parecer activo con frecuencia cuando llegan caracteres NMEA
  • led[2] debería generar pulsos cuando terminan líneas completas
  • led[3] debería generar pulsos cuando se vean sentencias RMC

Interpretación:

  • led[1] apagado todo el tiempo:
  • problema de cableado
  • mapeo de pin incorrecto
  • nivel de voltaje incorrecto
  • tasa de baudios incorrecta
  • GPS sin alimentación
  • led[1] activo pero led[3] nunca activo:
  • el analizador no está viendo RMC
  • corrupción serie
  • formato de mensaje/talker inesperado

5) Validar la indicación de fix

Observa led[4]:

  • led[4] = 0 significa que el último estado RMC analizado no estaba activo (V) o que todavía no se ha visto una línea activa válida
  • led[4] = 1 significa que se ha analizado una sentencia RMC con estado A

Este es el criterio principal de éxito para un monitor GPS útil.

6) Validar actualizaciones continuas en campo

Observa los indicadores de actualización:

  • led[5] conmuta cuando se actualiza el campo de tiempo
  • led[6] conmuta cuando se actualiza el campo de latitud
  • led[7] conmuta cuando se actualiza el campo de longitud

Si estos cambian con el tiempo mientras led[3] genera pulsos, la FPGA está analizando campos clave de posición/tiempo en lugar de limitarse a detectar tráfico UART bruto.

7) Comportamiento esperado realista

En una sesión práctica:

  • En interiores sin vista al cielo:
  • normalmente aparece actividad UART
  • puede haber RMC
  • el fix puede seguir inválido durante mucho tiempo
  • Al aire libre:
  • un fix activo suele ser mucho más probable
  • led[4] debería encenderse finalmente
  • los indicadores de campo deberían seguir cambiando

Nota educativa de validación

Antes de la publicación, este caso superó la compuerta de validación automatizada de Prometeo con estado PASS. Para este perfil FPGA/ULX3S, los bloques Verilog sintetizables se comprobaron con Yosys (read_verilog) y el conjunto de diseño/prueba Verilog se revisó con Verilator. El validador también comprobó la estructura de los bloques de código, opciones de comandos ASCII seguras para copiar/pegar, stacks no compatibles y la disponibilidad de la cadena de herramientas ULX3S/ECP5 (yosys, nextpnr-ecp5, ecppack, openFPGALoader).

Esta validación confirma la sintaxis y la compatibilidad de herramientas para el código publicado, pero no sustituye las pruebas físicas en tu revisión exacta de placa ULX3S, archivo de restricciones de pines y cableado real.

Nota educativa de seguridad

Este prototipo es un monitor educativo de datos GPS, no un instrumento certificado de navegación, temporización, automoción, aviación, marina, industrial o de seguridad crítica.

Puntos de seguridad y limitación:

  • Usa solo cableado UART de 3.3 V hacia la entrada de la FPGA, a menos que hayas verificado positivamente la compatibilidad eléctrica.
  • Muchas placas breakout GPS difieren en alimentación y comportamiento de E/S. Comprueba tu módulo exacto antes de conectarlo.
  • No uses este proyecto para tomar decisiones en tiempo real para:
  • vehículos
  • drones
  • embarcaciones
  • navegación personal en zonas peligrosas
  • infraestructuras críticas de temporización
  • Las configuraciones de banco alimentadas por USB pueden provocar errores accidentales de cableado. Apaga siempre antes de recablear.
  • Este tutorial no cubre diseño de carcasas para exterior, protección contra sobretensiones, protección ESD ni endurecimiento ambiental.
  • Si pruebas al aire libre, asegura cables y placas para que no generen riesgos de tropiezo o exposición al clima.
  • La indicación de fix en este proyecto refleja el estado NMEA analizado, no una corrección absoluta garantizada de posición.

Solución de problemas

Ningún LED responde excepto quizá el latido

Comprueba:

  • ¿El módulo GPS está alimentado correctamente?
  • ¿La tierra está compartida entre el GPS y la ULX3S?
  • ¿La TX del GPS está realmente conectada a la entrada FPGA elegida?
  • ¿Usaste el pin LPF correcto para la revisión real de tu placa ULX3S?

El latido funciona, pero no hay actividad UART

Posibles causas:

  • Tasa de baudios incorrecta:
  • la mayoría de los módulos NEO-6M usan 9600 baudios por defecto, pero verifica el tuyo
  • Nivel lógico de TX del GPS incompatible o ausente
  • Incompatibilidad de ubicación de pin en el LPF
  • Cable jumper roto
  • Módulo GPS no completamente alimentado o sin arrancar

Hay actividad UART, pero no detección de RMC

Posibles causas:

  • Tu GPS entrega GNRMC en lugar de GPRMC
  • este diseño ya acepta tanto GPRMC como GNRMC
  • Corrupción serie debido a mal cableado
  • Temporización de baudios incorrecta porque el reloj de tu placa no es realmente de 25 MHz
  • Ruido en la entrada RX

Se detecta RMC, pero el fix nunca se activa

Esto a menudo significa que el diseño FPGA está bien y que el problema es el entorno del GPS.

Prueba:

  • Moverte al aire libre
  • Esperar más tiempo para un arranque en frío
  • Comprobar la conexión de la antena
  • Verificar el estado del módulo con un terminal serie en la PC

Errores de compilación en nextpnr o en el mapeo LPF

Causas probables:

  • El encapsulado CABGA381 no coincide con tu placa
  • Los nombres de pin de LED o reloj son incorrectos para tu revisión de ULX3S
  • Los nombres de pin de restricciones necesitan adaptarse desde los archivos oficiales de ULX3S

Si hace falta, mantén el Verilog sin cambios y ajusta solo el LPF.

Mejoras

Una vez que el monitor base funcione, puedes ampliarlo hasta convertirlo en un instrumento de campo más capaz.

Mejoras prácticas

  • Añadir salida de siete segmentos u OLED
  • Mostrar la hora UTC directamente en la pantalla local
  • Exponer valores analizados por una segunda UART
  • Enviar estado compacto legible por máquina a una PC o microcontrolador
  • Añadir verificación de checksum
  • Mejorar la confianza en que las sentencias analizadas no están corruptas
  • Soportar más sentencias NMEA
  • Analizar GGA para altitud y conteo de satélites
  • Añadir tiempo de espera de fix
  • Apagar el LED de fix si no llega ninguna sentencia activa durante varios segundos
  • Registrar estadísticas de sentencias
  • Contar líneas por segundo, tramas inválidas y transiciones de fix
  • Páginas de modo controladas por botones
  • Un modo para estado de tráfico bruto, otro para tendencias del estado de fix

Mejoras de ingeniería

  • Añadir un FIFO pequeño entre UART y analizador
  • Añadir comprobaciones explícitas de encuadre de línea CR/LF
  • Añadir botones con anti-rebote para borrar banderas de estado
  • Usar un analizador de máquina de estados finitos más estricto para ID de sentencias y campos
  • Exportar bytes de campos analizados a un banco de registros simple para acceso futuro desde un host

Lista de verificación final

Usa esta lista antes de declarar el proyecto completo:

  • [ ] Usé la familia de hardware exacta: FPGA
  • [ ] Usé el modelo exacto: Radiona ULX3S (Lattice ECP5-85F) + u-blox NEO-6M GPS module + 3.3 V UART wiring
  • [ ] El GPS y la ULX3S comparten una tierra común
  • [ ] La TX del GPS está conectada al pin de entrada FPGA definido en el LPF
  • [ ] Verifiqué que la lógica UART del GPS es segura para 3.3 V
  • [ ] El lint de Verilator se completó sin errores bloqueantes
  • [ ] La simulación imprimió PASS: RMC monitor parsed active fix.
  • [ ] La síntesis de Yosys se completó correctamente
  • [ ] nextpnr-ecp5 se completó correctamente para el objetivo ECP5-85F
  • [ ] El bitstream se empaquetó con ecppack
  • [ ] La placa se programó con openFPGALoader -b ulx3s
  • [ ] led[0] parpadea después de programar
  • [ ] led[1] muestra actividad UART cuando el GPS está conectado
  • [ ] led[3] indica que se están reconociendo sentencias RMC
  • [ ] led[4] se enciende cuando el GPS informa un fix activo
  • [ ] led[5], led[6] y led[7] cambian a medida que se actualizan los campos de tiempo/posición

Si todos los elementos están marcados, tienes un gps-nmea-position-time-monitor práctico basado en FPGA que es genuinamente útil para diagnóstico de módulos GPS y educación sobre datos serie.

        <div class="amazon-affiliate">
          <p><strong>Encuentra este producto y/o libros sobre este tema en Amazon</strong></p>
          <p><a class="amazon-affiliate-btn" href="https://amzn.to/4mt8r4C" target="_blank" rel="nofollow sponsored noopener">Ir a Amazon</a></p>
          <p class="amazon-affiliate-disclaimer">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.</p>
        </div>

Quiz rápido

Pregunta 1: ¿Qué placa FPGA se utiliza para construir el monitor GPS según el artículo?




Pregunta 2: ¿Qué módulo GPS se especifica para este proyecto?




Pregunta 3: ¿A qué velocidad de baudios se reciben los datos del módulo GPS?




Pregunta 4: ¿Qué tipo de cableado y voltaje se utiliza para la comunicación con el GPS?




Pregunta 5: ¿Qué formato de datos transmite el módulo GPS para ser analizado?




Pregunta 6: ¿Dónde se visualiza la actividad UART y el estado de fix del GPS?




Pregunta 7: ¿Cuál es la latencia esperada para la actualización de sentencias de tiempo y posición?




Pregunta 8: ¿Qué ventaja ofrece este monitor para la verificación en banco del módulo GPS?




Pregunta 9: ¿Cuál es la frecuencia típica de actualización visible de estado en este sistema?




Pregunta 10: Para la formación en diseño digital, ¿qué demuestra este proyecto en la FPGA?




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