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
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.
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:
yosysnextpnr-ecp5ecppackopenFPGALoaderverilator
Materiales
Usa exactamente estos elementos de hardware:
| Elemento | Modelo exacto | Propósito |
|---|---|---|
| Placa FPGA | Radiona ULX3S (Lattice ECP5-85F) | Plataforma FPGA principal |
| Módulo GPS | u-blox NEO-6M GPS module | Fuente de datos NMEA por UART |
| Nivel de voltaje serie | 3.3 V UART wiring | Conexión directa segura a nivel lógico |
| Cable USB | Micro-USB o USB-C según la revisión de ULX3S | Alimentación y programación |
| Cables jumper | Hembra-hembra o mixtos según sea necesario | Conexiones entre ULX3S y NEO-6M |
| Computadora | Host Linux | Compilació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:
- Tierra común
- GPS TX -> entrada FPGA de la ULX3S
- 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_25mhzcomo reloj del sistemagps_rx_icomo entrada UART desde el módulo GPSled[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:
- Empezar desde la plantilla de restricciones conocida y funcional de tu placa ULX3S
- Reemplazar solo las señales usadas aquí
- 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á funcionandoled[1]: pulso de actividad de caracteres UARTled[2]: línea NMEA válida completadaled[3]: sentencia RMC válida detectadaled[4]: estado RMC =A(fix activo)led[5]: conmuta cuando se actualiza el campo de tiempoled[6]: conmuta cuando se actualiza el campo de latitudled[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 ...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 ...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 ...`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 COMPsi 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_ide 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 a1
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 NMEAled[2]debería generar pulsos cuando terminan líneas completasled[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 peroled[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] = 0significa que el último estado RMC analizado no estaba activo (V) o que todavía no se ha visto una línea activa válidaled[4] = 1significa que se ha analizado una sentenciaRMCcon estadoA
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 tiempoled[6]conmuta cuando se actualiza el campo de latitudled[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
GNRMCen lugar deGPRMC - este diseño ya acepta tanto
GPRMCcomoGNRMC - 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
CABGA381no 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]yled[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>




