P1: Introducción a la RPi Pico 2 - myTeachingURJC/Arq-computadores-01 GitHub Wiki

Sesión 1: Introducción a Raspberry Pi Pico 2 con RISC-V

A lo largo de estas sesiones llevaremos al mundo real todo lo que hemos aprendido con el RISC-V utilizando la Raspberry Pi Pico 2.

Esta placa integra un microprocesador con arquitectura dual: 2 núcleos ARM y 2 núcleos RISC-V. Nosotros activaremos y utilizaremos exclusivamente los núcleos RISC-V para programar en lenguaje ensamblador nativo.

1. Hardware y Pinout

Para simplificar las conexiones y la visualización, la Pico 2 se encuentra montada sobre una Breakout Board. Esta placa adicional nos proporciona acceso sencillo a los pines y dispone de LEDs indicadores para el estado de los mismos.

Mapeo de Memoria (MMIO)

La placa dispone de 27 pines GPIO (General Purpose Input Output). Un GPIO es un pin genérico capaz de comportarse como entrada o salida digital, o bien asumir funciones especiales (SPI, I2C, PWM, etc.).

Para esta asignatura hemos preconfigurado el entorno de modo que el acceso a los pines sea idéntico al funcionamiento de la MMIO en RARS.

Pines Función Dirección de Memoria Descripción
GP0 - GP7 Salida 0xD0000010 Escribe aquí para encender los LEDs.
GP8 - GP15 Entrada 0xD0000005 Lee de aquí para obtener el estado de los interruptores.

Instrucciones de uso:

  • Usar sb (store byte) en 0xD0000010 para escribir en los LEDs.
  • Usar lb (load byte) en 0xD0000005 para leer las entradas.

IMPORTANTE: Lógica Activa a Nivel Bajo

En la Raspberry Pi Pico 2, configurar los pines con la resistencia interna Pull Up garantiza que reciban alimentación continua. Esto significa que la entrada se mantiene en un nivel alto por defecto.

Comportamiento del Pulsador

  • Sin pulsar: El voltaje se mantiene alto y la placa registra de forma continua un 1.
  • Pulsado: El circuito conecta directamente con tierra (GND), el voltaje cae a cero y la placa registra un 0.

Ejemplo Práctico de Lectura en un Byte.

Al agrupar varias entradas para leer un byte completo, los valores numéricos cambian dependiendo del botón que conectemos a tierra:

  • Ningún botón pulsado: Todos los bits están en 1. El valor de lectura es 255.
  • Botón del primer bit pulsado: Solo esa entrada baja a 0. El valor de lectura es 254.
  • Botón del bit 7 pulsado: El extremo de mayor peso baja a 0. El valor de lectura es 127.

A la hora de programar tu práctica, el código deberá evaluar estas caídas a cero o los cambios numéricos exactos para ejecutar las acciones correspondientes.


2. Flujo de trabajo: Cargar un programa

Vamos a cargar nuestro primer programa ("Hola Mundo" en hardware) que hará parpadear el led conectado a GP0 y nos imprimirá por el puerto serie "Hola mundo".

Configuración de Visual Studio Code

Para editar, compilar y visualizar el puerto serie utilizaremos Visual Studio Code.

Abre VS Code e instala las siguientes extensiones que necesitaremos:

  • Pulsa Ctrl + P
  • Pega los siguientes enlaces de las extensiones:
ext install zhwu95.riscv
ext install ms-vscode.vscode-serial-monitor

Esto instalará una extensión para resaltar la sintaxis del Risc-V y un monitor serie.

Pasos para la compilación y carga:

  1. Preparación: Descarga y descomprime el archivo Pico2.zip con el entorno de trabajo. Si trabajas desde tu ordenador personal, descarga este de otro enlace.

  2. Compilación: Abre en vscode la carpeta que acabas de descargar, y después abre el archivo holamundo.s. Para compilar pulsa Ctrl + Shift + B o Pulsa Terminal -> Run Build Task.

Se ejecutará un script que ensambla el código de holamundo.s y genera el archivo binario holamundo.uf2.

  1. Modo Bootloader: Mantén pulsado el botón BOOTSEL de la Pico 2. Sin soltarlo, conéctala al PC mediante el USB.
  2. Carga: El PC reconocerá la placa como una unidad de almacenamiento externa (similar a un Pendrive). Arrastra el archivo holamundo.uf2 dentro de esa unidad.
  3. Ejecución: La placa se reiniciará automáticamente y ejecutará el código. Deberías ver 8 LEDs encendidos y uno parpadeando.
  4. Puerto Serie: Para ver los mensajes que envía la RPi por el usb, una vez cargado el programa, pulsa en Serial Monitor en la barra de abajo, donde salen los mensajes de compilación. Si no tienes la barra, pulsa Terminal -> New Terminal. Asegurate de seleccionar el puerto de tu placa, que debería ser /dev/ttyACM0. Activa también la opcción de Automatic Reconnection.

