Objetivo y caso de uso
Qué construirás: Un sistema de comunicación UART entre la FPGA Pico-ICE y una Raspberry Pi, permitiendo el envío de mensajes de bajo consumo.
Para qué sirve
- Intercambio de datos entre dispositivos de bajo consumo en aplicaciones IoT.
- Control de sensores conectados a la Raspberry Pi mediante comandos UART desde la FPGA.
- Implementación de un sistema de eco para pruebas de comunicación en tiempo real.
- Desarrollo de prototipos de comunicación en proyectos de robótica.
Resultado esperado
- Latencia de comunicación menor a 10 ms entre la FPGA y la Raspberry Pi.
- Capacidad de enviar y recibir hasta 115200 bps sin pérdida de datos.
- Consumo de energía inferior a 50 mW durante la operación de transmisión.
- Mensajes de eco recibidos en la Raspberry Pi con un 99% de precisión.
Público objetivo: Desarrolladores y estudiantes de electrónica; Nivel: básico
Arquitectura/flujo: Comunicación UART desde la FPGA Pico-ICE a la Raspberry Pi, utilizando Python para el procesamiento de datos.
Nivel: basico
Prerrequisitos
Sistema operativo y entorno base
- Raspberry Pi OS Bookworm 64-bit (kernel y userland de 64 bits).
- Raspberry Pi 4/400/5 con puertos USB tipo A (host).
- Python 3.11 (incluido en Raspberry Pi OS Bookworm 64-bit).
Asegúrate de que tu sistema está actualizado:
sudo apt update
sudo apt full-upgrade -y
sudo reboot
Toolchain exacta para FPGA y utilidades
Para sintetizar, colocar y enrutar en el iCE40UP5K de la Pico-ICE usaremos la OSS CAD Suite, que trae todo integrado (Yosys, nextpnr-ice40, icestorm):
- OSS CAD Suite (aarch64 / ARM64) versión: 2024-10-01
- yosys (incluido en el bundle 2024-10-01)
- nextpnr-ice40 (incluido en el bundle 2024-10-01)
- icestorm: icepack/iceprog (incluidos en el bundle 2024-10-01)
Instalación en /opt/oss-cad-suite:
# 1) Descarga la OSS CAD Suite (ARM64) 2024-10-01
cd /tmp
curl -LO https://github.com/YosysHQ/oss-cad-suite-build/releases/download/2024-10-01/oss-cad-suite-linux-arm64-20241001.tgz
# 2) Descomprime en /opt (requiere sudo)
sudo mkdir -p /opt
sudo tar -xzf oss-cad-suite-linux-arm64-20241001.tgz -C /opt
# 3) Crea un enlace conveniente
sudo ln -sfn /opt/oss-cad-suite /opt/oss-cad-suite-2024-10-01
# 4) Exporta PATH para la sesión actual
export PATH=/opt/oss-cad-suite-2024-10-01/bin:$PATH
# 5) Opcional: añade a tu ~/.bashrc para que quede persistente:
echo 'export PATH=/opt/oss-cad-suite-2024-10-01/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
# 6) Verifica versiones
yosys -V
nextpnr-ice40 --version
iceprog -v
Nota: Si cambias la versión en el futuro, actualiza el path simbólico y el PATH en consecuencia.
Habilitar interfaces en Raspberry Pi OS (UART)
En este caso usaremos el UART por GPIO del Raspberry Pi para hablar con la FPGA (eco). Debes:
1) Desactivar la consola por serie.
2) Activar el puerto serie (UART) para uso general.
Con raspi-config:
sudo raspi-config
# Interface Options -> Serial Port
# - "Would you like a login shell to be accessible over serial?" -> No
# - "Would you like the serial port hardware to be enabled?" -> Yes
# Finish -> Reboot
O editando /boot/firmware/config.txt:
sudo nano /boot/firmware/config.txt
# Asegúrate de tener:
enable_uart=1
# Guarda y reinicia
sudo reboot
Después del reinicio, el UART principal estará accesible como /dev/serial0 (en Pi 4/5 suele apuntar a /dev/ttyAMA0 o /dev/ttyS0 según la configuración interna).
Entorno Python 3.11 con venv y librerías
Crearemos un entorno virtual para scripts de validación por serie y, ya que estamos en Raspberry Pi, dejaremos listos paquetes típicos:
sudo apt install -y python3-venv python3-dev python3-pip \
minicom screen git
# gpiozero depende de RPi.GPIO o lgpio según el modelo; instalaremos por pip
# (pyserial lo usaremos para la validación UART)
python3 -m venv ~/venvs/picoice
source ~/venvs/picoice/bin/activate
pip install --upgrade pip
pip install pyserial==3.5 gpiozero==1.6.2 smbus2==0.5.3 spidev==3.6
# Verifica
python -c "import sys,serial; print(sys.version); print(serial.__version__)"
deactivate
- Python: 3.11.x (sistema).
- pyserial: 3.5
- gpiozero: 1.6.2
- smbus2: 0.5.3
- spidev: 3.6
Estas librerías no todas se usarán en este caso concreto, pero el entorno queda listo para prácticas futuras (GPIO, I2C, SPI).
Materiales
- 1 × Raspberry Pi 4/400/5 con Raspberry Pi OS Bookworm 64‑bit y acceso a Internet.
- 1 × Tarjeta microSD (≥16 GB) con Raspberry Pi OS Bookworm 64-bit.
- 1 × Placa “Pico-ICE (Lattice iCE40UP5K)”. Modelo exacto: Pico-ICE (FPGA iCE40UP5K).
- 1 × Cable USB-A a Micro‑USB o USB-C (según la revisión de tu Pico-ICE; revisa la serigrafía). Se usa para programación del FPGA a través del RP2040 onboard.
- 3 × Cables Dupont macho‑hembra para conectar UART GPIO del Raspberry Pi a dos pines IO del FPGA + GND.
- Opcional pero recomendable: 1 × Adaptador USB-UART (FT232/CP2102) como alternativa al UART por GPIO del Raspberry Pi.
- 1 × Multímetro básico y/o analizador lógico (opcional) para validación adicional.
Nota: La Pico-ICE integra un RP2040 que actúa como programador del FPGA; no necesitamos JTAG externo.
Preparación y conexión
Disposición general
- USB del Raspberry Pi a la Pico-ICE: para programar el bitstream (iceprog detecta el programador USB del RP2040 de la Pico-ICE).
- UART por GPIO del Raspberry Pi a dos IO de la FPGA:
- TX del Raspberry Pi (GPIO14) → RX del FPGA (señal uart_rx en el diseño).
- RX del Raspberry Pi (GPIO15) → TX del FPGA (señal uart_tx en el diseño).
- GND ↔ GND común.
Asegúrate de que todos los niveles lógicos son 3.3 V (tanto el Raspberry Pi como el iCE40UP5K trabajan a 3.3 V en sus bancos de IO).
Tabla de pines Raspberry Pi para UART
| Señal | GPIO | Cabecera física | Dirección | Comentario |
|---|---|---|---|---|
| TXD | GPIO14 | Pin 8 | Salida de la Pi | Conectar a RX de la FPGA (uart_rx) |
| RXD | GPIO15 | Pin 10 | Entrada a la Pi | Conectar a TX de la FPGA (uart_tx) |
| GND | — | Pin 6 (u otro GND) | — | Común con GND de la Pico-ICE |
Pines de la Pico-ICE (lado FPGA)
- La Pico-ICE expone IO del iCE40UP5K en cabeceras/PMODs. Consulta la serigrafía de tu placa y el fichero de constraints (PCF) del fabricante.
- Elegiremos dos IO contiguos de un conector (por ejemplo, PMOD A) para UART:
- FPGA: IO_UART_RX (entrada al FPGA) → conéctalo al TX de la Pi (GPIO14).
- FPGA: IO_UART_TX (salida del FPGA) → conéctalo al RX de la Pi (GPIO15).
- GND a GND.
Si tu repositorio de la placa trae un PCF con alias de conector (p. ej., PMODA1, PMODA2), usaremos esos alias para asignar “uart_rx” y “uart_tx” sin tener que recordar nombres de bolas del encapsulado.
Ejemplo de conexión (texto, sin esquema):
– Cable 1: Raspberry Pi Pin 8 (GPIO14, TXD) → Pico-ICE PMOD A pin 1 (IO_UART_RX).
– Cable 2: Raspberry Pi Pin 10 (GPIO15, RXD) → Pico-ICE PMOD A pin 2 (IO_UART_TX).
– Cable 3: Raspberry Pi Pin 6 (GND) → Pico-ICE GND.
Verifica que el conector PMOD y el GND estén claramente identificados en tu placa.
Código completo (HDL para el FPGA iCE40UP5K)
Objetivo “uart-hello-world-eco”:
– Al arrancar, el FPGA envía “Hello, world!” por UART a 115200 baudios, 8N1.
– Después, hace eco de todo lo que recibe (devuelve el mismo byte).
– Opcional: parpadea el LED RGB en cada retorno de línea para validar visualmente.
Notas de reloj/baudio:
– Usaremos el oscilador interno SB_HFOSC del iCE40UP5K.
– Configuraremos el HFOSC a ~12 MHz (dividiendo por 4 el nominal 48 MHz).
– Baudrate: 115200 → divisor ≈ clk/baud = 12e6 / 115200 ≈ 104.166. Usaremos 104 (pequeño error aceptable para 8N1 a 115200).
– Para robustez, el receptor usa muestreo a mitad de bit con un tick a “baud × 16” usando un divisor adicional. Mantendremos una implementación simple y didáctica.
Estructura de archivos:
– rtl/top.v (top-level con HFOSC, UART TX/RX, lógica de eco).
– rtl/uart_tx.v (transmisor UART).
– rtl/uart_rx.v (receptor UART).
rtl/uart_tx.v
// Transmisor UART simple: 8N1, LSB-first
// Genera un tick a "baud" desde un reloj de sistema.
// Entradas:
// - clk: reloj de sistema
// - resetn: reset activo en bajo
// - baud_tick: pulso 1clk a la frecuencia de baudios
// - data[7:0], data_valid: carga de dato
// Salidas:
// - tx: línea UART
// - busy: transmisor ocupado
module uart_tx (
input wire clk,
input wire resetn,
input wire baud_tick,
input wire [7:0] data,
input wire data_valid,
output reg tx,
output reg busy
);
reg [3:0] bit_idx;
reg [9:0] shifter;
always @(posedge clk) begin
if (!resetn) begin
tx <= 1'b1; // idle alto
busy <= 1'b0;
bit_idx <= 4'd0;
shifter <= 10'h3FF;
end else begin
if (!busy) begin
if (data_valid) begin
// start(0), 8 datos LSB-first, stop(1)
shifter <= {1'b1, data, 1'b0};
bit_idx <= 4'd0;
busy <= 1'b1;
end
end else begin
if (baud_tick) begin
tx <= shifter[0];
shifter <= {1'b1, shifter[9:1]}; // shift right, relleno con '1' (stop)
bit_idx <= bit_idx + 4'd1;
if (bit_idx == 4'd9) begin
busy <= 1'b0;
tx <= 1'b1; // volver a idle
end
end
end
end
end
endmodule
rtl/uart_rx.v
// Receptor UART simple: 8N1, LSB-first
// Usa "oversampling_tick" 16x para muestrear en el centro de cada bit.
// Entradas:
// - clk, resetn
// - oversampling_tick: pulso a "baud*16"
// - rx: línea UART
// Salidas:
// - data[7:0], data_ready: byte recibido listo (1 pulso)
module uart_rx (
input wire clk,
input wire resetn,
input wire oversampling_tick,
input wire rx,
output reg [7:0] data,
output reg data_ready
);
localparam OVERSAMPLE = 16;
reg [3:0] sample_cnt;
reg [3:0] bit_idx;
reg [7:0] rx_shift;
reg busy;
reg rx_sync;
// Sincronizar RX a clk
reg rx_meta;
always @(posedge clk) begin
rx_meta <= rx;
rx_sync <= rx_meta;
end
always @(posedge clk) begin
if (!resetn) begin
sample_cnt <= 4'd0;
bit_idx <= 4'd0;
rx_shift <= 8'h00;
busy <= 1'b0;
data <= 8'h00;
data_ready <= 1'b0;
end else begin
data_ready <= 1'b0; // pulso de un ciclo
if (!busy) begin
// Detecta start bit (flanco alto->bajo)
if (!rx_sync) begin
busy <= 1'b1;
sample_cnt <= 4'd0;
bit_idx <= 4'd0;
end
end else if (oversampling_tick) begin
sample_cnt <= sample_cnt + 4'd1;
if (sample_cnt == (OVERSAMPLE/2)) begin
// Muestreo a mitad de bit
if (bit_idx == 4'd0) begin
// Asegura que seguimos en start bit bajo
if (rx_sync) begin
// Ruido: cancelar
busy <= 1'b0;
end
end
end
if (sample_cnt == (OVERSAMPLE-1)) begin
sample_cnt <= 4'd0;
bit_idx <= bit_idx + 4'd1;
if (bit_idx >= 4'd1 && bit_idx <= 4'd8) begin
rx_shift <= {rx_sync, rx_shift[7:1]}; // LSB-first
end else if (bit_idx == 4'd9) begin
// stop bit: finalizar
data <= rx_shift;
data_ready <= 1'b1;
busy <= 1'b0;
end
end
end
end
end
endmodule
rtl/top.v
// Top: UART hello-world con eco para Pico-ICE (iCE40UP5K)
// - Oscilador interno HFOSC -> ~12 MHz
// - Baud 115200, 8N1
// - Envía "Hello, world!\r\n" al arrancar, luego eco.
// - LED RGB: parpadea (breve) al recibir '\n'
module top (
input wire uart_rx, // desde Raspberry Pi TX (GPIO14)
output wire uart_tx, // hacia Raspberry Pi RX (GPIO15)
output wire led_r, // LED RGB (anodo/cátodo según placa)
output wire led_g,
output wire led_b
);
// Reset simple: soltar tras unos ciclos
reg [15:0] rst_cnt = 0;
wire resetn = rst_cnt[15];
always @(posedge clk) begin
if (!resetn) rst_cnt <= rst_cnt + 16'd1;
end
// Oscilador interno: HFOSC ~48 MHz / 4 = ~12 MHz
wire clk;
SB_HFOSC #(
.CLKHF_DIV("0b10") // 00: 48MHz, 01: 24MHz, 10: 12MHz, 11: 6MHz
) u_hfosc (
.CLKHFEN(1'b1),
.CLKHFPU(1'b1),
.CLKHF(clk)
);
// Divisores para baud y oversampling (16x)
// Aprox: clk = 12_000_000 Hz
localparam integer BAUD = 115200;
localparam integer BAUD_DIV = 104; // 12e6 / 115200 ≈ 104.166
localparam integer OVER_DIV = BAUD_DIV / 16; // ≈ 6 (redondeo hacia abajo)
// Implementamos 2 contadores: uno para 16x y otro para 1x.
reg [7:0] over_cnt = 0;
reg [7:0] baud_cnt = 0;
reg over_tick = 0;
reg baud_tick = 0;
always @(posedge clk) begin
if (!resetn) begin
over_cnt <= 0;
baud_cnt <= 0;
over_tick <= 0;
baud_tick <= 0;
end else begin
// Oversampling (16x)
over_tick <= 0;
if (over_cnt == (OVER_DIV-1)) begin
over_cnt <= 0;
over_tick <= 1;
end else begin
over_cnt <= over_cnt + 1;
end
// Baudrate 1x
baud_tick <= 0;
if (baud_cnt == (BAUD_DIV-1)) begin
baud_cnt <= 0;
baud_tick <= 1;
end else begin
baud_cnt <= baud_cnt + 1;
end
end
end
// Instancias UART
wire tx_busy;
reg [7:0] tx_data;
reg tx_valid;
wire [7:0] rx_data;
wire rx_ready;
uart_tx u_tx (
.clk (clk),
.resetn (resetn),
.baud_tick (baud_tick),
.data (tx_data),
.data_valid (tx_valid),
.tx (uart_tx),
.busy (tx_busy)
);
uart_rx u_rx (
.clk (clk),
.resetn (resetn),
.oversampling_tick (over_tick),
.rx (uart_rx),
.data (rx_data),
.data_ready (rx_ready)
);
// Máquina para enviar "Hello, world!\r\n" al arranque
localparam integer MSG_LEN = 15;
reg [7:0] hello [0:MSG_LEN-1];
initial begin
hello[0] = "H";
hello[1] = "e";
hello[2] = "l";
hello[3] = "l";
hello[4] = "o";
hello[5] = ",";
hello[6] = " ";
hello[7] = "w";
hello[8] = "o";
hello[9] = "r";
hello[10] = "l";
hello[11] = "d";
hello[12] = "!";
hello[13] = "\r";
hello[14] = "\n";
end
reg [4:0] hello_idx = 0;
reg hello_sent = 0;
// LED sencillo: breve pulso en azul al recibir '\n'
reg [19:0] led_cnt = 0;
reg led_pulse = 0;
always @(posedge clk) begin
if (!resetn) begin
hello_idx <= 0;
hello_sent <= 0;
tx_valid <= 0;
tx_data <= 8'h00;
led_cnt <= 0;
led_pulse <= 0;
end else begin
// Pulso LED
if (led_pulse) begin
if (led_cnt == 20'd800000) begin // ~66ms a 12MHz
led_pulse <= 0;
led_cnt <= 0;
end else begin
led_cnt <= led_cnt + 1;
end
end
// Envío del mensaje inicial
if (!hello_sent) begin
if (!tx_busy && !tx_valid) begin
tx_data <= hello[hello_idx];
tx_valid <= 1;
end else if (tx_valid && tx_busy) begin
tx_valid <= 0; // consumido
if (hello_idx == (MSG_LEN-1)) begin
hello_sent <= 1;
hello_idx <= 0;
end else begin
hello_idx <= hello_idx + 1;
end
end
end else begin
// Modo eco: enviar cualquier byte recibido
if (rx_ready && !tx_busy && !tx_valid) begin
tx_data <= rx_data;
tx_valid <= 1;
if (rx_data == 8'h0A) begin
// '\n'
led_pulse <= 1;
led_cnt <= 0;
end
end else if (tx_valid && tx_busy) begin
tx_valid <= 0; // consumido
end
end
end
end
// LED RGB (ajusta polaridad según tu placa; muchas UP5K encienden con '0')
// Suponemos LED activo en 0 (común en iCE40 dev boards)
assign led_r = 1'b1; // apagado
assign led_g = 1'b1; // apagado
assign led_b = led_pulse ? 1'b0 : 1'b1; // parpadeo en azul
endmodule
Breve explicación de partes clave:
– SB_HFOSC: oscilador interno, configurado a ~12 MHz para un divisor de baudios fácil.
– uart_tx / uart_rx: Implementan la trama 8N1 a 115200 baudios. El receptor usa oversampling 16x para robustez.
– Máquina de estados en top: Al arrancar envía “Hello, world!
” byte a byte (respetando tx_busy). Después pasa a modo “eco”: cada byte recibido se reenvía. Si detecta ‘
‘, hace un pulso en el LED azul como feedback visual.
Compilación, programación y ejecución
Árbol de proyecto
Usa este layout:
picoice-uart-echo/
├─ rtl/
│ ├─ top.v
│ ├─ uart_tx.v
│ └─ uart_rx.v
├─ constr/
│ └─ pico-ice.pcf # constraints (lo editaremos)
├─ build/ # se genera al compilar
└─ scripts/
└─ test_uart.py # validación en la Pi
Crea carpetas y coloca los archivos .v en rtl/. El PCF lo preparamos a continuación.
Constraints (PCF) para Pico-ICE
1) Consigue el PCF base de tu Pico-ICE (por ejemplo, del repositorio del fabricante o ejemplos de la placa). Copia ese archivo como constr/pico-ice.pcf.
2) Edita el final del PCF para asignar nuestras señales top-level a pines del conector que vayas a usar. Supongamos que tu PCF define alias de conector “PMODA1, PMODA2, …” (nombres de ejemplo; ajusta a tu archivo real):
# Al final de constr/pico-ice.pcf:
# Asignación UART:
set_io uart_rx PMODA1 # PMOD A pin 1 -> entrada al FPGA (desde TX de la Pi)
set_io uart_tx PMODA2 # PMOD A pin 2 -> salida del FPGA (hacia RX de la Pi)
# LED RGB (ajusta si tu PCF trae alias para el LED)
# Ejemplos genéricos; reemplaza por los alias correctos de tu placa:
set_io led_r LED_R
set_io led_g LED_G
set_io led_b LED_B
Si tu PCF no trae alias de PMOD ni LED, consulta la documentación de la Pico-ICE para mapear a nombres de bola del encapsulado del UP5K y reemplaza PMODA1/PMODA2/LED_* por los pines correctos.
3) Paquete del chip: la Pico-ICE monta iCE40UP5K; el paquete más habitual es SG48. Usaremos “–up5k –package sg48” en nextpnr-ice40. Si tu Pico-ICE usa otro paquete, cámbialo acorde.
Comandos de síntesis, PnR, empaquetado y programación
Desde la raíz del proyecto (picoice-uart-echo/), con la OSS CAD Suite 2024-10-01 en el PATH:
# 0) Prepara carpeta de build
mkdir -p build
# 1) Síntesis con Yosys
yosys -p "read_verilog rtl/uart_tx.v rtl/uart_rx.v rtl/top.v; synth_ice40 -top top -json build/top.json"
# 2) Place & route con nextpnr-ice40 (ajusta --package si aplica)
nextpnr-ice40 --up5k --package sg48 --json build/top.json --pcf constr/pico-ice.pcf --asc build/top.asc
# 3) Empaquetar a bitstream binario
icepack build/top.asc build/top.bin
# 4) Programa la Pico-ICE conectada por USB al Raspberry Pi
# Conecta la Pico-ICE por USB al Pi y verifica que se enciende.
# Asegúrate de no tener el puerto serie ocupado antes de programar.
iceprog build/top.bin
Notas:
– Si hay varias placas, puedes usar iceprog -d <device> para especificar.
– Para programar en SPI flash (persistente) usa iceprog -S build/top.bin. Para pruebas en SRAM, usa el comando básico (sin -S).
Conexión física antes de ejecutar
- Conecta el cable USB de la Pico-ICE a un puerto USB del Raspberry Pi (para la programación).
- Conecta los tres cables Dupont:
- Pi Pin 8 (GPIO14, TXD) → PMOD A pin 1 (uart_rx en el PCF).
- Pi Pin 10 (GPIO15, RXD) → PMOD A pin 2 (uart_tx en el PCF).
- Pi GND (Pin 6) → GND Pico-ICE.
Validación paso a paso
1) Verificar el puerto serie en la Pi
Comprueba que /dev/serial0 existe:
ls -l /dev/serial0
# salida esperada -> enlace a /dev/ttyAMA0 o /dev/ttyS0 según la plataforma
Opcional: prueba con minicom:
sudo apt install -y minicom
minicom -b 115200 -o -D /dev/serial0
# -b 115200: baudios
# -o: no inicializar modem
# -D: dispositivo
- Al abrir minicom, si todo está bien, deberías ver “Hello, world!” seguido de retorno de carro y nueva línea.
- Teclea cualquier texto; debería devolverse (eco). Al enviar una línea con Enter (que manda ‘\r’ y/o ‘
‘ según configuración), el LED azul hace un pulso breve.
Para salir de minicom: Ctrl-A, X, Enter.
2) Validación con Python (recomendado para scripting)
Activa el venv y ejecuta el script de prueba:
scripts/test_uart.py:
#!/usr/bin/env python3
import time
import serial
PORT = "/dev/serial0"
BAUD = 115200
def main():
print(f"Abrir {PORT} @ {BAUD} 8N1...")
with serial.Serial(PORT, BAUD, timeout=1) as ser:
# Espera el banner
time.sleep(0.5)
data = ser.read(64)
print("Recibido inicial:", data)
msg = b"ping eco!\r\n"
print("Enviar:", msg)
ser.write(msg)
time.sleep(0.2)
echo = ser.read(len(msg))
print("Eco:", echo)
# Prueba interacción
for i in range(3):
payload = f"L{i} hola FPGA\r\n".encode()
ser.write(payload)
time.sleep(0.2)
got = ser.read(len(payload))
print(f"Eco {i}:", got)
if __name__ == "__main__":
main()
Ejecución:
source ~/venvs/picoice/bin/activate
python scripts/test_uart.py
deactivate
Resultados esperados:
– Línea “Recibido inicial: b’Hello, world!
‘” (o similar).
– “Eco: b’ping eco!
‘”.
– Tres líneas de eco subsiguientes igual a lo enviado.
– El LED azul parpadea brevemente cada vez que se recibe ‘
‘.
3) Comprobaciones adicionales
- Si tienes un multímetro con medición de frecuencia o un analizador lógico, puedes medir la línea TX del FPGA y comprobar el patrón UART a 115200 8N1.
- Con screen (alternativa a minicom):
screen /dev/serial0 115200
# Para salir: Ctrl-A, K, y
Troubleshooting (5–8 errores típicos y su solución)
1) No aparece “Hello, world!” en minicom/pyserial
– Causas:
– El bitstream no está cargado en la FPGA (error en iceprog).
– PCF no mapea correctamente uart_tx/uart_rx a los pines usados.
– Baud rate/configuración errónea (asegúrate de 115200 8N1 en el host).
– Consola serie del sistema aún activa ocupando /dev/serial0.
– Soluciones:
– Reprograma: iceprog build/top.bin y verifica salida de iceprog.
– Revisa/ajusta constr/pico-ice.pcf; confirma que PMODA1/PMODA2 (o alias correctos) se corresponden a los pines usados.
– Verifica sudo raspi-config → Serial → login por serial desactivado, hardware serial activado.
– Prueba con screen /dev/serial0 115200 para descartar minicom.
2) Caracteres corruptos o eco inestable
– Causas:
– Error de baudios por reloj inexacto o divisor mal calculado.
– Cableado largo o flojo; GND no común.
– Soluciones:
– Asegura HFOSC a 12 MHz y BAUD_DIV=104; recompila.
– Mantén cables cortos, conexiones firmes y GND compartido.
– Prueba bajar a 57600 baudios: ajusta divisor (12e6/57600 ≈ 208) y recompila.
3) No puedo programar la Pico-ICE (iceprog falla)
– Causas:
– Cable USB defectuoso o solo carga.
– Permisos USB (udev) insuficientes.
– La Pico-ICE no entra en modo programable (reconexión necesaria).
– Soluciones:
– Cambia cable USB y puerto del Raspberry Pi.
– Prueba sudo iceprog build/top.bin.
– Desconecta y reconecta la Pico-ICE; verifica con lsusb que aparezca un dispositivo con RP2040/programador.
4) LED no parpadea al recibir ‘
‘
– Causas:
– LED con polaridad distinta (activo en alto vs activo en bajo).
– PCF no mapea correctamente LED_B a la bola del LED.
– Soluciones:
– Invierte la lógica del LED (0 ↔ 1) en top.v o mapea al canal correcto (R/G/B).
– Revisa el PCF/serigrafía para asginar el pin correcto.
5) El Raspberry Pi no muestra /dev/serial0
– Causas:
– UART deshabilitado (enable_uart=0 o sin overlay).
– Conflito con consola por serie.
– Soluciones:
– En /boot/firmware/config.txt: enable_uart=1.
– sudo raspi-config → Interface Options → Serial → login “No”, hardware “Yes”.
– Reinicia y verifica ls -l /dev/serial0.
6) Eco funciona, pero faltan caracteres
– Causas:
– El receptor UART simple pierde bits si llegan muy pegados sin buffering.
– Soluciones:
– Introduce una pequeña pausa al enviar desde el host (timeout o sleeps).
– Añade un FIFO simple en el FPGA si planeas ráfagas largas.
7) nextpnr error: paquete incorrecto
– Causa:
– La Pico-ICE usa un paquete (p. ej., sg48) distinto al usado en el comando.
– Solución:
– Confirma el paquete real de tu Pico-ICE y reemplaza --package sg48 por el adecuado.
8) Minicom muestra CRLF dobles o saltos raros
– Causa:
– Configuración de traducción de retorno de carro/nueva línea en minicom.
– Solución:
– En minicom, ajusta “Serial port setup” → desactiva mapeos LF/CR o usa screen/pyserial.
Mejoras/variantes
- Cambiar baudios: ajusta BAUD y divisores (por ejemplo, 57600, 38400, 9600) y recompila. Para 57600, usa BAUD_DIV ≈ 208; para 9600, ≈ 1250.
- Eco con transformación: convierte minúsculas a mayúsculas antes del eco para validar lógica.
- Buffer FIFO: añade un pequeño FIFO para tolerar ráfagas sin perder datos, o usa handshake XON/XOFF.
- Comandos simples: reconoce comandos “LED ON”, “LED OFF” para controlar el LED por UART.
- Reubicación de pines: mapea el UART a otro PMOD o a pines con hardware específico (si tu placa comparte con otros periféricos).
- Persistencia: programa en SPI flash con
iceprog -S build/top.binpara que arranque el bitstream automáticamente al energizar. - Integración con Python: usa pyserial para crear un pequeño monitor interactivo o un test automatizado que mida latencia de eco.
Checklist de verificación
- [ ] Raspberry Pi con Raspberry Pi OS Bookworm 64-bit actualizado y Python 3.11 operativo.
- [ ] OSS CAD Suite 2024-10-01 instalada en /opt y PATH configurado;
yosys -Vynextpnr-ice40 --versionfuncionan. - [ ] Interfaz serie del Raspberry Pi habilitada: /dev/serial0 visible; consola por serial desactivada.
- [ ] Entorno virtual Python creado y activable; pyserial 3.5 instalado.
- [ ] Carpeta del proyecto creada con rtl/top.v, rtl/uart_tx.v, rtl/uart_rx.v y constr/pico-ice.pcf editado con las asignaciones correctas (uart_rx/uart_tx y LED).
- [ ] Conexiones físicas: USB del Pi a Pico-ICE; GPIO14 (TX) → uart_rx, GPIO15 (RX) → uart_tx; GND compartido.
- [ ] Flujo de build ejecutado sin errores:
- [ ] Yosys genera build/top.json
- [ ] nextpnr-ice40 genera build/top.asc
- [ ] icepack genera build/top.bin
- [ ] iceprog programa la Pico-ICE
- [ ] En minicom o con scripts/test_uart.py aparece “Hello, world!” al conectar.
- [ ] El eco funciona: lo que escribes/mandas vuelve idéntico.
- [ ] El LED azul parpadea brevemente al enviar ‘
‘. - [ ] Opcional: bitstream persistente en SPI flash con
iceprog -S.
Con este caso práctico has completado un flujo básico pero completo: escribir HDL, sintetizar con Yosys, colocar y enrutar con nextpnr-ice40, empaquetar con icepack, programar la Pico-ICE con iceprog, y validar por puerto serie desde una Raspberry Pi con Raspberry Pi OS Bookworm 64‑bit y Python 3.11. El objetivo “uart-hello-world-eco” queda resuelto con materiales, conexión, código y validación coherentes con el modelo “Pico-ICE (Lattice iCE40UP5K)”.
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.



