You dont have javascript enabled! Please enable it!

Caso práctico: UART con eco en FPGA Pico-ICE y Raspberry Pi

Caso práctico: UART con eco en FPGA Pico-ICE y Raspberry Pi — hero

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.bin para 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 -V y nextpnr-ice40 --version funcionan.
  • [ ] 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

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: ¿Qué sistema operativo se recomienda para utilizar con Raspberry Pi en este artículo?




Pregunta 2: ¿Cuál es la versión de Python mencionada en los requisitos?




Pregunta 3: ¿Cuál es la herramienta utilizada para sintetizar en el iCE40UP5K?




Pregunta 4: ¿Qué comando se utiliza para actualizar el sistema?




Pregunta 5: ¿Dónde se debe descomprimir la OSS CAD Suite?




Pregunta 6: ¿Qué comando se usa para crear un enlace simbólico a la OSS CAD Suite?




Pregunta 7: ¿Qué variable de entorno se debe exportar para el PATH?




Pregunta 8: ¿Qué comando se utiliza para verificar la versión de Yosys?




Pregunta 9: ¿Qué interfaz se habilita en Raspberry Pi OS para comunicarse con la FPGA?




Pregunta 10: ¿Cuál es el nombre del bundle de la OSS CAD Suite mencionado?




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