Ahora ya puedes hacer tus propios programas en ensamblador, generar el binario y probarlos en la Pico.

Cada vez que quieras cargar un nuevo programa, tendrás que desconectar y conectar la PICO manteniendo pulsado BOOTSEL.

[!WARNING] ⚠️ PRECAUCIÓN HARDWARE ⚠️ El conector micro-USB de la Pico es frágil y tiene un número limitado de ciclos de conexión.

  • NO desconectes el cable del lado de la Pico repetidamente.
  • Para reiniciar o cargar un nuevo programa, desconecta y conecta el extremo del cable que va al ordenador, manteniendo siempre el cable conectado a la placa.

3. Análisis del código: holamundo.s

El salto del simulador al mundo real implica considerar detalles que antes gestionaba el simulador. Analicemos el archivo base holamundo.s.

# -- Include library headers
.include "compile/gpio.s"

.global asm_main   

# -- Constants definitions
.eqv GPIO_BASE,       0xD0000001
.eqv GPIO_IN_OFFSET,  0x5
.eqv GPIO_OUT_OFFSET, 0x10


# -- Data Section
.data

cad:
    .string "Hola mundo!\n"


# -- Code Section
.text

# -- Punto de entrada
asm_main:

    #-- Inicializar GPIOs
    jal init

    #-- Programa principal

    #-- Carga de las dirección base del MMIO
    li s0, GPIO_BASE
    

    #-- Comienzo del bucle principal
bucle:
    
    #-- Encender led
    li t0, 1
    sb t0, GPIO_OUT_OFFSET(s0)

    #-- Imprimir mensaje
    la a0, cad
    jal print

    #-- Espera de 0.5 segundos
    li a0, 500
    jal sleep_ms

    #-- Apagado del led
    li t0, 0
    sb t0, GPIO_OUT_OFFSET(s0)

    #-- Espera de 0.5 segundos
    li a0, 500
    jal sleep_ms

    #-- Bucle
    j bucle

Diferencias clave con RARS

1. La rutina de inicio (jal init)

Esta instrucción es obligatoria al principio de _start. Si la omites, los pines no funcionarán y tus variables en .data no estarán inicializadas en la RAM.

2. Ausencia de Sistema Operativo (ecall)

En RARS usábamos la llamada al sistema 10 para terminar el programa. En este hardware no hay sistema operativo al que devolver el control. El procesador nunca se detiene.

Por ello existen dos estrategias:

  • Bucle de trabajo: El programa se repite indefinidamente realizando una tarea.
  • Detención: Si el programa termina su tarea, debemos capturarlo en un bucle infinito (j loop) para evitar que el contador de programa (PC) siga avanzando y ejecute instrucciones basura de la memoria memoria vacía.

Como no tenemos Sistema Operativo, no tenemos llamadas al sistema con ecall. Sin embargo, tenemos implementadas unas subrutinas que se pueden llamar con jal que se describen a continuación:

  • print: Imprime por terminal el string cuya dirección base tengamos cargada en el registro a0
  • print_unsigned_int: Imprime por terminal el entero que tengamos cargado en el registro a0
  • sleep_ms: Realiza una espera del tiempo en milisegundos que tengamos en el registro a0

Para ver la salida de por terminal de las subrutinas *print y print_unsigned_int, metete en Serial Monitor

Es recomendable no cerrar esa terminal, detecta las conexiones y desconexiones de la Pico automáticamente y así leeremos la salida desde que se inicia el programa.

Para cerrar el comando tio Pulsa ctrl + t, y después q, o simplemente cierra la terminal

NOTA: Recuerda que no son llamadas al sistema con ecall, sino subrutinas que se llaman con jal, por lo que tendrás que respetar el convenio ABI y por lo tanto los datos en registros temporales no se mantienen tras la llamada a estas subrutinas.


3. Ejercicios Propuestos

Ejercicio 1: Contador Binario

Implementa un programa que utilice los LEDs (GP0-GP7) para mostrar un contador binario de 8 bits que incremente su valor continuamente.

Resultado esperado: Los LEDs deben contar en binario (00000001, 00000010, etc.).

WhatsApp Video 2026-01-15 at 13 12 13(1)

Ejercicio 2: Entradas

Prueba las entradas de los pines GP8-GP15. Crea un programa que encienda los leds GP0-GP7 en función de la entrada de GP8-GP15, es decir si la entrada es 1 en GP8, se enciende GP0, si la entrada es 1 en GP9, se enciende GP1, etc. Imprime además por terminal el valor leido por la entrada

Autores

Licencia

Enlaces