Home - Obijuan/Learn-raspberry-pico2 GitHub Wiki

LOG

Notas sobre la placa Raspberry pico2

2025-04-25

A través de Mastodón me enteré que la placa Raspberry Pico 2 incluye 2 cores RISCV, de 32-bits. Así que me fui a Amazon y me compré una para evaluarla, y aprender sobre ella

Esta es el enlace de la compra:

https://www.amazon.es/dp/B0DCKH85WR?ref=ppx_yo2ov_dt_b_fed_asin_title

La placa me llegó muy rápido (en 2 días), y ya la tengo aquí:

Voy a empezar las pruebas y la documentación. Lo primero que hago es conectarla al USB a ver qué dispositivo aparece por defecto, y cómo se comporta la placa

La conecto y NO se enciende ningún led... pero se monta como una unidad de USB y se abre un explorador de ficheros

También podemos acceder desde el terminal. Vemos que hay dos archivos

El fichero INFO_UF2.TXT contiene esta información:

UF2 Bootloader v1.0
Model: Raspberry Pi RP2350
Board-ID: RP2350

El otro fichero es un HTML que al pincharlo te redirije a esta web:

https://www.raspberrypi.com/documentation/microcontrollers/?version=5A09D5312E22

Esa URL es la que voy a utilizar como referencia para la documentación

La Raspberry pico 2 utiliza el Chip RP2350, desarrollado por la propia empresa Raspberry. Incluye 2 cores de ARM y 2 cores de RISC-V. NO hay 4 cores en total, sino que se pueden seleccionar los dos ARM o los 2 RISC-V. Yo estoy interesado en los RISC-V, y es en lo que quiero profundizar

Buscando información, he visto que la placa se puede utilizar desde el Entorno de Arduino. Así que es lo primero que voy a hacer, a ver si así consigo cargar el primer hola mundo

Entorno de Arduino

Voy a seguir las instrucciones indicadas en este vídeo de Youtube: https://www.youtube.com/watch?v=cG89EN-pkWs

La placa no está directamente soportada por el IDE, pero se puede añadir muy fácilmente. Hay que configurarla con un fichero JSON accesible desde esta URL: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json

Voy a echar un vistazo al fichero, por curiosidad. Lo he subido también a este repo. Es un fichero muy grande. Pero su estructura principal es esta:

{
  "packages": [
    {
      "name": "rp2040",
      "maintainer": "Earle F. Philhower, III",
      "websiteURL": "https://github.com/earlephilhower/arduino-pico",
      "email": "[email protected]",
      "help": {
        "online": "https://arduino-pico.readthedocs.io/en/latest/"
      },
      "platforms": [...
      ],
      "tools": [...
      ]
   }
  ]
}

Vemos lo que hay en platforms:

{
"platforms": [
        {
          "category": "Raspberry Pi Pico",
          "name": "Raspberry Pi Pico/RP2040/RP2350",
          "url": "https://github.com/earlephilhower/arduino-pico/releases/download/4.5.2/rp2040-4.5.2.zip",
          "version": "4.5.2",
          "architecture": "rp2040",
          "archiveFileName": "rp2040-4.5.2.zip",
          "boards": [...
          ],
          "help": {...},
          "size": "...",
          "checksum": "..."
        }
    ]        
}

Vemos que apunta al paquete rp2040-4.5.2.zip dado por una URL... Lo he bajado, y lo que está ahí guardado es el entorno de desarrollo pico-sdk

En tools están las toolchains, como el gcc... Están compiladas en este enlace: https://github.com/earlephilhower/pico-quick-toolchain

He echado un vistazo a este fichero:

x86_64-linux-gnu.riscv32-unknown-elf-8ec9d6f.240929.tar.gz

Y efectivamente están todas las herramientas de gnu para el riscv. Ya compiladas

Ahora que ya tengo claro el panorama, sigo las instrucciones del vídeo: https://www.youtube.com/watch?v=cG89EN-pkWs

Lo primero es ir a File/Preferences y copiar en la zona indicada este enlace con el json:

https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json

Al hacerlo se baja el fichero json y se instala. El siguiente paso es instalar las toolchains para ello pinchamos en el botón icono del lateral izquierda (Board manager). En la búsqueda ponemos pico y nos aparecerá la placa Pico 2. Pinchamos en el icono de INSTALL. En el pantallazo yo ya lo he instalado, por lo que aparece como ya instalado y con la posibilidad de eliminarlos.

Esto se baja todas las toolchains y las instala. Antes de seguir quiero ver dónde ha instalado las toolchains

Los paquetes se han instalado aquí: /home/obijuan/.arduino15/packages

  • Las toolchains están aquí: /home/obijuan/.arduino15/packages/rp2040/tools
  • El pico-sdk aquí: /home/obijuan/.arduino15/packages/rp2040/hardware/rp2040

Todos los binarios del gcc están aquí: /home/obijuan/.arduino15/packages/rp2040/tools/pqt-gcc-riscv/4.0.1-8ec9d6f/bin

Ahora seleccionamos la placa Raspberry pico 2 desde el menú Tool/Board/Raspberry pi Pico/Raspberry pi Pico2

En la parte superio vemos que está seleccionada

En el menú de tools encontramos la información sobre la placa. Vamos a establecer el RISC-V:

Desde Tools/Port seleccionamos la opción UF2 Board

En la parte inferior derecha vemos que está la Raspberry pi pico 2 con UF2 detectada

Ahora llega el momento clave. Hay que pulsar el botón de UPLOAD. Lo que vamos a cargar es un programa NULO. La idea es comprobar primero si las toolchains y las comunicaciones con la placa van bien

Empieza el proceso. Primero pone que compilando y luego subiendo...

y Ya termina!!!! Vemos que ahora nos pone que NO CONECTADA

Esto es así porque la placa ya no está MONTADA como una unidad USB. Se ha cargado el programa nulo y se está ejecutado. En la placa no vemos nada, pero ya no está en modo USB. Sin embargo sí que vemos que ha aparecido un PUERTO SERIE:

obijuan@JANEL:~$ ls /dev/ttyACM0 
/dev/ttyACM0
obijuan@JANEL:~$

Esta placa es la leche!! Ahora configuramos arduino para decirle que la placa está en el puerto serie, y será lo que utilicemos ahora para las cargas

Ahora en la parte inferior derecha vemos que pone Raspberry pi pico2 on /dev/ttyACM0

Problemas de escritura

Si ahora repetimos el proceso, es decir, volvemos a cargar el sketch nulo pero a través del puerto serie... aparece un mensaje de error

Lo pongo aquí en modo texto:

Sketch uses 63928 bytes (1%) of program storage space. Maximum is 4186112 bytes.
Global variables use 21916 bytes (4%) of dynamic memory, leaving 502372 bytes for local variables. Maximum is 524288 bytes.
Resetting /dev/ttyACM0
Converting to uf2, output size: 188928, start address: 0x2000
Scanning for RP2040 devices
No drive to deploy.
Failed uploading: uploading error: exit status 1

En vídeo sí les funciona bien... Pero es una máquina windows... Esto huele a que es un problema de permisos con el puerto serie... Seguramente tengo que añadir algunas reglas en el udev para tener todal acceso a todo. Voy a buscar información sobre el tema

Según leo en este enlace:

https://github.com/raspberrypi/picotool#linux--macos

dice que para usar las picotools sin sudo hay que copiar el fichero de las reglas en el /etc/udev/rules.d/

Lo podemos hacer ejecutando estos dos comandos. Primero bajamos el archivo .rules y luego lo copiamos

wget https://github.com/raspberrypi/picotool/raw/refs/heads/master/udev/99-picotool.rules
sudo cp 99-picotool.rules /etc/udev/rules.d/

Comprobamos que efectivamente se ha copiado bien. Esto es lo que me aparece a mi:

obijuan@JANEL:~$ ls /etc/udev/rules.d/
60-libsigrok.rules              70-snap.gimp.rules
61-libsigrok-plugdev.rules      70-snap.libreoffice.rules
61-libsigrok-uaccess.rules      70-snap.snapd-desktop-integration.rules
70-snap.cheese.rules            70-snap.snapd.rules
70-snap.chromium.rules          70-snap.snap-store.rules
70-snap.cups.rules              80-fpga-ftdi.rules
70-snap.firefox.rules           99-picotool.rules
70-snap.firmware-updater.rules
obijuan@JANEL:~$ 

Para que tenga efecto hay que cerrar la sesión y volver a abrirla. Pero para comprobar que efectivamente tenemos permisos vamos a ejecutar este comando de prueba:

obijuan@JANEL:~$ echo "hola" > /dev/ttyACM0 
bash: /dev/ttyACM0: Permission denied
obijuan@JANEL:~$

Efectivamente NO tenemos acceso. Vamos a cerrar la sesión y volver a entrar, y repetimos la operación

He cerrado la sesión y vuelto a entrar: no consigo los permisos... joder... He apagado y vuelto a encender... tampoco!!! No obstante voy a volver a probar con el ide de arduino para cerciorarme...

Nada... no se carga. El mensaje de error es el mismo. He visto que hay una opción para cambiar el módo de carga y he seleccionado picotool (Está en Tools/upload Method). Sale otro mensaje de error:

Sketch uses 64960 bytes (1%) of program storage space. Maximum is 4186112 bytes.
Global variables use 22068 bytes (4%) of dynamic memory, leaving 502220 bytes for local variables. Maximum is 524288 bytes.
No accessible RP2040/RP2350 devices in BOOTSEL mode were found.
Failed uploading: uploading error: exit status 249

Así que voy a intentar lograr permisos de acceso, y luego sigo las pruebas

Miro el dispositivo. Pertenece al grupo "dialout"

obijuan@JANEL:~$ ls -l /dev/ttyACM0 
crw-rw---- 1 root dialout 166, 0 Apr 25 13:24 /dev/ttyACM0

Cuando conecto la Alhambra-II, veo que sus dos dispositivos pertenecen al grupo plugdev obijuan@JANEL:$ ls -l /dev/ttyUSB0 crw-rw---- 1 root plugdev 188, 0 Apr 25 13:30 /dev/ttyUSB0 obijuan@JANEL:$ ls -l /dev/ttyUSB1 crw-rw---- 1 root plugdev 188, 1 Apr 25 13:30 /dev/ttyUSB1

En el rules de la pico NO VEO NADA DE dialout... así que voy a cambiar el grupo de dialout a plugdev a ver qué pasa

obijuan@JANEL:~$ sudo chgrp plugdev /dev/ttyACM0
obijuan@JANEL:~$ ls -l /dev/ttyACM0
crw-rw---- 1 root plugdev 166, 0 Apr 25 13:32 /dev/ttyACM0

Y ahora intento escribir:

obijuan@JANEL:~$ echo "hola" > /dev/ttyACM0 
obijuan@JANEL:~$

Siiiii!!! Bueno... esto ya lo tengo.... Ahora voy a probar con el ide de arduino

SIIIIIIIIIIIIIIIIIIII!!! Ha cargado!!!!!!

Ahora viene ya la prueba definitiva... cargar el Blink!!!

y... NO HA FUNCIONADO!! Su puta madre...

Haciendo pruebas he visto que se ha vuelto a montar el /dev/ttyACM0 en el grupo dialout... así que voy a probar otra cosa...

Mierda!!! He visto que mi usuario NO ESTÁ EN EL GRUPO dialout!!!

obijuan@JANEL:~$ groups
obijuan adm cdrom sudo dip plugdev users lpadmin
obijuan@JANEL:~$ 

Se añade con este comando:

obijuan@JANEL:~$ sudo usermod -a -G dialout obijuan
[sudo] password for obijuan: 
obijuan@JANEL:~$

He tenido algunos problemas... tras reiniciar ya consigo aparecer en el grupo correcto:

obijuan@JANEL:~$ groups
obijuan adm dialout cdrom sudo dip plugdev users lpadmin
obijuan@JANEL:~$ echo "hola" > /dev/ttyACM0
obijuan@JANEL:~$

SIIIIII!!! Joder... ya sí ha funcionado.... Ya tengo el blink!

He modificado el blink para tener un LEDON, que sólo deja el LED encendido. Así puedo cargar un programa u otro y comprobar que efectivamente todo va ok

Dejo aquí un pantallazo con los valores de la configuración. He visto que ha veces el upload method se cambia a picotool y ahí falla la carga

La placa, entonces, ya está lista y operativa con el entorno de Arduino

Ingenieria inversa

Antes de seguir voy a intentar descubrir en qué directorio se generan los ficheros ejecutables. Sería genial incluso poder ver qué comando se han ejecutado exactamente para compilar y cargar... no sé cómo hacer esto en arduino

He encontrado que en la parte de File/preferences está la opción show verbose output during [] compile [] upload. Las he activado, y efectivamente ahora sale más informació en la consola de salida. La reproduzco aquí:

FQBN: rp2040:rp2040:rpipico2:arch=riscv
Using board 'rpipico2' from platform in folder: /home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2
Using core 'rp2040' from platform in folder: /home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2

Detecting libraries used...
/home/obijuan/.arduino15/packages/rp2040/tools/pqt-gcc-riscv/4.0.1-8ec9d6f/bin/riscv32-unknown-elf-g++ -I /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/core -c -Werror=return-type -Wno-psabi -DUSBD_PID=0x000f -DUSBD_VID=0x2e8a -DUSBD_MAX_POWER_MA=250 -DUSB_MANUFACTURER="Raspberry Pi" -DUSB_PRODUCT="Pico 2" -DLWIP_IPV6=0 -DLWIP_IPV4=1 -DLWIP_IGMP=1 -DLWIP_CHECKSUM_CTRL_PER_NETIF=1 -DFILE_COPY_CONSTRUCTOR_SELECT=FILE_COPY_CONSTRUCTOR_PUBLIC -DUSE_UTF8_LONG_NAMES=1 -DDISABLE_FS_H_WARNING=1 -DARDUINO_VARIANT="rpipico2" -DPICO_FLASH_SIZE_BYTES=4194304 @/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/platform_def.txt -march=rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb -mabi=ilp32 -ffunction-sections -fdata-sections -fno-exceptions -iprefix/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/ @/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/platform_inc.txt @/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/core_inc.txt -I/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/include -fno-rtti -std=gnu++17 -g -pipe -w -x c++ -E -CC -DF_CPU=150000000L -DARDUINO=10607 -DARDUINO_RASPBERRY_PI_PICO_2 -DBOARD_NAME="RASPBERRY_PI_PICO_2" -DARDUINO_ARCH_RP2040 -Os -DWIFICC=CYW43_COUNTRY_WORLDWIDE -I/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/cores/rp2040 -I/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/variants/rpipico2 /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/sketch/Test1.ino.cpp -o /dev/null
Generating function prototypes...
/home/obijuan/.arduino15/packages/rp2040/tools/pqt-gcc-riscv/4.0.1-8ec9d6f/bin/riscv32-unknown-elf-g++ -I /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/core -c -Werror=return-type -Wno-psabi -DUSBD_PID=0x000f -DUSBD_VID=0x2e8a -DUSBD_MAX_POWER_MA=250 -DUSB_MANUFACTURER="Raspberry Pi" -DUSB_PRODUCT="Pico 2" -DLWIP_IPV6=0 -DLWIP_IPV4=1 -DLWIP_IGMP=1 -DLWIP_CHECKSUM_CTRL_PER_NETIF=1 -DFILE_COPY_CONSTRUCTOR_SELECT=FILE_COPY_CONSTRUCTOR_PUBLIC -DUSE_UTF8_LONG_NAMES=1 -DDISABLE_FS_H_WARNING=1 -DARDUINO_VARIANT="rpipico2" -DPICO_FLASH_SIZE_BYTES=4194304 @/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/platform_def.txt -march=rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb -mabi=ilp32 -ffunction-sections -fdata-sections -fno-exceptions -iprefix/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/ @/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/platform_inc.txt @/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/core_inc.txt -I/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/include -fno-rtti -std=gnu++17 -g -pipe -w -x c++ -E -CC -DF_CPU=150000000L -DARDUINO=10607 -DARDUINO_RASPBERRY_PI_PICO_2 -DBOARD_NAME="RASPBERRY_PI_PICO_2" -DARDUINO_ARCH_RP2040 -Os -DWIFICC=CYW43_COUNTRY_WORLDWIDE -I/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/cores/rp2040 -I/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/variants/rpipico2 /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/sketch/Test1.ino.cpp -o /tmp/2011513287/sketch_merged.cpp
/home/obijuan/.arduino15/packages/builtin/tools/ctags/5.8-arduino11/ctags -u --language-force=c++ -f - --c++-kinds=svpf --fields=KSTtzns --line-directives /tmp/2011513287/sketch_merged.cpp

Compiling sketch...
/home/obijuan/.arduino15/packages/rp2040/tools/pqt-python3/1.0.1-base-3a57aed-1/python3 -I /home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/tools/signing.py --mode header --publickey /home/obijuan/Arduino/Test1/public.key --out /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/core/Updater_Signing.h
/home/obijuan/.arduino15/packages/rp2040/tools/pqt-gcc-riscv/4.0.1-8ec9d6f/bin/riscv32-unknown-elf-g++ -I /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/core -c -Werror=return-type -Wno-psabi -DUSBD_PID=0x000f -DUSBD_VID=0x2e8a -DUSBD_MAX_POWER_MA=250 "-DUSB_MANUFACTURER=\"Raspberry Pi\"" "-DUSB_PRODUCT=\"Pico 2\"" -DLWIP_IPV6=0 -DLWIP_IPV4=1 -DLWIP_IGMP=1 -DLWIP_CHECKSUM_CTRL_PER_NETIF=1 -DFILE_COPY_CONSTRUCTOR_SELECT=FILE_COPY_CONSTRUCTOR_PUBLIC -DUSE_UTF8_LONG_NAMES=1 -DDISABLE_FS_H_WARNING=1 "-DARDUINO_VARIANT=\"rpipico2\"" -DPICO_FLASH_SIZE_BYTES=4194304 @/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/platform_def.txt -march=rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb -mabi=ilp32 -ffunction-sections -fdata-sections -fno-exceptions -MMD -iprefix/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/ @/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/platform_inc.txt @/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/core_inc.txt -I/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/include -fno-rtti -std=gnu++17 -g -pipe -DF_CPU=150000000L -DARDUINO=10607 -DARDUINO_RASPBERRY_PI_PICO_2 "-DBOARD_NAME=\"RASPBERRY_PI_PICO_2\"" -DARDUINO_ARCH_RP2040 -Os -DWIFICC=CYW43_COUNTRY_WORLDWIDE -I/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/cores/rp2040 -I/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/variants/rpipico2 /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/sketch/Test1.ino.cpp -o /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/sketch/Test1.ino.cpp.o
Compiling libraries...
Compiling core...
Using precompiled core: /home/obijuan/.cache/arduino/cores/f91cdcca11b77092aaa03390a3ec89b0/core.a
Linking everything together...
/home/obijuan/.arduino15/packages/rp2040/tools/pqt-python3/1.0.1-base-3a57aed-1/python3 -I /home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/tools/simplesub.py --input /home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/memmap_default.ld --out /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/memmap_default.ld --sub __FLASH_LENGTH__ 4186112 --sub __EEPROM_START__ 272621568 --sub __FS_START__ 272621568 --sub __FS_END__ 272621568 --sub __RAM_LENGTH__ 512k --sub __PSRAM_LENGTH__ 0x000000
/home/obijuan/.arduino15/packages/rp2040/tools/pqt-gcc-riscv/4.0.1-8ec9d6f/bin/riscv32-unknown-elf-gcc -Werror=return-type -Wno-psabi -DUSBD_PID=0x000f -DUSBD_VID=0x2e8a -DUSBD_MAX_POWER_MA=250 "-DUSB_MANUFACTURER=\"Raspberry Pi\"" "-DUSB_PRODUCT=\"Pico 2\"" -DLWIP_IPV6=0 -DLWIP_IPV4=1 -DLWIP_IGMP=1 -DLWIP_CHECKSUM_CTRL_PER_NETIF=1 -DFILE_COPY_CONSTRUCTOR_SELECT=FILE_COPY_CONSTRUCTOR_PUBLIC -DUSE_UTF8_LONG_NAMES=1 -DDISABLE_FS_H_WARNING=1 "-DARDUINO_VARIANT=\"rpipico2\"" -DPICO_FLASH_SIZE_BYTES=4194304 @/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/platform_def.txt -march=rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb -mabi=ilp32 -ffunction-sections -fdata-sections -fno-exceptions -Os -u _printf_float -u _scanf_float -c /home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/boot2/rp2350-riscv/none.S -I/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/pico-sdk/src/rp2350-riscv/hardware_regs/include/ -I/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/pico-sdk/src/common/pico_binary_info/include -o /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/boot2.o
/home/obijuan/.arduino15/packages/rp2040/tools/pqt-gcc-riscv/4.0.1-8ec9d6f/bin/riscv32-unknown-elf-g++ -L/home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377 -Werror=return-type -Wno-psabi -DUSBD_PID=0x000f -DUSBD_VID=0x2e8a -DUSBD_MAX_POWER_MA=250 "-DUSB_MANUFACTURER=\"Raspberry Pi\"" "-DUSB_PRODUCT=\"Pico 2\"" -DLWIP_IPV6=0 -DLWIP_IPV4=1 -DLWIP_IGMP=1 -DLWIP_CHECKSUM_CTRL_PER_NETIF=1 -DFILE_COPY_CONSTRUCTOR_SELECT=FILE_COPY_CONSTRUCTOR_PUBLIC -DUSE_UTF8_LONG_NAMES=1 -DDISABLE_FS_H_WARNING=1 "-DARDUINO_VARIANT=\"rpipico2\"" -DPICO_FLASH_SIZE_BYTES=4194304 @/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/platform_def.txt -march=rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb -mabi=ilp32 -ffunction-sections -fdata-sections -fno-exceptions -Os -u _printf_float -u _scanf_float @/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/platform_wrap.txt @/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/core_wrap.txt -Wl,--cref -Wl,--check-sections -Wl,--gc-sections -Wl,--unresolved-symbols=report-all -Wl,--warn-common -Wl,--undefined=runtime_init_install_ram_vector_table -Wl,--undefined=__pre_init_runtime_init_clocks -Wl,--undefined=__pre_init_runtime_init_bootrom_reset -Wl,--undefined=__pre_init_runtime_init_early_resets -Wl,--undefined=__pre_init_runtime_init_usb_power_down -Wl,--undefined=__pre_init_runtime_init_clocks -Wl,--undefined=__pre_init_runtime_init_post_clock_resets -Wl,--undefined=__pre_init_runtime_init_spin_locks_reset -Wl,--undefined=__pre_init_runtime_init_boot_locks_reset -Wl,--undefined=__pre_init_runtime_init_bootrom_locking_enable -Wl,--undefined=__pre_init_runtime_init_mutex -Wl,--undefined=__pre_init_runtime_init_default_alarm_pool -Wl,--undefined=__pre_init_first_per_core_initializer -Wl,--undefined=__pre_init_runtime_init_per_core_bootrom_reset -Wl,--undefined=__pre_init_runtime_init_per_core_h3_irq_registers -Wl,--undefined=__pre_init_runtime_init_per_core_irq_priorities -Wl,--script=/home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/memmap_default.ld -Wl,-Map,/home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/Test1.ino.map -o /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/Test1.ino.elf -Wl,--no-warn-rwx-segments -Wl,--start-group /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/sketch/Test1.ino.cpp.o /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/../../cores/f91cdcca11b77092aaa03390a3ec89b0/core.a /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/boot2.o /home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/ota.o /home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/libpico.a /home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/libipv4.a /home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/lib/rp2350-riscv/libbearssl.a -lm -lc -lstdc++ -lc -Wl,--end-group
/home/obijuan/.arduino15/packages/rp2040/tools/pqt-gcc-riscv/4.0.1-8ec9d6f/bin//riscv32-unknown-elf-objcopy -Obinary /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/Test1.ino.elf /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/Test1.ino.bin
/home/obijuan/.arduino15/packages/rp2040/tools/pqt-python3/1.0.1-base-3a57aed-1/python3 -I /home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/tools/signing.py --mode sign --privatekey /home/obijuan/Arduino/Test1/private.key --bin /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/Test1.ino.bin --out /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/Test1.ino.bin.signed
/home/obijuan/.arduino15/packages/rp2040/tools/pqt-picotool/4.0.1-8ec9d6f/picotool uf2 convert /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/Test1.ino.elf /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/Test1.ino.uf2 --family rp2350-riscv --abs-block
RP2350-E9: Adding absolute block to UF2 targeting 0x10ffff00
/home/obijuan/.arduino15/packages/rp2040/tools/pqt-gcc-riscv/4.0.1-8ec9d6f/bin/riscv32-unknown-elf-size -A /home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/Test1.ino.elf
Sketch uses 64508 bytes (1%) of program storage space. Maximum is 4186112 bytes.
Global variables use 22064 bytes (4%) of dynamic memory, leaving 502224 bytes for local variables. Maximum is 524288 bytes.
"/home/obijuan/.arduino15/packages/rp2040/tools/pqt-python3/1.0.1-base-3a57aed-1/python3" -I "/home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/tools/uf2conv.py" --serial "/dev/ttyACM0" --family RP2040 --deploy "/home/obijuan/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/Test1.ino.uf2"
Resetting /dev/ttyACM0
Converting to uf2, output size: 189952, start address: 0x2000
Scanning for RP2040 devices
Flashing /media/obijuan/RP2350 (RP2350)
Wrote 189952 bytes to /media/obijuan/RP2350/NEW.UF2

Hay muchos parámetros que se pasan, y los paths son larguísimo... Voy a ir desenmarañando un poco todo

Las fuentes se guardan en esta ruta ~/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/sketch. Ese número largo es un número único que se genera. Los ficheros que nos encontramos ahí son:

obijuan@JANEL:~/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/sketch$ ls
Test1.ino.cpp  Test1.ino.cpp.d  Test1.ino.cpp.o
obijuan@JANEL:~/.cache/arduino/sketches/9D0067A39D552051537EA5CF80B2B377/sketch$
  • Test1.ino.cpp: Son las fuentes originales, ligeramente modificadas
#include <Arduino.h>
#line 1 "/home/obijuan/Arduino/Test1/Test1.ino"
#line 1 "/home/obijuan/Arduino/Test1/Test1.ino"
void setup();
#line 5 "/home/obijuan/Arduino/Test1/Test1.ino"
void loop();
#line 1 "/home/obijuan/Arduino/Test1/Test1.ino"
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);                    
}

Estos son los comandos ejecutados, sin paths, sin parámetros y sólo dejando lo fundamental

  1. riscv32-unknown-elf-g++ Test1.ino.cpp -o /dev/null

Se compila el fichero Test1.ino.cpp, pero no se genera ejecutable, que se envía a /dev/null. Me imagino que simplemente es para comprobar si está todo ok..

  1. riscv32-unknown-elf-g++ -c Test1.ino.cpp -o /tmp/2011513287/sketch_merged.cpp

Ahora se compila "de verdad" y se deja en un fichero temporal sketch_merged.cpp

  1. ctags -u --language-force=c++ -f /tmp/2011513287/sketch_merged.cpp

El comando ctags no se bien lo que hace, pero en cualquier caso añade etiquetas o meta-información en el archivo antes creado (sketch_merged.cpp)

  1. python3 signing.py --out 9D0067A39D552051537EA5CF80B2B377/core/Updater_Signing.h

Este comando se usa para firmar. Genera el archivo Updater_Signing.h que contiene esto:

#define ARDUINO_SIGNING 0
  1. riscv32-unknown-elf-g++ -c Test1.ino.cpp -o 9D0067A39D552051537EA5CF80B2B377/sketch/Test1.ino.cpp.o

Compilación del sketch

  1. python3 -I simplesub.py --out memmap_default.ld

Creación del fichero del linker script

/* Based on GCC ARM embedded samples.
   Defines the following symbols for use by code:
    __exidx_start
    __exidx_end
    __etext
    __data_start__
    __preinit_array_start
    __preinit_array_end
    __init_array_start
    __init_array_end
    __fini_array_start
    __fini_array_end
    __data_end__
    __bss_start__
    __bss_end__
    __end__
    end
    __HeapLimit
    __StackLimit
    __StackTop
    __stack (== StackTop)
*/


MEMORY
{
    FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 4186112
    PSRAM(rwx) : ORIGIN = 0x11000000, LENGTH = 0x000000
    RAM(rwx) : ORIGIN =  0x20000000, LENGTH = 512k
    SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 4k
    SCRATCH_Y(rwx) : ORIGIN = 0x20081000, LENGTH = 4k
}

PROVIDE ( _EEPROM_start = 272621568 );
PROVIDE ( _FS_start     = 272621568 );
PROVIDE ( _FS_end       = 272621568 );

ENTRY(_entry_point)

SECTIONS
{
    .flash_begin : {
        __flash_binary_start = .;
    } > FLASH

    .ota : {
        /* Start image with OTA */
        KEEP (*(.OTA))
        *ota.o
    } > FLASH

    .partition : {
        . = __flash_binary_start + 0x2ff0;
        LONG(272621568)
        LONG(272621568)
        LONG(272621568)
        LONG(4186112)
    } > FLASH

    /* The bootrom will enter the image at the point indicated in your
       IMAGE_DEF, which is usually the reset handler of your vector table.

       The debugger will use the ELF entry point, which is the _entry_point
       symbol, and in our case is *different from the bootrom's entry point.*
       This is used to go back through the bootrom on debugger launches only,
       to perform the same initial flash setup that would be performed on a
       cold boot.
    */

    .text : {
        KEEP (*(.vectors))
        __logical_binary_start = .;
        KEEP (*(.binary_info_header))
        __binary_info_header_end = .;
        KEEP (*(.embedded_block))
        __embedded_block_end = .;
        KEEP (*(.reset))
        /* TODO revisit this now memset/memcpy/float in ROM */
        /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from
         * FLASH ... we will include any thing excluded here in .data below by default */
        *(.init)
        *libgcc.a:cmse_nonsecure_call.o
        *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*)
        *(.fini)
        /* Pull all c'tors into .text */
        *crtbegin.o(.ctors)
        *crtbegin?.o(.ctors)
        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
        *(SORT(.ctors.*))
        *(.ctors)
        /* Followed by destructors */
        *crtbegin.o(.dtors)
        *crtbegin?.o(.dtors)
        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
        *(SORT(.dtors.*))
        *(.dtors)

        . = ALIGN(4);
        /* preinit data */
        PROVIDE_HIDDEN (__preinit_array_start = .);
        KEEP(*(SORT(.preinit_array.*)))
        KEEP(*(.preinit_array))
        PROVIDE_HIDDEN (__preinit_array_end = .);

        . = ALIGN(4);
        /* init data */
        PROVIDE_HIDDEN (__init_array_start = .);
        KEEP(*(SORT(.init_array.*)))
        KEEP(*(.init_array))
        PROVIDE_HIDDEN (__init_array_end = .);

        . = ALIGN(4);
        /* finit data */
        PROVIDE_HIDDEN (__fini_array_start = .);
        *(SORT(.fini_array.*))
        *(.fini_array)
        PROVIDE_HIDDEN (__fini_array_end = .);

        *(.eh_frame*)
        . = ALIGN(4);
    } > FLASH

    /* Note the boot2 section is optional, and should be discarded if there is
       no reference to it *inside* the binary, as it is not called by the
       bootrom. (The bootrom performs a simple best-effort XIP setup and
       leaves it to the binary to do anything more sophisticated.) However
       there is still a size limit of 256 bytes, to ensure the boot2 can be
       stored in boot RAM.

       Really this is a "XIP setup function" -- the name boot2 is historic and
       refers to its dual-purpose on RP2040, where it also handled vectoring
       from the bootrom into the user image.
    */
/*
    .boot2 : {
        __boot2_start__ = .;
        *(.boot2)
        __boot2_end__ = .;
    } > FLASH

    ASSERT(__boot2_end__ - __boot2_start__ <= 256,
        "ERROR: Pico second stage bootloader must be no more than 256 bytes in size")
*/
    .rodata : {
        *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*)
        *(.srodata*)
        . = ALIGN(4);
        *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*)))
        . = ALIGN(4);
    } > FLASH

    .ARM.extab :
    {
        *(.ARM.extab* .gnu.linkonce.armextab.*)
    } > FLASH

    __exidx_start = .;
    .ARM.exidx :
    {
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
    } > FLASH
    __exidx_end = .;

    /* Machine inspectable binary information */
    . = ALIGN(4);
    __binary_info_start = .;
    .binary_info :
    {
        KEEP(*(.binary_info.keep.*))
        *(.binary_info.*)
    } > FLASH
    __binary_info_end = .;
    . = ALIGN(4);

    .ram_vector_table (NOLOAD): {
        *(.ram_vector_table)
    } > RAM

    .uninitialized_data (NOLOAD): {
        . = ALIGN(4);
        *(.uninitialized_data*)
    } > RAM

    .data : {
        __data_start__ = .;
        *(vtable)

        *(.time_critical*)

        /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */
        *(.text*)
        . = ALIGN(4);
        *(.rodata*)
        . = ALIGN(4);

        *(.data*)
        *(.sdata*)

        . = ALIGN(4);
        *(.after_data.*)
        . = ALIGN(4);
        /* preinit data */
        PROVIDE_HIDDEN (__mutex_array_start = .);
        KEEP(*(SORT(.mutex_array.*)))
        KEEP(*(.mutex_array))
        PROVIDE_HIDDEN (__mutex_array_end = .);

        *(.jcr)
        . = ALIGN(4);
    } > RAM AT> FLASH

    .tdata : {
        . = ALIGN(4);
		*(.tdata .tdata.* .gnu.linkonce.td.*)
        /* All data end */
        __tdata_end = .;
    } > RAM AT> FLASH
    PROVIDE(__data_end__ = .);

    /* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */
    __etext = LOADADDR(.data);

    .tbss (NOLOAD) : {
        . = ALIGN(4);
        __bss_start__ = .;
        __tls_base = .;
        *(.tbss .tbss.* .gnu.linkonce.tb.*)
        *(.tcommon)

        __tls_end = .;
    } > RAM

    .bss (NOLOAD) : {
        . = ALIGN(4);
        __tbss_end = .;

        *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*)))
        *(COMMON)
        PROVIDE(__global_pointer$ = . + 2K);
        *(.sbss*)
        . = ALIGN(4);
        __bss_end__ = .;
    } > RAM

    .heap (NOLOAD):
    {
        __end__ = .;
        end = __end__;
        KEEP(*(.heap*))
        /* historically on GCC sbrk was growing past __HeapLimit to __StackLimit, however
           to be more compatible, we now set __HeapLimit explicitly to where the end of the heap is */
        . = ORIGIN(RAM) + LENGTH(RAM);
        __HeapLimit = .;
    } > RAM

    /* Start and end symbols must be word-aligned */
    .scratch_x : {
        __scratch_x_start__ = .;
        *(.scratch_x.*)
        . = ALIGN(4);
        __scratch_x_end__ = .;
    } > SCRATCH_X AT > FLASH
    __scratch_x_source__ = LOADADDR(.scratch_x);

    .scratch_y : {
        __scratch_y_start__ = .;
        *(.scratch_y.*)
        . = ALIGN(4);
        __scratch_y_end__ = .;
    } > SCRATCH_Y AT > FLASH
    __scratch_y_source__ = LOADADDR(.scratch_y);

    /* .stack*_dummy section doesn't contains any symbols. It is only
     * used for linker to calculate size of stack sections, and assign
     * values to stack symbols later
     *
     * stack1 section may be empty/missing if platform_launch_core1 is not used */

    /* by default we put core 0 stack at the end of scratch Y, so that if core 1
     * stack is not used then all of SCRATCH_X is free.
     */
    .stack1_dummy (NOLOAD):
    {
        *(.stack1*)
    } > SCRATCH_X
    .stack_dummy (NOLOAD):
    {
        KEEP(*(.stack*))
    } > SCRATCH_Y

    .flash_end : {
        KEEP(*(.embedded_end_block*))
        PROVIDE(__flash_binary_end = .);
    } > FLASH = 0xaa

    .psram (NOLOAD) : {
        __psram_start__ = .;
        *(.psram*)
        . = ALIGN(4096);
        __psram_heap_start__ = .;
    } > PSRAM

    /* stack limit is poorly named, but historically is maximum heap ptr */
    __StackLimit = ORIGIN(RAM) + LENGTH(RAM);
    __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X);
    __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y);
    __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy);
    __StackBottom = __StackTop - SIZEOF(.stack_dummy);
    PROVIDE(__stack = __StackTop);

    /* picolibc and LLVM */
    PROVIDE (__heap_start = __end__);
    PROVIDE (__heap_end = __HeapLimit);
    PROVIDE( __tls_align = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss)) );
    PROVIDE( __tls_size_align = (__tls_size + __tls_align - 1) & ~(__tls_align - 1));
    PROVIDE( __arm32_tls_tcb_offset = MAX(8, __tls_align) );

    /* TLSF */
    PROVIDE (__psram_start = __psram_start__);
    PROVIDE (__psram_heap_start = __psram_heap_start__);

    /* llvm-libc */
    PROVIDE (_end = __end__);
    PROVIDE (__llvm_libc_heap_limit = __HeapLimit);

    /* Check if data + heap + stack exceeds RAM limit */
    ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")

    ASSERT( __binary_info_header_end - __logical_binary_start <= 1024, "Binary info must be in first 1024 bytes of the binary")
    ASSERT( __embedded_block_end - __logical_binary_start <= 4096, "Embedded block must be in first 4096 bytes of the binary")

    /* todo assert on extra code */
}
  1. riscv32-unknown-elf-gcc -march=rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb -mabi=ilp32 -c /home/obijuan/.arduino15/packages/rp2040/hardware/rp2040/4.5.2/boot2/rp2350-riscv/none.S -o boot2.o

El fichero none.S se compila a boot2.o. Vamos a echar un vistacillo a este fichero en ensamblador, a ver si saco algo en claro

.section .boot2, "ax"

.global __boot2_entry_point
__boot2_entry_point:

Se define la sección .boot2 y el punto de entrada __boot2_entry_point

  1. riscv32-unknown-elf-g++ -Wl,--script=memmap_default.ld -Wl,-Map,Test1.ino.map -o Test1.ino.elf Test1.ino.cpp.o core.a boot2.o ota.o libpico.a libipv4.a libbearssl.a

Aquí se hace el linkado para generar el fichero ejecutable: Test1.ino.elf

  1. riscv32-unknown-elf-objcopy -Obinary Test1.ino.elf Test1.ino.bin

Se genera el fichero ejecutable binario puro

  1. python3 -I signing.py --bin Test1.ino.bin --out Test1.ino.bin.signed

Se firma el binario puro

  1. picotool uf2 convert Test1.ino.elf Test1.ino.uf2

Se genera el fichero de arranque .uf2 que es el que se mete en el dispositivo usb. Se genera a partir del elf... no de los binarios creados antes (ni el puro ni el firmado)

  1. riscv32-unknown-elf-size -A Test1.ino.elf

Mostrar información sobre el tamaño del ejecutable

  1. python3 -I uf2conv.py --serial "/dev/ttyACM0" --family RP2040 --deploy Test1.ino.uf2

Este comando realiza el flasheado

Conociendo cuál es el ejecutable, se puede desensamblar... el problema es que se linka con cantidad de librerías.. y es un ejecutable muy gordo. Para desensamblaro usamos este comando:

riscv64-unknown-elf-objdump  -m riscv -D -d  -M no-aliases --disassembler-color=on  Test1.ino.elf

Sale mucho código. Si lo redireccionamos a un fichero y lo editamos, encontramos el punto de entrada main. Reproduzco aquí su comienzo:

10003346 <main>:
10003346:	1141                	c.addi	sp,-16
10003348:	00000793          	addi	a5,zero,0
1000334c:	c422                	c.swsp	s0,8(sp)
1000334e:	00f03733          	sltu	a4,zero,a5
10003352:	20005437          	lui	s0,0x20005
10003356:	c606                	c.swsp	ra,12(sp)
10003358:	c226                	c.swsp	s1,4(sp)
1000335a:	c04a                	c.swsp	s2,0(sp)
1000335c:	56e40123          	sb	a4,1378(s0) # 20005562 <__isFreeRTOS>
10003360:	e7a1                	c.bnez	a5,100033a8 <main+0x62>
10003362:	00000793          	addi	a5,zero,0
10003366:	e781                	c.bnez	a5,1000336e <main+0x28>
10003368:	00000793          	addi	a5,zero,0
1000336c:	cf95                	c.beqz	a5,100033a8 <main+0x62>
1000336e:	04c00593          	addi	a1,zero,76
10003372:	4505                	c.li	a0,1
10003374:	2239                	c.jal	10003482 <__wrap_calloc>
10003376:	200057b7          	lui	a5,0x20005
1000337a:	04c00613          	addi	a2,zero,76
1000337e:	4581                	c.li	a1,0
10003380:	84aa                	c.mv	s1,a0
[...]

El código tiene sentido... se compila usando el juego de instrucciones comprimido

Vamos a echar un vistazo al setup, a ver si lo encontramos

10003170 <setup>:
10003170:	4585                	c.li	a1,1
10003172:	4565                	c.li	a0,25
10003174:	a641                	c.j	100034f4 <__pinMode>

Este código tiene mucho sentido. Se mete en a0 el pin del LED. Y en el a1 el valor 1 (que no tengo claro lo que es todavía). Y luego salta a la función __pinMode

100034f4 <__pinMode>:
100034f4:	47f5                	c.li	a5,29
100034f6:	0ca7e963          	bltu	a5,a0,100035c8 <__pinMode+0xd4>
100034fa:	1141                	c.addi	sp,-16
100034fc:	c226                	c.swsp	s1,4(sp)
100034fe:	c606                	c.swsp	ra,12(sp)
10003500:	c422                	c.swsp	s0,8(sp)
10003502:	47a1                	c.li	a5,8
10003504:	84ae                	c.mv	s1,a1
10003506:	0ab7ec63          	bltu	a5,a1,100035be <__pinMode+0xca>
1000350a:	100127b7          	lui	a5,0x10012
1000350e:	1a078793          	addi	a5,a5,416 # 100121a0 <__EH_FRAME_BEGIN__+0x36c>
10003512:	20f5c7b3          	sh2add	a5,a1,a5
10003516:	439c                	c.lw	a5,0(a5)
10003518:	842a                	c.mv	s0,a0
1000351a:	8782                	c.jr	a5
1000351c:	22a010ef          	jal	ra,10004746 <gpio_init>
10003520:	4581                	c.li	a1,0
10003522:	8522                	c.mv	a0,s0
10003524:	377d                	c.jal	100034d2 <gpio_set_dir>
10003526:	4601                	c.li	a2,0
10003528:	4581                	c.li	a1,0
1000352a:	8522                	c.mv	a0,s0
1000352c:	1e0010ef          	jal	ra,1000470c <gpio_set_pulls>
10003530:	200057b7          	lui	a5,0x20005
10003534:	90878793          	addi	a5,a5,-1784 # 20004908 <_ZL3_pm>
10003538:	20f447b3          	sh2add	a5,s0,a5
1000353c:	c384                	c.sw	s1,0(a5)
1000353e:	47e5                	c.li	a5,25
10003540:	0687ff63          	bgeu	a5,s0,100035be <__pinMode+0xca>
10003544:	8522                	c.mv	a0,s0
10003546:	4422                	c.lwsp	s0,8(sp)
10003548:	40b2                	c.lwsp	ra,12(sp)
1000354a:	4492                	c.lwsp	s1,4(sp)
1000354c:	0141                	c.addi	sp,16
1000354e:	0c60106f          	jal	zero,10004614 <_Z13__clearADCPinh>

Lo interesante es que se llama a funciones interesantes: gp_io_init, <gpio_set_dir>, <gpio_set_pulls>. Vamos a echar un vistazo a ver qué es lo que hacen...

No saco nada en claro porque están con el juego comprimido... Voy a analizar loop a ver...

10003176 <loop>:
10003176:	4585                	c.li	a1,1
10003178:	4565                	c.li	a0,25
1000317a:	a981                	c.j	100035ca <__digitalWrite>

Se llama a la función DigitalWrite pasándole como argumentos a0=25, que es el pin, y a1 es el estado 1

Vamos a ver la función digitalWrite.... ummm está... pero no saco nada en claro

Hacia el Hola mundo

Dejamos el entorno de Arduino. Está muy bien para hacer las primeras pruebas, y para comprobar cosas.. sin embargo lo veo complicado para llegar al bajo nivel...

El objetivo es hacer un "Hola mundo" en ensamblador y conocer la manera de cargarlo en la placa... Voy a buscar información sobre el tema

Con VSCode hay una extensión... pero de momento voy a instalar las herramientas "a mano" para entenderlas bien. Las instrucciones las he encontrado en este PDF: https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf, En el apéndice C (Página 31 del pdf)

Lo primero es bajarse el script "pico_setup.sh". Me lo bajo en el home:

obijuan@JANEL:~$ wget https://raw.githubusercontent.com/raspberrypi/pico-setup/master/pico_setup.sh 
--2025-04-25 20:00:28--  https://raw.githubusercontent.com/raspberrypi/pico-setup/master/pico_setup.sh
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.111.133, 185.199.108.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3344 (3.3K) [text/plain]
Saving to: ‘pico_setup.sh’

pico_setup.sh       100%[===================>]   3.27K  --.-KB/s    in 0s      

2025-04-25 20:00:29 (24.0 MB/s) - ‘pico_setup.sh’ saved [3344/3344]

obijuan@JANEL:~$ 

OK.... casi la cago... lo anterior era para desarrollar desde una raspberry pi.... Las instrucciones están en la siguiente página:

Hay que clonarse repositorios de github:

obijuan@JANEL:~$ cd Develop/
obijuan@JANEL:~/Develop$ mkdir pico
obijuan@JANEL:~/Develop$ cd pico
obijuan@JANEL:~/Develop/pico$ git clone https://github.com/raspberrypi/pico-sdk.git --branch master
[...]
obijuan@JANEL:~/Develop/pico$ cd pico-sdk/
obijuan@JANEL:~/Develop/pico/pico-sdk$ git submodule update --init
[...]
obijuan@JANEL:~/Develop/pico/pico-sdk$ cd ..
obijuan@JANEL:~/Develop/pico$ git clone https://github.com/raspberrypi/pico-examples.git --branch master

Vamos a probar el hola mundo:

obijuan@JANEL:~/Develop/pico$ cd pico-examples/
obijuan@JANEL:~/Develop/pico/pico-examples$ mkdir build
obijuan@JANEL:~/Develop/pico/pico-examples$ cd build/
obijuan@JANEL:~/Develop/pico/pico-examples$ export PICO_SDK_PATH=../../pico-sdk

Mierda... hay que usar el cmake y peta porque no encuentra la toolchain... pero es que está buscando la de arm!!! Joder...

Voy a leer esto:

https://github.com/raspberrypi/pico-sdk

No me queda claro... joder... el cmake me sigue petando... así que voy a seguir las instrucciones como para usar arm. Tenog que instalar esto:

sudo apt install g++ libstdc++-arm-none-eabi-newlib

Ahora voy a preparar los ejemplos. Al crear el cmake le digo que quiero el riscv, con estos parámetros que he sacado de la documentación:

obijuan@JANEL:~/Develop/pico/pico-examples/build$ cmake -DPICO_BOARD='pico2' -DPICO_PLATFORM=rp2350-riscv ..
PICO_SDK_PATH is /home/obijuan/Develop/pico/pico-sdk
Target board (PICO_BOARD) is 'pico2'.
Using board configuration from /home/obijuan/Develop/pico/pico-sdk/src/boards/include/boards/pico2.h
Pico Platform (PICO_PLATFORM) is 'rp2350-riscv'.
Defaulting compiler (PICO_COMPILER) to 'pico_arm_cortex_m0plus_gcc' since not specified.
Configuring toolchain based on PICO_COMPILER 'pico_arm_cortex_m0plus_gcc'
-- The C compiler identification is GNU 13.2.1
-- The CXX compiler identification is GNU 13.2.1
-- The ASM compiler identification is GNU
-- Found assembler: /usr/bin/arm-none-eabi-gcc
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/arm-none-eabi-gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/arm-none-eabi-g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
Build type is Release
-- Found Python3: /usr/bin/python3 (found version "3.12.3") found components: Interpreter 
TinyUSB available at /home/obijuan/Develop/pico/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040; enabling build support for USB.
BTstack available at /home/obijuan/Develop/pico/pico-sdk/lib/btstack
cyw43-driver available at /home/obijuan/Develop/pico/pico-sdk/lib/cyw43-driver
lwIP available at /home/obijuan/Develop/pico/pico-sdk/lib/lwip
mbedtls available at /home/obijuan/Develop/pico/pico-sdk/lib/mbedtls
CMake Warning at /home/obijuan/Develop/pico/pico-sdk/tools/Findpicotool.cmake:30 (message):
  No installed picotool with version 2.1.1 found - building from source

  It is recommended to build and install picotool separately, or to set
  PICOTOOL_FETCH_FROM_GIT_PATH to a common directory for all your SDK
  projects
Call Stack (most recent call first):
  /home/obijuan/Develop/pico/pico-sdk/tools/CMakeLists.txt:138 (find_package)
  /home/obijuan/Develop/pico/pico-sdk/tools/CMakeLists.txt:493 (pico_init_picotool)
  /home/obijuan/Develop/pico/pico-sdk/src/cmake/on_device.cmake:56 (picotool_postprocess_binary)
  blink/CMakeLists.txt:13 (pico_add_extra_outputs)


Downloading Picotool
Only building blink_any for non W boards as PICO_CYW43_SUPPORTED is not set
Skipping encrypted example which is unsupported on this platform
Skipping hello_dcp example which is unsupported on this platform
Skipping cache_perfctr example which is unsupported on this platform
Skipping ssi_dma example which is unsupported on this platform
Skipping multicore_fifo_irqs example which is unsupported on this platform
Skipping RTC examples as hardware_rtc is unavailable on this platform
Skipping universal examples as PICO_RISCV_TOOLCHAIN_PATH and PICO_ARM_TOOLCHAIN_PATH are not defined
Skipping TinyUSB dual examples, as TinyUSB hw/mcu/raspberry_pi/Pico-PIO-USB submodule unavailable
Skipping FreeRTOS examples as FREERTOS_KERNEL_PATH not defined
-- Configuring done (2.6s)
-- Generating done (1.5s)
-- Build files have been written to: /home/obijuan/Develop/pico/pico-examples/build
obijuan@JANEL:~/Develop/pico/pico-examples/build$

Esto NO ha petado...

Para compilar el blink hay que hacer:

obijuan@JANEL:~/Develop/pico/pico-examples/build$ cd blink
obijuan@JANEL:~/Develop/pico/pico-examples/build/blink$ ls
CMakeFiles  cmake_install.cmake  Makefile  picotool
obijuan@JANEL:~/Develop/pico/pico-examples/build/blink$

Ahora ejecutamos make

obijuan@JANEL:~/Develop/pico/pico-examples/build/blink$ make
Built target picotoolForceReconfigure
Creating directories for 'picotoolBuild'
No download step for 'picotoolBuild'
No update step for 'picotoolBuild'
No patch step for 'picotoolBuild'
Performing configure step for 'picotoolBuild'
Not searching for unused variables given on the command line.
-- The C compiler identification is GNU 13.3.0
-- The CXX compiler identification is GNU 13.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
.//home/obijuan/Develop/pico/pico-examples/build/_deps/picotool/
-- Using the single-header code from /home/obijuan/Develop/pico/pico-examples/build/_deps/picotool-src/lib/nlohmann_json/single_include/
PICOTOOL_NO_LIBUSB is set - no USB support will be built
-- Configuring done (0.3s)
-- Generating done (0.0s)
-- Build files have been written to: /home/obijuan/Develop/pico/pico-examples/build/_deps/picotool-build
Performing build step for 'picotoolBuild'
[ 83%] Built target mbedtls
[ 85%] Built target errors
[ 88%] Built target elf
[ 91%] Built target bintool
[ 93%] Built target elf2uf2
[100%] Built target picotool
Performing install step for 'picotoolBuild'
[ 83%] Built target mbedtls
[ 85%] Built target errors
[ 88%] Built target elf
[ 91%] Built target bintool
[ 93%] Built target elf2uf2
[100%] Built target picotool
Install the project...
-- Install configuration: "Release"
Performing test step for 'picotoolBuild'
picotool v2.1.2-develop (Linux, GNU-13.3.0, Release)
Completed 'picotoolBuild'
Built target picotoolBuild
Building ASM object pico-sdk/src/rp2350/boot_stage2/CMakeFiles/bs2_default.dir/compile_time_choice.S.o
Linking ASM executable bs2_default.elf
Built target bs2_default
Generating bs2_default.bin
Generating bs2_default_padded_checksummed.S
Building ASM object pico-sdk/src/rp2350/boot_stage2/CMakeFiles/bs2_default_library.dir/bs2_default_padded_checksummed.S.o
Built target bs2_default_library
Building C object blink/CMakeFiles/blink.dir/blink.c.o
/tmp/ccsWr6Mz.s: Assembler messages:
/tmp/ccsWr6Mz.s:59: Error: selected processor does not support `mcrr p0,#4,r3,r2,c4' in Thumb mode
/tmp/ccsWr6Mz.s:106: Error: selected processor does not support `mcrr p0,#4,r3,r0,c0' in Thumb mode
/tmp/ccsWr6Mz.s:171: Error: selected processor does not support `mcrr p0,#4,r3,r2,c4' in Thumb mode
/tmp/ccsWr6Mz.s:234: Error: selected processor does not support `mcrr p0,#4,r4,r6,c0' in Thumb mode
/tmp/ccsWr6Mz.s:265: Error: selected processor does not support `mcrr p0,#4,r4,r5,c0' in Thumb mode
make[2]: *** [blink/CMakeFiles/blink.dir/build.make:76: blink/CMakeFiles/blink.dir/blink.c.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:5640: blink/CMakeFiles/blink.dir/all] Error 2
make: *** [Makefile:91: all] Error 2
obijuan@JANEL:~/Develop/pico/pico-examples/build/blink$

Joder.. hace mil cosas... Y PETA!!

2025-04-26

Hacia el Hola Mundo

Voy a seguir este tutorial: https://www.cnx-software.com/2024/08/31/using-risc-v-cores-on-the-raspberry-pi-pico-2-board-and-rp2350-mcu-from-blinking-an-led-to-building-linux/

Primero lo ponen en marcha con el ARM y luego con el risc-v para comparar el rendimiento

Instalamos las herramientas necesarias:

sudo apt install cmake gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential

Es la hora de compilar el ejemplo blink_simple:

obijuan@JANEL:~/Develop/pico$ cd pico-examples/blink_simple/
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple$ export PICO_SDK_PATH=../../pico-sdk

Hacemos el cmake. Todo parece funcionar ok

obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple$ cmake -DPICO_PLATFORM=rp2350 ..
Using PICO_SDK_PATH from environment ('../../pico-sdk')
PICO_SDK_PATH is /home/obijuan/Develop/pico/pico-sdk
Defaulting target board (PICO_BOARD) to 'pico2' since not specified.
Using board configuration from /home/obijuan/Develop/pico/pico-sdk/src/boards/include/boards/pico2.h
Pico Platform (PICO_PLATFORM) is 'rp2350-arm-s'.
-- Defaulting build type to 'Release' since not specified.
Defaulting compiler (PICO_COMPILER) to 'pico_arm_cortex_m33_gcc' since not specified.
Configuring toolchain based on PICO_COMPILER 'pico_arm_cortex_m33_gcc'
Defaulting PICO_GCC_TRIPLE to 'arm-none-eabi'
-- The C compiler identification is GNU 13.2.1
-- The CXX compiler identification is GNU 13.2.1
-- The ASM compiler identification is GNU
-- Found assembler: /usr/bin/arm-none-eabi-gcc
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/arm-none-eabi-gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/arm-none-eabi-g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
Build type is Release
CMake Warning at /home/obijuan/Develop/pico/pico-sdk/tools/Findpicotool.cmake:30 (message):
  No installed picotool with version 2.1.1 found - building from source

  It is recommended to build and install picotool separately, or to set
  PICOTOOL_FETCH_FROM_GIT_PATH to a common directory for all your SDK
  projects
Call Stack (most recent call first):
  /home/obijuan/Develop/pico/pico-sdk/tools/CMakeLists.txt:138 (find_package)
  /home/obijuan/Develop/pico/pico-sdk/src/cmake/on_device.cmake:34 (pico_init_picotool)
  /home/obijuan/Develop/pico/pico-sdk/src/rp2350/boot_stage2/CMakeLists.txt:57 (pico_add_dis_output)
  /home/obijuan/Develop/pico/pico-sdk/src/rp2350/boot_stage2/CMakeLists.txt:98 (pico_define_boot_stage2)


Downloading Picotool
-- Found Python3: /usr/bin/python3 (found version "3.12.3") found components: Interpreter 
TinyUSB available at /home/obijuan/Develop/pico/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040; enabling build support for USB.
BTstack available at /home/obijuan/Develop/pico/pico-sdk/lib/btstack
cyw43-driver available at /home/obijuan/Develop/pico/pico-sdk/lib/cyw43-driver
lwIP available at /home/obijuan/Develop/pico/pico-sdk/lib/lwip
mbedtls available at /home/obijuan/Develop/pico/pico-sdk/lib/mbedtls
Only building blink_any for non W boards as PICO_CYW43_SUPPORTED is not set
Skipping cache_perfctr example which is unsupported on this platform
Skipping ssi_dma example which is unsupported on this platform
Skipping multicore_fifo_irqs example which is unsupported on this platform
Skipping RTC examples as hardware_rtc is unavailable on this platform
Skipping universal examples as PICO_RISCV_TOOLCHAIN_PATH and PICO_ARM_TOOLCHAIN_PATH are not defined
Skipping TinyUSB dual examples, as TinyUSB hw/mcu/raspberry_pi/Pico-PIO-USB submodule unavailable
Skipping FreeRTOS examples as FREERTOS_KERNEL_PATH not defined
-- Configuring done (2.7s)
-- Generating done (1.4s)
-- Build files have been written to: /home/obijuan/Develop/pico/pico-examples/blink_simple
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple$

Ahora vamos a compilar

obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple$ cd blink_simple
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$ 
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$ make
[  0%] Built target picotoolForceReconfigure
[  0%] Creating directories for 'picotoolBuild'
[  0%] No download step for 'picotoolBuild'
[  0%] No update step for 'picotoolBuild'
[  0%] No patch step for 'picotoolBuild'
[  0%] Performing configure step for 'picotoolBuild'
Not searching for unused variables given on the command line.
-- The C compiler identification is GNU 13.3.0
-- The CXX compiler identification is GNU 13.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
.//home/obijuan/Develop/pico/pico-examples/blink_simple/_deps/picotool/
-- Using the single-header code from /home/obijuan/Develop/pico/pico-examples/blink_simple/_deps/picotool-src/lib/nlohmann_json/single_include/
PICOTOOL_NO_LIBUSB is set - no USB support will be built
-- Configuring done (0.3s)
-- Generating done (0.0s)
-- Build files have been written to: /home/obijuan/Develop/pico/pico-examples/blink_simple/_deps/picotool-build
[  0%] Performing build step for 'picotoolBuild'
[ 83%] Built target mbedtls
[ 85%] Built target errors
[ 88%] Built target elf
[ 91%] Built target bintool
[ 93%] Built target elf2uf2
[100%] Built target picotool
[  0%] Performing install step for 'picotoolBuild'
[ 83%] Built target mbedtls
[ 85%] Built target errors
[ 88%] Built target elf
[ 91%] Built target bintool
[ 93%] Built target elf2uf2
[100%] Built target picotool
Install the project...
-- Install configuration: "Release"
[  0%] Performing test step for 'picotoolBuild'
picotool v2.1.2-develop (Linux, GNU-13.3.0, Release)
[  0%] Completed 'picotoolBuild'
[  0%] Built target picotoolBuild
[  0%] Building ASM object pico-sdk/src/rp2350/boot_stage2/CMakeFiles/bs2_default.dir/compile_time_choice.S.o
[  0%] Linking ASM executable bs2_default.elf
[  0%] Built target bs2_default
[  0%] Generating bs2_default.bin
[ 50%] Generating bs2_default_padded_checksummed.S
[ 50%] Building ASM object pico-sdk/src/rp2350/boot_stage2/CMakeFiles/bs2_default_library.dir/bs2_default_padded_checksummed.S.o
[ 50%] Built target bs2_default_library
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/blink_simple.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_stdlib/stdlib.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_gpio/gpio.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2350/pico_platform/platform.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_platform_panic/panic.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/hardware_claim/claim.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_sync/sync.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_sync_spin_lock/sync_spin_lock.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_irq/irq.c.o
[ 50%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_irq/irq_handler_chain.S.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_sync/sem.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_sync/lock_core.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_sync/mutex.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_sync/critical_section.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_time/time.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_time/timeout_helper.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_timer/timer.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_util/datetime.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_util/pheap.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_util/queue.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_uart/uart.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_clocks/clocks.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_pll/pll.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_vreg/vreg.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_watchdog/watchdog.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_ticks/ticks.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_bootrom/bootrom.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_bootrom/bootrom_lock.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_boot_lock/boot_lock.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_flash/flash.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_xosc/xosc.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_divider/divider.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_runtime/runtime.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_runtime_init/runtime_init.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_runtime_init/runtime_init_clocks.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_runtime_init/runtime_init_stack_guard.c.o
[ 50%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_bit_ops/bit_ops_aeabi.S.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_divider/divider_compiler.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_double/double_math.c.o
[ 50%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_double/double_aeabi_dcp.S.o
[ 50%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_double/double_fma_dcp.S.o
[ 50%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_double/double_sci_m33.S.o
[ 50%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_double/double_conv_m33.S.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_float/float_math.c.o
[ 50%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_float/float_conv32_vfp.S.o
[ 50%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_float/float_common_m33.S.o
[100%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_float/float_sci_m33_vfp.S.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_malloc/malloc.c.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_atomic/atomic.c.o
[100%] Building CXX object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_cxx_options/new_delete.cpp.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_standard_binary_info/standard_binary_info.c.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_printf/printf.c.o
[100%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_crt0/crt0.S.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_clib_interface/newlib_interface.c.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_stdio/stdio.c.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_stdio_uart/stdio_uart.c.o
[100%] Linking CXX executable blink_simple.elf
[100%] Built target blink_simple
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$ 

Como es la primera vez, lo primero que se hace es compilar las herramientas necesarias. Luego se generan los ejecutables

obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$ ls
blink_simple.bin  blink_simple.elf.map  CMakeFiles
blink_simple.dis  blink_simple.hex      cmake_install.cmake
blink_simple.elf  blink_simple.uf2      Makefile
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$

Comprobamos que el ejecutable generado es efectivamente para el ARM:

obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$ file blink_simple.elf
blink_simple.elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$

Para cargarlo montamos la raspberry pi como unidad de almacenamiento, conectándola al usb con el botón de boot apretado, y copiamos el fichero blink_simple.uf2:

obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$ cp blink_simple.uf2 /media/obijuan/RP2350

El blinky ha cargado (muy rápidamente) y está funcionando. Todo ok

Ahora vamos a compilarlo para el riscv

Instala la toolchain... Yo en principio ya la tengo instalada, pero para asegurarme voy a instalar la misma del artículo

Esta es la versión que tengo instalada en ubuntu 24.04:

obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$ riscv64-unknown-elf-gcc --version
riscv64-unknown-elf-gcc (13.2.0-11ubuntu1+12) 13.2.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$

Seguimos las instrucciones, a ver qué pasa...

obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$ cd ../../..
obijuan@JANEL:~/Develop/pico$

Descargamos la toolchain. NO hay para ubuntu 24.04.. así que cruzo los dedos e instalo la misma que el autor del artículo:

obijuan@JANEL:~/Develop/pico$ wget https://buildbot.embecosm.com/job/corev-gcc-ubuntu2204/47/artifact/corev-openhw-gcc-ubuntu2204-20240530.tar.gz
--2025-04-26 09:10:31--  https://buildbot.embecosm.com/job/corev-gcc-ubuntu2204/47/artifact/corev-openhw-gcc-ubuntu2204-20240530.tar.gz
Resolving buildbot.embecosm.com (buildbot.embecosm.com)... 212.69.42.53
Connecting to buildbot.embecosm.com (buildbot.embecosm.com)|212.69.42.53|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1783523123 (1.7G) [application/gzip]
Saving to: ‘corev-openhw-gcc-ubuntu2204-20240530.tar.gz’

corev-openhw-gcc-ub 100%[===================>]   1.66G  48.7MB/s    in 39s     

2025-04-26 09:11:11 (43.3 MB/s) - ‘corev-openhw-gcc-ubuntu2204-20240530.tar.gz’ saved [1783523123/1783523123]

obijuan@JANEL:~/Develop/pico$ tar xvf corev-openhw-gcc-ubuntu2204-20240530.tar.gz 
obijuan@JANEL:~/Develop/pico$ export PICO_TOOLCHAIN_PATH=~/edev/Raspberry-Pi-Pico-2/corev-openhw-gcc-ubuntu2204-20240530
obijuan@JANEL:~/Develop/pico$ export PICO_RISCV_TOOLCHAIN_PATH=~/edev/Raspberry-Pi-Pico-2/corev-openhw-gcc-ubuntu2204-20240530

La hora de la verdad... ahora hay que compilar

obijuan@JANEL:~/Develop/pico$ cd pico-examples/blink_simple/
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple$ rm CMakeCache.txt

El momento de la verdad...

obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple$ cmake -DPICO_PLATFORM=rp2350-riscv ..
Using PICO_SDK_PATH from environment ('../../pico-sdk')
PICO_SDK_PATH is /home/obijuan/Develop/pico/pico-sdk
Defaulting target board (PICO_BOARD) to 'pico2' since not specified.
Using board configuration from /home/obijuan/Develop/pico/pico-sdk/src/boards/include/boards/pico2.h
Pico Platform (PICO_PLATFORM) is 'rp2350-riscv'.
-- Defaulting build type to 'Release' since not specified.
Defaulting compiler (PICO_COMPILER) to 'pico_riscv_gcc' since not specified.
Configuring toolchain based on PICO_COMPILER 'pico_riscv_gcc'
Defaulting PICO_GCC_TRIPLE to 'riscv32-unknown-elf;riscv32-corev-elf'
CMake Warning at /home/obijuan/Develop/pico/pico-sdk/cmake/preload/toolchains/util/find_compiler.cmake:22 (message):
  PICO_TOOLCHAIN_PATH specified
  (/home/obijuan/edev/Raspberry-Pi-Pico-2/corev-openhw-gcc-ubuntu2204-20240530),
  but not found there
Call Stack (most recent call first):
  /home/obijuan/Develop/pico/pico-sdk/cmake/preload/toolchains/util/find_compiler.cmake:39 (pico_find_compiler)
  /home/obijuan/Develop/pico/pico-sdk/cmake/preload/toolchains/util/pico_arm_gcc_common.cmake:25 (pico_find_compiler_with_triples)
  /home/obijuan/Develop/pico/pico-sdk/cmake/preload/toolchains/pico_riscv_gcc.cmake:7 (include)
  /usr/share/cmake-3.28/Modules/CMakeDetermineSystem.cmake:170 (include)
  CMakeLists.txt:7 (project)


CMake Error at /home/obijuan/Develop/pico/pico-sdk/cmake/preload/toolchains/util/find_compiler.cmake:29 (message):
  Compiler 'riscv32-unknown-elf-gcc / riscv32-corev-elf-gcc' not found, you
  can specify search path with "PICO_TOOLCHAIN_PATH".
Call Stack (most recent call first):
  /home/obijuan/Develop/pico/pico-sdk/cmake/preload/toolchains/util/find_compiler.cmake:39 (pico_find_compiler)
  /home/obijuan/Develop/pico/pico-sdk/cmake/preload/toolchains/util/pico_arm_gcc_common.cmake:25 (pico_find_compiler_with_triples)
  /home/obijuan/Develop/pico/pico-sdk/cmake/preload/toolchains/pico_riscv_gcc.cmake:7 (include)
  /usr/share/cmake-3.28/Modules/CMakeDetermineSystem.cmake:170 (include)
  CMakeLists.txt:7 (project)


CMake Error: CMake was unable to find a build program corresponding to "Unix Makefiles".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.
CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage
CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage
CMake Error: CMAKE_ASM_COMPILER not set, after EnableLanguage
-- Configuring incomplete, errors occurred!
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple$

Mierda... peta....

Voy a comparar línea a línea la salida de cmake con la proporcionada en el artículo

La discrepancia es en esta línea:

CMake Warning at /home/obijuan/Develop/pico/pico-sdk/cmake/preload/toolchains/util/find_compiler.cmake:22 (message):
  PICO_TOOLCHAIN_PATH specified
  (/home/obijuan/edev/Raspberry-Pi-Pico-2/corev-openhw-gcc-ubuntu2204-20240530),
  but not found there
Call Stack (most recent call first):
  /home/obijuan/Develop/pico/pico-sdk/cmake/preload/toolchains/util/find_compiler.cmake:39 (pico_find_compiler)
  /home/obijuan/Develop/pico/pico-sdk/cmake/preload/toolchains/util/pico_arm_gcc_common.cmake:25 (pico_find_compiler_with_triples)
  /home/obijuan/Develop/pico/pico-sdk/cmake/preload/toolchains/pico_riscv_gcc.cmake:7 (include)
  /usr/share/cmake-3.28/Modules/CMakeDetermineSystem.cmake:170 (include)
  CMakeLists.txt:7 (project)

Lo que debería salir es:

-- The C compiler identification is GNU 14.1.0

Parece que no lo encuentra...

OK!! Ya he visto el problema... usa path absolutos... y yo he hecho copy&paste directamente... Tengo que actualizarlo con mi path.... Repito

obijuan@JANEL:~/Develop/pico$ export PICO_TOOLCHAIN_PATH=~/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530
obijuan@JANEL:~/Develop/pico$ export PICO_RISCV_TOOLCHAIN_PATH=~/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530
obijuan@JANEL:~/Develop/pico$ cd pico-examples/blink_simple/
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple$ rm CMakeCache.txt

Y repetimos

obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple$ cmake -DPICO_PLATFORM=rp2350-riscv ..
Using PICO_SDK_PATH from environment ('../../pico-sdk')
PICO_SDK_PATH is /home/obijuan/Develop/pico/pico-sdk
Defaulting target board (PICO_BOARD) to 'pico2' since not specified.
Using board configuration from /home/obijuan/Develop/pico/pico-sdk/src/boards/include/boards/pico2.h
Pico Platform (PICO_PLATFORM) is 'rp2350-riscv'.
-- Defaulting build type to 'Release' since not specified.
Defaulting compiler (PICO_COMPILER) to 'pico_riscv_gcc' since not specified.
Configuring toolchain based on PICO_COMPILER 'pico_riscv_gcc'
Defaulting PICO_GCC_TRIPLE to 'riscv32-unknown-elf;riscv32-corev-elf'
-- The C compiler identification is GNU 14.1.0
-- The CXX compiler identification is GNU 14.1.0
-- The ASM compiler identification is GNU
-- Found assembler: /home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-gcc
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
Build type is Release
-- Found Python3: /usr/bin/python3 (found version "3.12.3") found components: Interpreter 
TinyUSB available at /home/obijuan/Develop/pico/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040; enabling build support for USB.
BTstack available at /home/obijuan/Develop/pico/pico-sdk/lib/btstack
cyw43-driver available at /home/obijuan/Develop/pico/pico-sdk/lib/cyw43-driver
lwIP available at /home/obijuan/Develop/pico/pico-sdk/lib/lwip
mbedtls available at /home/obijuan/Develop/pico/pico-sdk/lib/mbedtls
Only building blink_any for non W boards as PICO_CYW43_SUPPORTED is not set
Skipping encrypted example which is unsupported on this platform
Skipping hello_dcp example which is unsupported on this platform
Skipping cache_perfctr example which is unsupported on this platform
Skipping ssi_dma example which is unsupported on this platform
Skipping multicore_fifo_irqs example which is unsupported on this platform
Skipping RTC examples as hardware_rtc is unavailable on this platform
Skipping universal examples as PICO_RISCV_TOOLCHAIN_PATH and PICO_ARM_TOOLCHAIN_PATH are not defined
Skipping TinyUSB dual examples, as TinyUSB hw/mcu/raspberry_pi/Pico-PIO-USB submodule unavailable
Skipping FreeRTOS examples as FREERTOS_KERNEL_PATH not defined
-- Configuring done (0.9s)
-- Generating done (1.3s)
-- Build files have been written to: /home/obijuan/Develop/pico/pico-examples/blink_simple
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple$ 

Vaaaaamos!!! Ahora parece que sí se ha configurado!!!

obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple$ cd blink_simple/
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$ make
[  0%] Building ASM object pico-sdk/src/rp2350/boot_stage2/CMakeFiles/bs2_default.dir/compile_time_choice.S.o
[ 50%] Linking ASM executable bs2_default.elf
[ 50%] Built target bs2_default
[ 50%] Generating bs2_default.bin
[ 50%] Generating bs2_default_padded_checksummed.S
[ 50%] Building ASM object pico-sdk/src/rp2350/boot_stage2/CMakeFiles/bs2_default_library.dir/bs2_default_padded_checksummed.S.o
[ 50%] Built target bs2_default_library
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/blink_simple.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_stdlib/stdlib.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_gpio/gpio.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2350/pico_platform/platform.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_platform_panic/panic.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/hardware_claim/claim.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_sync/sync.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_sync_spin_lock/sync_spin_lock.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_irq/irq.c.o
[ 50%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_irq/irq_handler_chain.S.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_sync/sem.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_sync/lock_core.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_sync/mutex.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_sync/critical_section.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_time/time.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_time/timeout_helper.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_timer/timer.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_util/datetime.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_util/pheap.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/common/pico_util/queue.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_uart/uart.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_clocks/clocks.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_pll/pll.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_vreg/vreg.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_watchdog/watchdog.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_ticks/ticks.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_bootrom/bootrom.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_bootrom/bootrom_lock.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_boot_lock/boot_lock.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_flash/flash.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_xosc/xosc.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_divider/divider.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_runtime/runtime.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_runtime_init/runtime_init.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_runtime_init/runtime_init_clocks.c.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_runtime_init/runtime_init_stack_guard.c.o
[ 50%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_bit_ops/bit_ops_aeabi.S.o
[ 50%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_divider/divider_compiler.c.o
[ 50%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_float/float_single_hazard3.S.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_malloc/malloc.c.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_atomic/atomic.c.o
[100%] Building CXX object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_cxx_options/new_delete.cpp.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_standard_binary_info/standard_binary_info.c.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_printf/printf.c.o
[100%] Building ASM object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_crt0/crt0_riscv.S.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_clib_interface/newlib_interface.c.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_stdio/stdio.c.o
[100%] Building C object blink_simple/CMakeFiles/blink_simple.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_stdio_uart/stdio_uart.c.o
[100%] Linking CXX executable blink_simple.elf
[100%] Built target blink_simple
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$

Comprobamos que el ejecutable generado es para riscv:

obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$ file blink_simple.elf
blink_simple.elf: ELF 32-bit LSB executable, UCB RISC-V, RVC, soft-float ABI, version 1 (SYSV), statically linked, with debug_info, not stripped
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$

siiiiiii!!!! Vaaaaamos!!!!

Para utilizar la utilidad uf2conv clonamos el repositorio primero, y luego la ejecutamos

obijuan@JANEL:~/Develop/pico$ git clone https://github.com/microsoft/uf2.git
obijuan@JANEL:~/Develop/pico$ cd uf2/utils/
obijuan@JANEL:~/Develop/pico/uf2/utils$ ./uf2conv.py -i ~/Develop/pico/pico-examples/blink_simple/blink_simple/blink_simple.uf2
--- UF2 File Header Info ---
Family ID is RP2XXX_ABSOLUTE, hex value is 0xe48bff57
Target Address is 0x10ffff00
Family ID is RP2350_RISCV, hex value is 0xe48bff5a
Target Address is 0x10000000
Flags were not all the same
----------------------------

Efectivamente es un archivo uf2 para RISCV

Ahora lo cargamos:

obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$ cp blink_simple.uf2 /media/obijuan/RP2350/
obijuan@JANEL:~/Develop/pico/pico-examples/blink_simple/blink_simple$

Listo!! Funciona!!!

Salida serie

Vamos a probar el hola mundo que envía una cadena por el puerto serie. En tramos en el directorio /pico-examples/hello_world y hacemos el proceso completo de configuración y compilación:

obijuan@JANEL:~/Develop/pico/pico-examples/hello_world$ cmake -DPICO_PLATFORM=rp2350-riscv ..
[...]
obijuan@JANEL:~/Develop/pico/pico-examples/hello_world$ cd hello_world/
obijuan@JANEL:~/Develop/pico/pico-examples/hello_world/hello_world$ make
[...]
obijuan@JANEL:~/Develop/pico/pico-examples/hello_world/hello_world$ cd usb
obijuan@JANEL:~/Develop/pico/pico-examples/hello_world/hello_world/usb$ cp hello_usb.uf2 /media/obijuan/RP2350/

Abrimos un terminal serie:

obijuan@JANEL:~$ tio /dev/ttyACM0 
[10:11:32.552] tio v3.9
[10:11:32.552] Press ctrl-t q to quit
[10:11:32.552] Connected to /dev/ttyACM0
Hello, world!
Hello, world!
Hello, world!
Hello, world!
[...]

Funciona!!!

Cargando un programa nuevo

Cuando se está ejecutando un programa en la pic2, deja de estar montado como una unidad usb... Para cargar otro programa hay que entrar en el modo usb, desconectando la placa, apretando el botón bootsel y conectándola de nuevo. Se monta la unidad usb...

Esto es muy incómodo. Tengo que encontrar los comandos para poder cargar desde la línea de comandos si tener que desconectar la placa

Voy a seguir leyendo la documentación: https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf

Tengo que instalar las picotools. Las he instalado siguiendo estas instrucciones: https://github.com/raspberrypi/picotool

Voy a aprender un poco sobre picotool. Primero ejecutamos el ejecutable, que acabamos de compilar:

obijuan@JANEL:~/Develop/pico/picotool/build$ ./picotool 
PICOTOOL:
    Tool for interacting with RP-series device(s) in BOOTSEL mode, or with an RP-series
    binary

SYNOPSIS:
    picotool info [-b] [-m] [-p] [-d] [--debug] [-l] [-a] [device-selection]
    picotool info [-b] [-m] [-p] [-d] [--debug] [-l] [-a] <filename> [-t <type>]
[...]
COMMANDS:
    info        Display information from the target device(s) or file.
                Without any arguments, this will display basic information for all
                connected RP-series devices in BOOTSEL mode
    config      Display or change program configuration settings from the target
                device(s) or file.
    load        Load the program / memory range stored in a file onto the device.
    encrypt     Encrypt the program.
    seal        Add final metadata to a binary, optionally including a hash and/or
                signature.
    link        Link multiple binaries into one block loop.
    save        Save the program / memory stored in flash on the device to a file.
    erase       Erase the program / memory stored in flash on the device.
    verify      Check that the device contents match those in the file.
    reboot      Reboot the device
    otp         Commands related to the RP2350 OTP (One-Time-Programmable) Memory
    partition   Commands related to RP2350 Partition Tables
    uf2         Commands related to UF2 creation and status
    version     Display picotool version
    coprodis    Post-process coprocessor instructions in disassembly files.
    help        Show general help or help for a specific command

Use "picotool help <cmd>" for more info
obijuan@JANEL:~/Develop/pico/picotool/build$

Voy a ir probando comandos poco a poco...

Primero info, a ver qué pasa. Ahora mismo la pico está corriendo el programa hello_world, y en otro terminal veo los mensajes que está mandando. No está montada por el USB

obijuan@JANEL:~/Develop/pico/picotool/build$ ./picotool info
No accessible RP-series devices in BOOTSEL mode were found.

but:

RP2350 device at bus 1, address 17 appears to have a USB serial connection, so consider
    -f (or -F) to force reboot in order to run the command.
obijuan@JANEL:~/Develop/pico/picotool/build$

No podemos obtener la información porque no está en modo BOOTSEL, pero la ayuda sugiere usar el comando -F

obijuan@JANEL:~/Develop/pico/picotool/build$ ./picotool info -F
Tracking device serial number D13CB08F7A0C8CE8 for reboot
The device was asked to reboot into BOOTSEL mode so the command can be executed.

Program Information
 name:          hello_usb
 web site:      https://github.com/raspberrypi/pico-examples/tree/HEAD/hello_world/usb
 features:      USB stdin / stdout
 binary start:  0x10000000
 binary end:    0x10006484
 target chip:   RP2350
 image type:    RISC-V

                The device has been left accessible, but without the drive mounted; use
                'picotool reboot' to reboot into regular BOOTSEL mode or application
                mode.
obijuan@JANEL:~/Develop/pico/picotool/build$

Tras esto el software se queda "congelado". En el terminal no se está ejecutando nada. Ejecuto el comando sugerido reboot

obijuan@JANEL:~/Develop/pico/picotool/build$ ./picotool reboot
obijuan@JANEL:~/Develop/pico/picotool/build$

La aplicación vuelve a funcionar

Voy a intentar cargar el blinky que ya he probado antes, y que se que funciona

obijuan@JANEL:~/Develop/pico/picotool/build$ ./picotool load ../../pico-examples/blink_simple/blink_simple/blink_simple.uf2
No accessible RP-series devices in BOOTSEL mode were found.

but:

RP2350 device at bus 1, address 19 appears to have a USB serial connection, so consider
    -f (or -F) to force reboot in order to run the command.
obijuan@JANEL:~/Develop/pico/picotool/build$

Lo mismo que antes... así que voy a usar -F:

obijuan@JANEL:~/Develop/pico/picotool/build$ ./picotool load ../../pico-examples/blink_simple/blink_simple/blink_simple.uf2 -F
Tracking device serial number D13CB08F7A0C8CE8 for reboot
The device was asked to reboot into BOOTSEL mode so the command can be executed.

Family ID 'rp2350-riscv' can be downloaded in absolute space:
  00000000->02000000
Loading into Flash:   [==============================]  100%

The device has been left accessible, but without the drive mounted; use 'picotool
reboot' to reboot into regular BOOTSEL mode or application mode.
obijuan@JANEL:~/Develop/pico/picotool/build$

Y ahora hago un reboot... ¡¡¡siiiii!!! Funciona!!!!!! Vamos!!!!

Mierda.. tras grabar el blink... ya no puedo hacer nada con el picotool...

obijuan@JANEL:~/Develop/pico/picotool/build$ ./picotool info -F
No accessible RP-series devices in BOOTSEL mode were found.

El puerto serie ha desaparecido.... con picotool no puedo hacer nada....

ok... esto pinta a algo de software... bueno... de momento puedo vivir con el modo BOOTSEL...

Profundizando en el Blinky

Vamos a empezar un proyecto nuevo, a partir del blinky... y lo vamos a ir simplificando hasta llegar a lo mínimo

No me queda nada claro... joder... Voy a empezar un proyecto nuevo desde cero, a ver si hay suerte

Voy a hacer lo que se indica en la página 36 de este pdf: https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf Apartado: "Manually Create your own Project"

obijuan@JANEL:~/Develop/pico$ mkdir test
obijuan@JANEL:~/Develop/pico$ cd test

Creamos el fichero test.c:

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "pico/binary_info.h"

const uint LED_PIN = 25;

int main() {

  bi_decl(bi_program_description("This is a test binary."));
  bi_decl(bi_1pin_with_name(LED_PIN, "On-board LED"));

  stdio_init_all();
  gpio_init(LED_PIN);
  gpio_set_dir(LED_PIN, GPIO_OUT);

  while (1) {
    gpio_put(LED_PIN, 0);
    sleep_ms(250);
    gpio_put(LED_PIN, 1);
    puts("Hello World\n");
    sleep_ms(1000);
  }
}

Creamos el CMakeLists.txt

cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(test_project C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(test
test.c
)
pico_enable_stdio_usb(test 1)
pico_enable_stdio_uart(test 1)
pico_add_extra_outputs(test)
target_link_libraries(test pico_stdlib)

Hay que copiar el fichero pico_sdk_import

obijuan@JANEL:~/Develop/pico/test$ cp ../pico-sdk/external/pico_sdk_import.cmake .

Creamos las variables de entorno:

obijuan@JANEL:~/Develop/pico/test/build$ export PICO_SDK_PATH=../../pico-sdk
obijuan@JANEL:~/Develop/pico/test/build$ export PICO_TOOLCHAIN_PATH=/home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530

Configuramos y compilamos

obijuan@JANEL:~/Develop/pico/test/build$ cmake -DPICO_PLATFORM=rp2350-riscv ..
[...]
obijuan@JANEL:~/Develop/pico/test/build$ make
[...]

Comprobamos que es RISCV y lo cargamos (Hay que activar el modo BOOTSEL manualmente)

obijuan@JANEL:~/Develop/pico/test/build$ file test.elf
test.elf: ELF 32-bit LSB executable, UCB RISC-V, RVC, soft-float ABI, version 1 (SYSV), statically linked, with debug_info, not stripped
obijuan@JANEL:~/Develop/pico/test/build$ cp test.uf2 /media/obijuan/RP2350/
obijuan@JANEL:~/Develop/pico/test/build$

Estos pasos los tengo claros... Ahora voy a ir reduciendo el programa a lo mínimo posible

Este es el programa mínimo que permite cargar programas con picotool sin necesidad de entrar en el modo BOOTSEL manualmente

#include "pico/stdlib.h"
#include "hardware/gpio.h"

const uint LED_PIN = 25;

int main() {

  stdio_init_all();
  gpio_init(LED_PIN);
  gpio_set_dir(LED_PIN, GPIO_OUT);

  gpio_put(LED_PIN, 1);
  while (1);

}

Si eliminamos stdio_init_all() ya sólo se puede hacer carga manual También quitamos el bucle infinito Este es el programa mínimo que enciende un LED

#include "hardware/gpio.h"

const uint LED_PIN = 25;

int main() {
  gpio_init(LED_PIN);
  gpio_set_dir(LED_PIN, GPIO_OUT);

  gpio_put(LED_PIN, 1);
}

Voy a seguir minimizándolo... quito las constantes

Esto es lo más pequeño que he conseguido:

#include "hardware/gpio.h"

int main() {
  gpio_init(25);
  gpio_set_dir(25, 1);

  gpio_put(25, 1);
}

Desensamblamos el binario y lo volcamos en test.s para verlo

obijuan@JANEL:~/Develop/pico/test/build$ $PICO_TOOLCHAIN_PATH/riscv32-corev-elf/bin/objdump -d test.elf > test.s

Este código generado lo entiendo mejor. Está comprimido, pero el desensamblador lo escribe en riscv-normal... así es muchísimo más legible Vamos a ver la función main:

10000184 <main>:
10000184:	1141                	addi	sp,sp,-16
10000186:	4565                	li	a0,25
10000188:	c606                	sw	ra,12(sp)
1000018a:	2819                	jal	100001a0 <gpio_init>
1000018c:	40b2                	lw	ra,12(sp)
1000018e:	d00007b7          	lui	a5,0xd0000
10000192:	02000737          	lui	a4,0x2000
10000196:	df98                	sw	a4,56(a5)
10000198:	cf98                	sw	a4,24(a5)
1000019a:	4501                	li	a0,0
1000019c:	0141                	addi	sp,sp,16
1000019e:	8082                	ret

Se ve claramente que es una función que empieza y termina (hay un ret).

  • Se ve claramente la llamada a gpio_init, pero no a las otras funciones gpio_set_dir() y gpio_put()
  • Igual están definidas como inline

Para diferencialo bien voy a meter un bucle infinito sólo en la escritura a 1 del pin

El nuevo programa es este:

#include "hardware/gpio.h"

int main() {
  gpio_init(25);
  gpio_set_dir(25, 1);

  while (1) 
    gpio_put(25, 1);
}

Y este es el nuevo main desensablado:

10000184 <main>:
10000184:	1141                	addi	sp,sp,-16
10000186:	4565                	li	a0,25
10000188:	c606                	sw	ra,12(sp)
1000018a:	2809                	jal	1000019c <gpio_init>
1000018c:	d00007b7          	lui	a5,0xd0000
10000190:	02000737          	lui	a4,0x2000
10000194:	df98                	sw	a4,56(a5)
10000196:	cf98                	sw	a4,24(a5)
10000198:	cf98                	sw	a4,24(a5)
1000019a:	bff5                	j	10000196 <main+0x12>

El bucle principal se ve claramente ahora. la única instrucción que se ejecuta es: sw a4,24(a5)

a5 contiene la dirección del pin. Su offset no lo conocemos... pero sí sus 20-bits. De momento podemos suponer que a5 vale 0xD0000_xxx El registro a4 contiene el valor a escribir. Tampoco sabemos el offset, pero sí sus bits de mayor peso: 0x02000_xxx

Voy a analizar gpio_init()

1000019c <gpio_init>:
1000019c:	28a016b3          	bset	a3,zero,a0
100001a0:	d0000737          	lui	a4,0xd0000
100001a4:	400387b7          	lui	a5,0x40038
100001a8:	c334                	sw	a3,64(a4)
100001aa:	0791                	addi	a5,a5,4 # 40038004 <__StackTop+0x1ffb6004>
100001ac:	d314                	sw	a3,32(a4)
100001ae:	20f547b3          	sh2add	a5,a0,a5
100001b2:	4398                	lw	a4,0(a5)
100001b4:	6685                	lui	a3,0x1
100001b6:	96be                	add	a3,a3,a5
100001b8:	04074713          	xori	a4,a4,64
100001bc:	0c077713          	andi	a4,a4,192
100001c0:	40028637          	lui	a2,0x40028
100001c4:	c298                	sw	a4,0(a3)
100001c6:	20c56533          	sh3add	a0,a0,a2
100001ca:	4715                	li	a4,5
100001cc:	668d                	lui	a3,0x3
100001ce:	c158                	sw	a4,4(a0)
100001d0:	97b6                	add	a5,a5,a3
100001d2:	10000713          	li	a4,256
100001d6:	c398                	sw	a4,0(a5)
100001d8:	8082                	ret
  • Por a0 se le pasa el pin (25)
  • Se activa el bit 25 de a3
  • a4=0xd0000_xxx
  • a5=0x40038_xxx No saco nada más en claro...

Hola mundo en ensamblador RISC-V

He encontrado este post sobre RISC-V en la pico2 en ensamblador

https://smist08.wordpress.com/2024/10/21/risc-v-on-the-raspberry-pi-pico-2/

Ahora que ya se un poco cómo manejarla, voy a ver si entiendo bien este post y consigo hacer el primer hola-mundo...

Utiliza el VSCode. Voy a intentar replicarlo en consola, y si no lo consigo, instalo el VSCode

Primero replico el proyecto Test, pero cambiando el nombre a Helloworld. Listo... ya lo tengo... Ahora voy a hacer las modificaciones necesarias

Listo!

Este es el programa:

#
# Risc-V Assembler program to print "Hello RISC-V World xx"
#
# a0-a1 - parameters to printf
# s0 - counter
#

.global main      # Provide program starting address to linker

main:   jal   stdio_init_all
        mv    s0, x0
loop:   la    a0, helloworld # load address of helloworld
        addi  s0, s0, 1
        mv    a1, s0        # counter        
        jal   printf
        j     loop
.data
helloworld:      .asciz "Hello RISC-V World %d\n"

Este es el CMakeList.txt:

cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(helloworld C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(helloworld
helloworld.S
)
pico_enable_stdio_usb(helloworld 1)
pico_enable_stdio_uart(helloworld 1)
pico_add_extra_outputs(helloworld)
target_link_libraries(helloworld pico_stdlib)

Voy a crear otro proyecto, test2, a partir de este en el que quiero modificarlo a lo mínimo: encender un led, y aprovechar tambien para obtener los comandos exactos de compilación

Listo... lo tengo...

Tengo que lograr ahora encender un LED. Para ello lo primero que necesito es ver las fuentes de la función gpio_init(). ¿Dónde está?

las definiciones de gpio.h están aquí:

/home/obijuan/Develop/pico/pico-sdk/src/host/hardware_gpio/include/hardware/gpio.h

La implementación está en este fichero: /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_gpio/gpio.c

void gpio_init(uint gpio) {
    gpio_set_dir(gpio, GPIO_IN);
    gpio_put(gpio, 0);
    gpio_set_function(gpio, GPIO_FUNC_SIO);
}

Vamos a por gpio_set_dir()

Está definida aquí: /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_gpio/include/hardware/gpio.h En este fichero hay documentación, en los comentarios, sobre el funcionamiento de los gpios

Es una función inline, como sospechaba, que está definida en esta otra: gpioc_bit_oe_put(gpio, out); Encuentro la de arm, pero no de riscv...

La cabecera más básica es pico.h: /home/obijuan/Develop/pico/pico-sdk/src/boards/include/boards/pico.h
Esta no incluye ningún otro fichero .h

Esta es otra pico.h: /home/obijuan/Develop/pico/pico-sdk/src/common/pico_base_headers/include/pico.h
Esta cabecera es la que se incluye en muchos sitios

Aquí están definidas las direcciones de memoria:

/home/obijuan/Develop/pico/pico-sdk/src/rp2350/hardware_regs/include/hardware/regs/addressmap.h

La hoja de datos del chip rp2350 está aquí: https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf

Puertos de entrada/salida

Los GPIOs son accesibles por muchos recursos dentro del microcontrolador. Uno de ellos es el SIO (Syngle cycle Input/Output), que es el que permite controlarlos por software. Es el software el que los pone a 1 ó a 0

2025-04-27

LEDON-asm

He encontrado este post donde muestran un hola mundo para risc-v en la raspberry pi pico2:

http://blog.wolfman.com/articles/2025/3/23/bare-metal-gpio-twiddling-for-risc-v-on-rpi-pico2

Lo voy a analizar a ver si saco algo en claro En este primer experimento compilo el programa completo (solo la parte de main), como fichero test2.S:

.global main      # Provide program starting address to linker

.equ SYSCTL_BASE,    0x40000000
.equ CLK_EN_REG,     SYSCTL_BASE + 0x100   # Clock enable register

.equ PAD_ISO_REG,    0x40038000 + 0x40   # Pad isolation control register for GPIO15

.equ IOMUX_BASE,     0x40028000
.equ IOMUX_GPIO15,   IOMUX_BASE + 0x7C     # IOMUX register for GPIO15

.equ SIO_BASE,       0xD0000000
.equ GPIO_OUT_REG,   SIO_BASE + 0x10       # GPIO output register
.equ GPIO_OUT_SET,   SIO_BASE + 0x14       # GPIO output set register
.equ GPIO_OUT_CLR,   SIO_BASE + 0x18       # GPIO output clear register
.equ GPIO_DIR_REG,   SIO_BASE + 0x30       # ✅ **Corrected: GPIO direction register**

.equ GPIO15_MASK,    (1 << 15)             # Bitmask for GPIO15


main:   


    # 1 Enable clock for GPIO peripheral (NOT NEEDED)
    ; li t0, CLK_EN_REG
    ; lw t1, 0(t0)         # Read current clock enable register
    ; li t2, 0x00000020    # Enable GPIO clock (check RP2350 datasheet)
    ; or t1, t1, t2
    ; sw t1, 0(t0)         # Write back to enable GPIO clock

    # 2 Configure IOMUX for GPIO15
    li t0, IOMUX_GPIO15
    lw t1, 0(t0)
    li t2, ~0x1F
    and t1, t1, t2   # clear it first
    ori t1, t1, 5        # Function 5 selects GPIO mode
    sw t1, 0(t0)         # Set IOMUX for GPIO15

    # 3 Set GPIO15 as an output
    li t0, GPIO_DIR_REG
    lw t1, 0(t0)         # Read current GPIO direction register
    li t2, GPIO15_MASK
    or t1, t1, t2
    sw t1, 0(t0)         # Set GPIO15 as output

    # 4 Clear Pad Isolation for GPIO
    li t0, PAD_ISO_REG
    lw t1, 0(t0)
    li t2, ~0x100
    and t1, t1, t2  # Clear GPIO15 isolation bit
    sw t1, 0(t0)

loop:
    # 5 Turn LED ON (Use SIO fast register access)
    li t0, GPIO_OUT_REG
    lw t1, 0(t0)        # Read current GPIO_OUT
    li t2, GPIO15_MASK
    or t1, t1, t2
    sw t1, 0(t0)        # Set GPIO15 high

    call delay

    # 6 Turn LED OFF
    li t0, GPIO_OUT_REG
    lw t1, 0(t0)
    li t2, ~GPIO15_MASK
    and t1, t1, t2
    sw t1, 0(t0)        # Set GPIO15 low

    call delay
    j loop

delay:
    li t0, 0xFFFFFF        # Simple delay loop
1:
    addi t0, t0, -1
    bnez t0, 1b
    ret

Compila y se graba bien en la pico2. Se supone que debe hacer parpadear el led situado en el GPIO 15


(Pinout)

Sueldo los pines, la conecto a la protoboard y le conecto un LED al GPIO15. He utilizado la placa FreeLEDs

Este es el montaje:

En esta animación se ve el led parpadeando

Esto es un gran avance. Ahora ya puedo ir reduciéndolo a lo mínimo para sólo encender un LED, y entender definitivamente todas los registros implicados, y sus direcciones

Como este es el código base, a partir del que voy a ir haciendo pruebas, lo voy a meter en el repositorio de este log

01-blinky-gpio15

Este es el primer ejemplo del repo. Una vez configurado con cmake (las instrucciones están en el README), compilamos con make, activamos el modo boot y copiamos el ejecutable (.uf2):

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ make
[...]
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ cp 01-blinky-gpio15.uf2 /media/obijuan/RP2350

y Listo.... tenemos el led conectado al gpio15 parpadeando, como en el ejemplo anterior. Este es nuestro ejemplo base

02-ledon-gpio15

En este ejemplo eliminamos la parte del blinky, y sólo encendemos el led de gpio15. Para compilarlo se ha modificado el CMakeList.txt:

cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(01-Ejemplos-asm C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()

#-- Ejemplo 01-blinky-gpio15
add_executable(01-blinky-gpio15
01-blinky-gpio15.S
)

pico_enable_stdio_usb(01-blinky-gpio15 1)
pico_enable_stdio_uart(01-blinky-gpio15 1)
pico_add_extra_outputs(01-blinky-gpio15)

target_link_libraries(01-blinky-gpio15 pico_stdlib)

#-- Ejemplo 02-ledon-gpio15

add_executable(02-ledon-gpio15
02-ledon-gpio15.S
)

pico_enable_stdio_usb(02-ledon-gpio15 1)
pico_enable_stdio_uart(02-ledon-gpio15 1)
pico_add_extra_outputs(02-ledon-gpio15)

target_link_libraries(02-ledon-gpio15 pico_stdlib)

Este es el programa

.global main      # Provide program starting address to linker

.equ SYSCTL_BASE,    0x40000000
.equ CLK_EN_REG,     SYSCTL_BASE + 0x100   # Clock enable register

.equ PAD_ISO_REG,    0x40038000 + 0x40   # Pad isolation control register for GPIO15

.equ IOMUX_BASE,     0x40028000
.equ IOMUX_GPIO15,   IOMUX_BASE + 0x7C     # IOMUX register for GPIO15

.equ SIO_BASE,       0xD0000000
.equ GPIO_OUT_REG,   SIO_BASE + 0x10       # GPIO output register
.equ GPIO_OUT_SET,   SIO_BASE + 0x14       # GPIO output set register
.equ GPIO_OUT_CLR,   SIO_BASE + 0x18       # GPIO output clear register
.equ GPIO_DIR_REG,   SIO_BASE + 0x30       # ✅ **Corrected: GPIO direction register**

.equ GPIO15_MASK,    (1 << 15)             # Bitmask for GPIO15


main:   

    # 2 Configure IOMUX for GPIO15
    li t0, IOMUX_GPIO15
    lw t1, 0(t0)
    li t2, ~0x1F
    and t1, t1, t2   # clear it first
    ori t1, t1, 5        # Function 5 selects GPIO mode
    sw t1, 0(t0)         # Set IOMUX for GPIO15

    # 3 Set GPIO15 as an output
    li t0, GPIO_DIR_REG
    lw t1, 0(t0)         # Read current GPIO direction register
    li t2, GPIO15_MASK
    or t1, t1, t2
    sw t1, 0(t0)         # Set GPIO15 as output

    # 4 Clear Pad Isolation for GPIO
    li t0, PAD_ISO_REG
    lw t1, 0(t0)
    li t2, ~0x100
    and t1, t1, t2  # Clear GPIO15 isolation bit
    sw t1, 0(t0)

loop:
    # 5 Turn LED ON (Use SIO fast register access)
    li t0, GPIO_OUT_REG
    lw t1, 0(t0)        # Read current GPIO_OUT
    li t2, GPIO15_MASK
    or t1, t1, t2
    sw t1, 0(t0)        # Set GPIO15 high

    j loop

Como hemos modificado el CMake, hay que reconstruirlo:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ cmake -DPICO_PLATFORM=rp2350-riscv ..
PICO_SDK_PATH is /home/obijuan/Develop/pico/pico-sdk
Target board (PICO_BOARD) is 'pico2'.
Using board configuration from /home/obijuan/Develop/pico/pico-sdk/src/boards/include/boards/pico2.h
Pico Platform (PICO_PLATFORM) is 'rp2350-riscv'.
Build type is Release
TinyUSB available at /home/obijuan/Develop/pico/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040; enabling build support for USB.
BTstack available at /home/obijuan/Develop/pico/pico-sdk/lib/btstack
cyw43-driver available at /home/obijuan/Develop/pico/pico-sdk/lib/cyw43-driver
lwIP available at /home/obijuan/Develop/pico/pico-sdk/lib/lwip
mbedtls available at /home/obijuan/Develop/pico/pico-sdk/lib/mbedtls
-- Configuring done (0.2s)
-- Generating done (0.0s)
-- Build files have been written to: /home/obijuan/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$

Y ahora ya podemos compilar con make:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ make
[  2%] Built target bs2_default
[  4%] Built target bs2_default_library
[ 52%] Built target 01-blinky-gpio15
[100%] Built target 02-ledon-gpio15
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$

Cargamos el ejecutable en la pico2 y se enciende el LED (sin parpadeos)

El programa lo primero que hace es configurar los IOMUX para el GPIO15. No sé nada de los IOMUX, así que voy a mirar el datasheet

La información que tenemos es que la dirección base para IOMUX es 0x40028000, y el registro IOMUX para el GPIO15 está en el offset 0x7C

Vamos a leer sobre esto

03-ledon-gpio15

En la página 32 están las direcciones de todos los elementos. Lo que en este código se llama IOMUX, en el datasheet lo llaman IO_BANK0_BASE. Aquí se encuentras registro de control, datos y estatus de los periféricos del banco 0. En este banco 0 están los 30 GPIOS de la pico2. Estos 30 GPIOS, del banco 0, se pueden asignar a diferentes periféricos. Uno de ellos es el SIO que lo que nos permite es el control directo mediante software. Es el que queremos usar

ok... ya lo voy entendiendo. Cada GPIO tiene 2 registros: Control y Status, situados a partir de la dirección base IO_BANK0_BASE, que es 0x40028000. El registro de control tiene 32 bits de configuración. Por defecto está todo a 0 (modo normal). Salvo los 5 bits de menor peso que determinan LA FUNCION DEL GPIO. Un valor de 0x1F significa NULL. Es decir, que el pin NO está asignado a ningún periférico, y NO se puede controlar por software por tanto. Un valor de 0x05 significa que se asigna a SIO (Es decir, controlados por software)

Vamos ahora a por la función SIO: Controlar los pines por software. Los registros encargados de esto están situados a partir de la dirección base SIO_BASE, que es 0xD00000000. Hay un registro, llamado GPIO_OE que constrola si la salida correspondiente se comporta como salida (1) o como entrada (0). El bit 0 se corresponde con GPIO0, el bit 1 con GPIO1, etc...

En la pico2 los pines tienen más opciones de configuración. Por defecto los pines están deshabilitados (para consumir menos). Por eso hay que eliminar esta deshabilitación a través de los registros PAD_CTRL, situados en el banco PADS_BANK0, en la dirección 0x40038000

El PAD_CTRL del GPIO15 está en el offset 0x40. El bit 8 se debe poner a 0 para habilitarlo (de lo contrario no se podrá constrolar por software)

Para escribir un bit por el pin indicado hay que escribir en el registros GPIO_OUT del bloque SIO. Este registro tiene el offset 0x10

Este es el nuevo programa, más simplificado, que enciende el LED GPIO15

.global main

# ----------------------------------------------
# -- Registro de control del GPIO15
.equ GPIO15_CTRL, 0x4002807C

# -- Habilitación del pin GPIO15
.equ GPIO15_PAD_ISO, 0x40038040

# -- Registro de configuración de la direcion (salida/entrada)
.equ GPIO_OE, 0xD0000030

# -- Registro de salida del GPIO
.equ GPIO_OUT, 0xD0000010

main:   

    #-- Configuracion de GPIO15 para controlarse por software
    li t0, GPIO15_CTRL  #-- Direccion
    li t1, 0x5          #-- Funcion: SIO. Control por software
    sw t1, 0(t0)        #-- Escribir en el registro de control

    #-- Configuracion de GPIO15 como salida
    li t0, GPIO_OE      #-- Direccion
    li t1, 0x00008000   #-- Poner bit 15 a 1: Salida. Esto a 0: entradas
    sw t1, 0(t0)        #-- Configurar el pin como salida

    #-- Habilitar el pin GPIO15
    li t0, GPIO15_PAD_ISO  #-- Direccion
    li t1, 0x016   #-- Apagar bit 8: ISO, dejar el resto con su estado inicial
    sw t1, 0(t0)        #-- Habilitar el pin GPIO15

loop:

    # -- Encender el LED
    li t0, GPIO_OUT
    li t1, 0x00008000  #-- Activar el bit 15
    sw t1, 0(t0)       #-- Escribir!

    j loop

04-ledon-gpio0

Para consolidar lo aprendido, y practicar más, voy a hacer el programa para encender el LED situado en el gpio0

Este es el programa. Las constantes que ya estaban definidas para el GPIO15 se mantienen

.global main

# --------------------------------
# -- Registros de control del GPIO
# -------------------------------- 
.equ GPIO00_CTRL, 0x40028004
.equ GPIO15_CTRL, 0x4002807C

# -------------------------------
# -- Registros de los PADs
# -------------------------------
.equ GPIO00_PAD_ISO, 0x40038004
.equ GPIO15_PAD_ISO, 0x40038040

# --------------------------------------
# -- Registro de habilitacion de salida
# -- GPIOs de 0 a 31
# --------------------------------------
.equ GPIO_OE, 0xD0000030

# --------------------------------------
# -- Registro de salida
# -- GPIOs de 0 a 31
# --------------------------------------
.equ GPIO_OUT, 0xD0000010

main:   

    #-- Configuracion de GPIO0 para controlarse por software
    li t0, GPIO00_CTRL  
    li t1, 0x5          
    sw t1, 0(t0)        

    #-- Configuracion de GPIO0 como salida
    li t0, GPIO_OE      
    li t1, 1
    sw t1, 0(t0)        

    #-- Habilitar el pin GPIO0
    li t0, GPIO00_PAD_ISO  
    li t1, 0x016   #-- Apagar bit 8: ISO, dejar el resto con su estado inicial
    sw t1, 0(t0)

    # -- Encender el LED
    li t0, GPIO_OUT
    li t1, 1    #-- Activar el bit 0
    sw t1, 0(t0)  

    # -- FIN
inf:  j inf

Conectamos el LED al gpio0, que es el primer pin de la pico2. ¡Se enciende!

05-ledon

En este ejemplo se enciende el LED integrado en la placa, que está en el GPIO25

.global main

# --------------------------------
# -- Registros de control del GPIO
# -------------------------------- 
.equ GPIO00_CTRL, 0x40028004
.equ GPIO15_CTRL, 0x4002807C
.equ GPIO25_CTRL, 0x400280CC

# -------------------------------
# -- Registros de los PADs
# -------------------------------
.equ GPIO00_PAD_ISO, 0x40038004
.equ GPIO15_PAD_ISO, 0x40038040
.equ GPIO25_PAD_ISO, 0x40038068

# --------------------------------------
# -- Registro de habilitacion de salida
# -- GPIOs de 0 a 31
# --------------------------------------
.equ GPIO_OE, 0xD0000030

# --------------------------------------
# -- Registro de salida
# -- GPIOs de 0 a 31
# --------------------------------------
.equ GPIO_OUT, 0xD0000010

# ----------------------------------------------
# -- Definiciones de las posiciones de los bits
# ----------------------------------------------
.equ BIT0,  0x00000001
.equ BIT25, (1 << 25)  # 0x0200_0000

main:   

    #-- Configuracion de GPIO0 para controlarse por software
    li t0, GPIO25_CTRL  
    li t1, 0x5          
    sw t1, 0(t0)        

    #-- Configuracion de GPIO0 como salida
    li t0, GPIO_OE      
    li t1, BIT25
    sw t1, 0(t0)

    #-- Habilitar el pin GPIO0
    li t0, GPIO25_PAD_ISO  
    li t1, 0x016   #-- Apagar bit 8: ISO, dejar el resto con su estado inicial
    sw t1, 0(t0)

    # -- Encender el LED
    li t0, GPIO_OUT
    li t1, BIT25    #-- Activar el bit 25
    sw t1, 0(t0)  

    # -- FIN
inf:  j inf

Este es el resultado:

06-ledon

Este es el mismo ejemplo ledon, pero llevamos las constantes al fichero gpio.h. El CMakeList.txt hay que modificarlo. He añadido esto para el ejemplo:

# -- [...] 
#-- Ejemplo 06-ledon
add_executable(06-ledon
06-ledon.S gpio.h
)
target_include_directories(06-ledon PRIVATE .)
pico_enable_stdio_usb(06-ledon 1)
pico_enable_stdio_uart(06-ledon 1)
pico_add_extra_outputs(06-ledon)
target_link_libraries(06-ledon pico_stdlib)

Si no se incluye el target_include_directories() no se detecta el fichero gpio.h, y da un error al hacer make

Este es el fichero gpio.h, que irá creciendo poco a poco con los ejemplos

# ----------------------------------------------------------
# -- Registros de control del GPIO
# ---------------------------------------------------------- 
# -- La información sobre los registros de control se encuentra
# -- en la sección 9.11.1, página 601 del datasheet del RP2350
# -- https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf
#
# | 31 32 | 28:29  | 27:18 | 17:16  | 15:14  | 13:12   | 11:5 | 4:0     |
# | Res   | IRQOVER| Res   | INOVER | OEOVER | OUTOVER | Res  | FUNCSEL |

# -- Direcciones 
.equ GPIO15_CTRL, 0x4002807C
.equ GPIO25_CTRL, 0x400280CC

# -- CAMPO FUNCSEL
.equ FUNC_SOFTWARE, 0x05  #-- Control por software
.equ FUNC_NULL,     0x1F  #-- Sin usar

# ----------------------------------------------------------
# -- Registros de los PADs
# ----------------------------------------------------------
# -- Sección 9.11.3, página 782 del datasheet del RP2350
# -- https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf

# -- Direcciones
.equ GPIO00_PAD_ISO, 0x40038004
.equ GPIO15_PAD_ISO, 0x40038040
.equ GPIO25_PAD_ISO, 0x40038068

# -- Valor para habilitar el PAD
# -- Y dejar el resto de campos a su valores por defecto
.equ PAD_ENABLE, 0x016


# ---------------------------------------------------------
# -- REGISTROS DEL BANCO SIO
# ---------------------------------------------------------
# -- Sección 3.1.11. Página 54 del datasheet del RP2350
# -- https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf

# --- DIRECCIONES ---------------------------------

# --------------------------------------
# -- Registro de habilitacion de salida
# -- GPIOs de 0 a 31
# --------------------------------------
.equ GPIO_OE, 0xD0000030

# --------------------------------------
# -- Registro de salida
# -- GPIOs de 0 a 31
# --------------------------------------
.equ GPIO_OUT, 0xD0000010

# ----------------------------------------------
# -- Definiciones de las posiciones de los bits
# -- Se usan como valores para GPIO_OE y GPIO_OUT
# ----------------------------------------------
.equ BIT0,  0x00000001
.equ BIT25, (1 << 25)  # 0x0200_0000

Este es el fichero 06-ledon.S

.global main

.include "gpio.h"

main:   

    #-- Configuracion de GPIO0 para controlarse por software
    li t0, GPIO25_CTRL  
    li t1, FUNC_SOFTWARE          
    sw t1, 0(t0)        

    #-- Configuracion de GPIO0 como salida
    li t0, GPIO_OE      
    li t1, BIT25
    sw t1, 0(t0)

    #-- Habilitar el pin GPIO0
    li t0, GPIO25_PAD_ISO  
    li t1, PAD_ENABLE
    sw t1, 0(t0)

    # -- Encender el LED
    li t0, GPIO_OUT
    li t1, BIT25    #-- Activar el bit 25
    sw t1, 0(t0)  

    # -- FIN
inf:  j inf

07-ledon

La idea con este ejemplo es la de averiguar exactamente cómo se realiza el ensamblado, linkado y generación del .uf2... Si se descubren los comandos ya no será necesario cmake

Para ver los comandos que usa make ponemos este comando make 07-ledon -n > test.txt. En el fichero test.txt aparecen todos los comandos ejecutados. Este es el resultado, eliminado cosas que no interesan (de dependencias de cmake):

/home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-gcc 
-DCFG_TUSB_DEBUG=0 
-DCFG_TUSB_MCU=OPT_MCU_RP2040 
-DCFG_TUSB_OS=OPT_OS_PICO 
-DLIB_BOOT_STAGE2_HEADERS=1 
-DLIB_PICO_ATOMIC=1 
-DLIB_PICO_BIT_OPS=1 
-DLIB_PICO_BIT_OPS_PICO=1 -DLIB_PICO_CLIB_INTERFACE=1 -DLIB_PICO_CRT0=1 -DLIB_PICO_CXX_OPTIONS=1
-DLIB_PICO_DIVIDER=1 -DLIB_PICO_DIVIDER_COMPILER=1 -DLIB_PICO_DOUBLE=1 -DLIB_PICO_DOUBLE_COMPILER=1
-DLIB_PICO_FIX_RP2040_USB_DEVICE_ENUMERATION=1 -DLIB_PICO_FLASH=1 -DLIB_PICO_FLOAT=1
-DLIB_PICO_FLOAT_PICO=1 -DLIB_PICO_INT64_OPS=1 -DLIB_PICO_INT64_OPS_COMPILER=1 -DLIB_PICO_MALLOC=1
-DLIB_PICO_MEM_OPS=1 -DLIB_PICO_MEM_OPS_COMPILER=1 -DLIB_PICO_NEWLIB_INTERFACE=1 -DLIB_PICO_PLATFORM=1
-DLIB_PICO_PLATFORM_COMPILER=1 -DLIB_PICO_PLATFORM_PANIC=1 -DLIB_PICO_PLATFORM_SECTIONS=1
-DLIB_PICO_PRINTF=1 -DLIB_PICO_PRINTF_PICO=1 -DLIB_PICO_RUNTIME=1 -DLIB_PICO_RUNTIME_INIT=1
-DLIB_PICO_STANDARD_BINARY_INFO=1 -DLIB_PICO_STANDARD_LINK=1 -DLIB_PICO_STDIO=1 -DLIB_PICO_STDIO_UART=1
-DLIB_PICO_STDIO_USB=1 -DLIB_PICO_STDLIB=1 -DLIB_PICO_SYNC=1 -DLIB_PICO_SYNC_CRITICAL_SECTION=1
-DLIB_PICO_SYNC_MUTEX=1 -DLIB_PICO_SYNC_SEM=1 -DLIB_PICO_TIME=1 -DLIB_PICO_TIME_ADAPTER=1
-DLIB_PICO_UNIQUE_ID=1 -DLIB_PICO_UTIL=1 -DPICO_32BIT=1 -DPICO_BOARD=\"pico2\" -DPICO_BUILD=1
-DPICO_CMAKE_BUILD_TYPE=\"Release\" -DPICO_COPY_TO_RAM=0 -DPICO_CXX_ENABLE_EXCEPTIONS=0
-DPICO_NO_FLASH=0 -DPICO_NO_HARDWARE=0 -DPICO_ON_DEVICE=1 -DPICO_RISCV=1 
-DPICO_RP2040_USB_DEVICE_UFRAME_FIX=1 -DPICO_RP2350=1 -DPICO_TARGET_NAME=\"07-ledon\" 
-DPICO_USE_BLOCKED_RAM=0 
-I/home/obijuan/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/. 
-I/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_atomic/include 
-I/home/obijuan/Develop/pico/pico-sdk/lib/tinyusb/src 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/common/pico_stdlib_headers/include
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_gpio/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/common/pico_base_headers/include 
-isystem /home/obijuan/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build/generated/pico_base 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/boards/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2350/pico_platform/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2350/hardware_regs/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_base/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_platform_compiler/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_platform_panic/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_platform_sections/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_dcp/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2350/hardware_structs/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_rcp/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_hazard3/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/common/hardware_claim/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_sync/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_sync_spin_lock/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_irq/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_uart/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_resets/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_clocks/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_pll/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_vreg/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_watchdog/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_ticks/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_bootrom/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/common/boot_picoboot_headers/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/boot_bootrom_headers/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_boot_lock/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_flash/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/common/pico_time/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_timer/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/common/pico_sync/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/common/pico_util/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_time_adapter/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_xosc/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_divider/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_runtime/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_runtime_init/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/common/pico_bit_ops_headers/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/common/pico_divider_headers/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_double/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_float/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_malloc/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/common/pico_binary_info/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_printf/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_stdio/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_stdio_uart/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_stdio_usb/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_unique_id/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_flash/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_xip_cache/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/common/pico_usb_reset_interface_headers/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_riscv/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_multicore/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/common/boot_picobin_headers/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2350/boot_stage2/include 
-isystem /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/include 
-march=rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb -mabi=ilp32 -g -O3 -DNDEBUG 
-ffunction-sections -fdata-sections -o CMakeFiles/07-ledon.dir/07-ledon.S.o   
-c /home/obijuan/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/07-ledon.S


/home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-objdump 
  -h /home/obijuan/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build/07-ledon.elf > 07-ledon.dis


/home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-objdump 
  -d /home/obijuan/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build/07-ledon.elf >> 07-ledon.dis


/home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-objcopy 
  -Oihex /home/obijuan/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build/07-ledon.elf 07-ledon.hex

/home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-objcopy 
  -Obinary /home/obijuan/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build/07-ledon.elf 07-ledon.bin

_deps/picotool/picotool uf2 convert --quiet 
  /home/obijuan/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build/07-ledon.elf 07-ledon.uf2 --family rp2350-riscv --abs-block

Voy a empezar por la conversión de elf a uf2

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ _deps/picotool/picotool uf2 convert 07-ledon.elf 07-ledon.uf2 --family rp2350-riscv --abs-block

Ahora que ya se generar el uf2 voy a hacer un experimento. En el CMakeList voy a dejar lo mínimo. Quiero quitar todas las dependencias, generar un elf, convertirlo a uf2 y probar

El elf actual tiene muchas dependencias. Esto es lo que ocupan los ficheros:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ ls -l *.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 21:15 01-blinky-gpio15.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 21:15 02-ledon-gpio15.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 21:15 03-ledon-gpio15.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 21:15 04-ledon-gpio0.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 21:16 05-ledon.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 21:16 06-ledon.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 22:08 07-ledon.uf2
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ ls -l *.elf
-rwxrwxr-x 1 obijuan obijuan 316924 Apr 27 21:15 01-blinky-gpio15.elf
-rwxrwxr-x 1 obijuan obijuan 316740 Apr 27 21:15 02-ledon-gpio15.elf
-rwxrwxr-x 1 obijuan obijuan 316452 Apr 27 21:15 03-ledon-gpio15.elf
-rwxrwxr-x 1 obijuan obijuan 316516 Apr 27 21:15 04-ledon-gpio0.elf
-rwxrwxr-x 1 obijuan obijuan 316604 Apr 27 21:16 05-ledon.elf
-rwxrwxr-x 1 obijuan obijuan 316652 Apr 27 21:16 06-ledon.elf
-rwxrwxr-x 1 obijuan obijuan 316652 Apr 27 21:16 07-ledon.elf
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ 

He reducido el CMakeList.txt:

#-- Ejemplo 07-ledon
add_executable(07-ledon 07-ledon.S gpio.h)
target_include_directories(07-ledon PRIVATE .)
target_link_libraries(07-ledon pico_stdlib)

Compilamos, y no se genera el 07-ledon.uf2. Pero ya sabemos cómo generarlo. Vemos los tamaños:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ ls -l *.uf2 && ls -l *.elf
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 21:15 01-blinky-gpio15.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 21:15 02-ledon-gpio15.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 21:15 03-ledon-gpio15.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 21:15 04-ledon-gpio0.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 21:16 05-ledon.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 21:16 06-ledon.uf2
-rw-rw-r-- 1 obijuan obijuan 12800 Apr 27 22:18 07-ledon.uf2
-rwxrwxr-x 1 obijuan obijuan 316924 Apr 27 21:15 01-blinky-gpio15.elf
-rwxrwxr-x 1 obijuan obijuan 316740 Apr 27 21:15 02-ledon-gpio15.elf
-rwxrwxr-x 1 obijuan obijuan 316452 Apr 27 21:15 03-ledon-gpio15.elf
-rwxrwxr-x 1 obijuan obijuan 316516 Apr 27 21:15 04-ledon-gpio0.elf
-rwxrwxr-x 1 obijuan obijuan 316604 Apr 27 21:16 05-ledon.elf
-rwxrwxr-x 1 obijuan obijuan 316652 Apr 27 21:16 06-ledon.elf
-rwxrwxr-x 1 obijuan obijuan 311256 Apr 27 22:16 07-ledon.elf

Tanto el elf como el uf2 son ligeramente más pequeños

Ahora reduzco el cmakelist todavía más, quitando las dependencias de la pico_stdlib:

#-- Ejemplo 07-ledon
add_executable(07-ledon
07-ledon.S gpio.h
)
target_include_directories(07-ledon PRIVATE .)
target_link_libraries(07-ledon)

Recompilamos y vemos los archivos generados:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ ls -l *.uf2 && ls -l *.elf
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 22:27 01-blinky-gpio15.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 22:27 02-ledon-gpio15.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 22:27 03-ledon-gpio15.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 22:27 04-ledon-gpio0.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 22:28 05-ledon.uf2
-rw-rw-r-- 1 obijuan obijuan 13312 Apr 27 22:28 06-ledon.uf2
-rwxrwxr-x 1 obijuan obijuan 316924 Apr 27 22:27 01-blinky-gpio15.elf
-rwxrwxr-x 1 obijuan obijuan 316740 Apr 27 22:27 02-ledon-gpio15.elf
-rwxrwxr-x 1 obijuan obijuan 316452 Apr 27 22:27 03-ledon-gpio15.elf
-rwxrwxr-x 1 obijuan obijuan 316516 Apr 27 22:27 04-ledon-gpio0.elf
-rwxrwxr-x 1 obijuan obijuan 316604 Apr 27 22:28 05-ledon.elf
-rwxrwxr-x 1 obijuan obijuan 316652 Apr 27 22:28 06-ledon.elf
-rwxrwxr-x 1 obijuan obijuan  13784 Apr 27 22:28 07-ledon.elf

El nuevo elf es mucho menor. Generamos el uf2...

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ _deps/picotool/picotool uf2 convert 07-ledon.elf 07-ledon.uf2 --family rp2350-riscv --abs-block
RP2350-E10: Adding absolute block to UF2 targeting 0x10ffff00
ERROR: entry point is not in mapped part of file
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$

Pero sale un ERROR... por tanto hay que volver al cmakelist anterior

08-ledon

En este ejemplo voy a intentar compilar "a pelo", sin utilizar cmake. Me voy a basar en este post:

http://blog.wolfman.com/articles/2025/3/23/bare-metal-gpio-twiddling-for-risc-v-on-rpi-pico2

Este es el comando para ensamblar

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ /home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-as -g -march=rv32imac -mabi=ilp32 -I.. -o 08-ledon.o ../08-ledon.S 
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$

Este es el script de linkado: linker.ld

OUTPUT_ARCH(riscv)
    ENTRY(_start)

    MEMORY {
        FLASH (rx)  : ORIGIN = 0x10000000, LENGTH = 2M
        RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
    }

    SECTIONS {
        .text : {
            *(.text*)
        } > FLASH

        .data : {
            *(.data*)
        } > RAM

        .bss : {
            *(.bss*)
        } > RAM

        .stack (NOLOAD) : {
            . = ALIGN(8);
            _stack_top = . + 4K; /* 4KB stack */
        } > RAM
    }

Para linkar usamos este comando:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ /home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-ld -g -m elf32lriscv -T ../linker.ld -o 08-ledon.elf 08-ledon.o
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$

Se general el 08-ledon.elf. Lo intentamos convertir a uf2... pero debería dar el mensaje de error, como antes. Lo probamos:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ _deps/picotool/picotool uf2 convert 08-ledon.elf 08-ledon.uf2 --family rp2350-riscv --abs-bloc
k
RP2350-E10: Adding absolute block to UF2 targeting 0x10ffff00
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$

Convertir lo ha convertido... pero no debería funcionar por el tema de la firma. Vamos a probar NO funciona, como era de esperar. Al copiarlo no hace nada, y a los pocos segundos se vuelve a montar el dispositivo usb. Cuando es un uf2 válido no se monta

Al añadir la firma siiiiiii!!! Se carga y se enciende el led!! Vaaaamos!!!!

Voy a recapitular todo. Este es el programa:

.section .text
.global _start

.include "gpio.h"

_start:
    la sp, _stack_top   # Load stack pointer
    call main           # Call main
    wfi                 # Wait for interrupt (to save power)
    j _start            # Loop forever (should never reach here)

# -----------------------------------------------------------------------------
.p2align 8 # This special signature must appear within the first 4 kb of
image_def: # the memory image to be recognised as a valid RISC-V binary.
# -----------------------------------------------------------------------------

.word 0xffffded3
.word 0x11010142
.word 0x00000344
.word _start
.word _stack_top
.word 0x000004ff
.word 0x00000000
.word 0xab123579

main:   

    #-- Configuracion de GPIO0 para controlarse por software
    li t0, GPIO25_CTRL  
    li t1, FUNC_SOFTWARE          
    sw t1, 0(t0)        

    #-- Configuracion de GPIO0 como salida
    li t0, GPIO_OE      
    li t1, BIT25
    sw t1, 0(t0)

    #-- Habilitar el pin GPIO0
    li t0, GPIO25_PAD_ISO  
    li t1, PAD_ENABLE
    sw t1, 0(t0)

    # -- Encender el LED
    li t0, GPIO_OUT
    li t1, BIT25    #-- Activar el bit 25
    sw t1, 0(t0)  

    # -- FIN
inf:  j inf
      nop

Estos son los comandos completos para probarlo:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ /home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-as -g -march=rv32imac -mabi=ilp32 -I.. -o 08-ledon.o ../08-ledon.S
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ /home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-ld -g -m elf32lriscv -T ../linker.ld -o 08-ledon.elf 08-ledon.o
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$ _deps/picotool/picotool uf2 convert 08-ledon.elf 08-ledon.uf2 --family rp2350-riscv --abs-block
RP2350-E10: Adding absolute block to UF2 targeting 0x10ffff00
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/01-Ejemplos-asm/build$

Esto genera el fichero 08-ledon.uf2 correcto

09-ledon

Este es el mismo ejemplo que el 8, pero llevado a un directorio nuevo. Ya no se necesita cmake. Se ha creado el script build.sh que realiza todo el proceso de ensamblado, linkado y conversión a uf2. También está el script clean.sh para limpiar todo y run.sh para copiar el uf2 a la pico2. Este es el proceso:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/02-Ejemplos-asm-no-sdk$ ./build.sh 
RP2350-E10: Adding absolute block to UF2 targeting 0x10ffff00
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/02-Ejemplos-asm-no-sdk$ # Poner pico en modo boot
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/02-Ejemplos-asm-no-sdk$ ./run.sh 
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/02-Ejemplos-asm-no-sdk$ 

La parte de la firma está guardada en el fichero boot.h

# -----------------------------------------------------------------------------
# Firma especial para que la pico2 reconozca este archivo como 
# un binario RISC-V.
# Debe estar situada en la primera sección de 4kb de la memoria
# -----------------------------------------------------------------------------

.p2align 8 

image_def:
.word 0xffffded3
.word 0x11010142
.word 0x00000344
.word _start
.word _stack_top
.word 0x000004ff
.word 0x00000000
.word 0xab123579

El programa de ejemplo es este:

# -- Se debe incluir para generar una imagen válida
.include "boot.h"

# -- Definición de constantes para accer 
# -- a los GPIOs
.include "gpio.h"

.section .text

# -- Punto de entrada
.global _start
_start:
    
    #-- Configuracion de GPIO0 para controlarse por software
    li t0, GPIO25_CTRL  
    li t1, FUNC_SOFTWARE          
    sw t1, 0(t0)        

    #-- Configuracion de GPIO0 como salida
    li t0, GPIO_OE      
    li t1, BIT25
    sw t1, 0(t0)

    #-- Habilitar el pin GPIO0
    li t0, GPIO25_PAD_ISO  
    li t1, PAD_ENABLE
    sw t1, 0(t0)

    # -- Encender el LED
    li t0, GPIO_OUT
    li t1, BIT25    #-- Activar el bit 25
    sw t1, 0(t0)  

    # -- FIN
inf:  j inf

2025-04-28

03-blinky

Continuo haciendo ejemplos... los ejemplos de la carpeta 01-ejemplos son para cmake. Los ejemplos de la carpeta 02-ejemplos son los que están en ensamblador y sin usar el sdk. A partir de ahora todos los ejemplos ya será así, y los pondré en carpetas sucesivas comenzando por 03-blinky. Cada carpeta tiene un ejemplo independiente

Este es el fichero blinky.S

# ----------------------------------------------
 # -- Parpadeo del LED, mediante espera activa 
 # ----------------------------------------------

# -- Se debe incluir para generar una imagen válida
.include "boot.h"

# -- Definición de constantes para acceder 
# -- a los GPIOs
.include "gpio.h"

.section .text

# ------------------------------
# -- Configurar el LED
# ------------------------------
config_led:

    #-- Configuracion de GPIO0 para controlarse por software
    li t0, GPIO25_CTRL  
    li t1, FUNC_SOFTWARE          
    sw t1, 0(t0)

    #-- Habilitar el pin GPIO0
    li t0, GPIO25_PAD_ISO  
    li t1, PAD_ENABLE
    sw t1, 0(t0)

    #-- Configuracion de GPIO0 como salida
    li t0, GPIO_OE      
    li t1, BIT25
    sw t1, 0(t0)
    ret

# ---------------------
# -- Encender el LED
# ---------------------
led_on:
    li t0, GPIO_OUT
    li t1, BIT25    #-- Activar el bit 25
    sw t1, 0(t0)
    ret  

# ---------------------
# -- Apagar el LED
# ---------------------
led_off:
    li t0, GPIO_OUT
    sw zero, 0(t0)
    ret

# -----------------------------------------------
# -- Delay
# -- Realizar una pausa de medio segundo aprox.
# -----------------------------------------------
delay:
    # -- Usar t0 como contador descendente
    li t0, 0xFFFFF
delay_loop:
    beq t0,zero, delay_end_loop
    addi t0, t0, -1
    j delay_loop

    # -- Cuando el contador llega a cero
    # -- se termina
delay_end_loop:
    ret


# -- Punto de entrada
.global _start
_start:
    
    #-- Configurar el LED
    jal config_led    

loop:
    # -- Encender el LED
    jal led_on

    # -- Esperar
    jal delay

    # -- Apagar el LED
    jal led_off

    # -- Esperar
    jal delay

    # -- Repetir
    j loop

Se han creado las primeras funciones, para comprobar que todo va ok. Como todas son funciones hoja, no es necesario utilizar la pila. Por ello no está inicializado el registro sp

04-blinky

Este es el mismo ejemplo del blinky, pero separado en ficheros:

En led.S tenemos las funciones de los LEDs

# --- Funciones de interfaz
.global config_led
.global led_on
.global led_off

# -- Definición de constantes para acceder 
# -- a los GPIOs
.include "gpio.h"

.section .text

# ------------------------------
# -- Configurar el LED
# ------------------------------
config_led:

    #-- Configuracion de GPIO0 para controlarse por software
    li t0, GPIO25_CTRL  
    li t1, FUNC_SOFTWARE          
    sw t1, 0(t0)

    #-- Habilitar el pin GPIO0
    li t0, GPIO25_PAD_ISO  
    li t1, PAD_ENABLE
    sw t1, 0(t0)

    #-- Configuracion de GPIO0 como salida
    li t0, GPIO_OE      
    li t1, BIT25
    sw t1, 0(t0)
    ret

# ---------------------
# -- Encender el LED
# ---------------------
led_on:
    li t0, GPIO_OUT
    li t1, BIT25    #-- Activar el bit 25
    sw t1, 0(t0)
    ret  

# ---------------------
# -- Apagar el LED
# ---------------------
led_off:
    li t0, GPIO_OUT
    sw zero, 0(t0)
    ret

Es es el fichero delay.S:

# -- Funciones de interfaz
.global delay

.section .text

# -----------------------------------------------
# -- Delay
# -- Realizar una pausa de medio segundo aprox.
# -----------------------------------------------
delay:
    # -- Usar t0 como contador descendente
    li t0, 0xFFFFF
delay_loop:
    beq t0,zero, delay_end_loop
    addi t0, t0, -1
    j delay_loop

    # -- Cuando el contador llega a cero
    # -- se termina
delay_end_loop:
    ret

Y este es el programa principal: blinky

# ----------------------------------------------
 # -- Parpadeo del LED, mediante espera activa 
 # ----------------------------------------------

# -- Se debe incluir para generar una imagen válida
.include "boot.h"

.section .text

# -- Punto de entrada
.global _start
_start:
    
    #-- Configurar el LED
    jal config_led    

loop:
    # -- Encender el LED
    jal led_on

    # -- Esperar
    jal delay

    # -- Apagar el LED
    jal led_off

    # -- Esperar
    jal delay

    # -- Repetir
    j loop

Hacia el puerto serie

Lo siguiente es poner en marcha la UART para tener comunicaciones serie. No se utiliza el USB integrado de la pico, sino que vamos a usar la uart interna y sacar las señales TX y RX por pines de la pico. Tenemos señales TTL de 3.3v que habrá que enviar al PC a través de un cables USB-serie

Así, la pico estará conectada al pc mediante 2 USB. Uno es para la grabación, y el otro es para el puerto serie

Para poner la uart en marcha hay que leer mucha información. Voy a empezar primero por echar un vistazo a los ejemplos de pico-examples

En los pico-examples está el ejemplo hello_uart.c, que genera una uart conectada a los pines 0 y 1:

https://github.com/raspberrypi/pico-examples/blob/master/uart/hello_uart/hello_uart.c

Voy a intentar poner en marcha este ejemplo. Lo primero es compilarlo. Luego crearé un setup usando la Alhambra-II como conversor usb-serie. De momento sólo en el sentido pico2 --> Alhambra --> PC (Porque no tengo claro la compatibilidad de tensiones entre pico2 y alhambra). Si conecto la salida de la Alhambra-II a la entrada de la pico2 no tengo claro qué pasará... la salida de la Alhambra-II en principio es de 5v... o no? No lo tengo claro...

  • Paso 1: Compilar los ejemplos de la UART

Abrimos un terminal y realizamos las exportaciones necesarias:

export PICO_SDK_PATH=/home/obijuan/Develop/pico/pico-sdk/
export PICO_TOOLCHAIN_PATH=/home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530

Ahora entramos en el directorio pico-examples/uart y ejecutamos el cmake -DPICO_PLATFORM=rp2350-riscv ..

obijuan@JANEL:~/Develop/pico/pico-examples/uart$ cmake -DPICO_PLATFORM=rp2350-riscv ..
PICO_SDK_PATH is /home/obijuan/Develop/pico/pico-sdk
Target board (PICO_BOARD) is 'pico2'.
Using board configuration from /home/obijuan/Develop/pico/pico-sdk/src/boards/include/boards/pico2.h
Pico Platform (PICO_PLATFORM) is 'rp2350-riscv'.
Defaulting compiler (PICO_COMPILER) to 'pico_riscv_gcc' since not specified.
Configuring toolchain based on PICO_COMPILER 'pico_riscv_gcc'
-- The C compiler identification is GNU 14.1.0
[...]
-- Configuring done (3.0s)
-- Generating done (3.3s)
-- Build files have been written to: /home/obijuan/Develop/pico/pico-examples/uart
obijuan@JANEL:~/Develop/pico/pico-examples/uart$ cmake -DPICO_PLATFORM=rp2350-riscv ..

Ahora compilamos:

obijuan@JANEL:~/Develop/pico/pico-examples/uart$ make
[  0%] Building ASM object pico-sdk/src/rp2350/boot_stage2/CMakeFiles/bs2_default.dir/compile_time_choice.S.o
[  0%] Linking ASM executable bs2_default.elf
[  0%] Built target bs2_default
[  0%] Generating bs2_default.bin
[  0%] Generating bs2_default_padded_checksummed.S
[...]
[100%] Building C object sha/mbedtls_sha256/CMakeFiles/mbedtls_sha256.dir/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_sha256/sha256.c.o
[100%] Linking CXX executable mbedtls_sha256.elf
[100%] Built target mbedtls_sha256
obijuan@JANEL:~/Develop/pico/pico-examples/uart$

Y lo cargamos en la pico2:

obijuan@JANEL:~/Develop/pico/pico-examples/uart/uart/hello_uart$ cp hello_uart.uf2 /media/obijuan/RP2350/

Se supone que por el pin de transmisión (TX) saca los valores en serie. Ese pin de TX lo voy a meter por un pin de la Alhambra-II donde habrá un receptor uart y un transmisor para enviarlo al PC

Así que primero voy a preparar la Alhambra-II para funcionar. Voy a hacer el circuito de eco por el puerto serie, para comprobar primero

El circuito para icestudio es eco.ice. Tiene esta pinta:

Lo probamos, para comprobar que está todo ok:

Ya estamos listo para hacer la prueba. En la FPGA metemos un circuito que recibe el dato serie de la pico2 y lo envía al PC. De momento sólo en ese sentido. Además lo saca por los LEDs

La alhambra recibe por el pin D13, y lo transmite al pc. La velocidad es de 115200 baudios

La pico2 transmite por el ping GPIO0 así que tiramos un cable desde pico2.gpio0 --> Alhambra.D13

De momento sólo tengo un único cable usb. Lo dejamos conectado a la Alhambra-II Para alimentar la pico tiramos un cable desde Alhambra.vcc --> pico2.VSYS. El pin VSYS es la entrada de tensión para alimentar externamente la pico2. Incorpora un regulador por lo que se le puede poner 5v sin problemas Unimos las masas. Alhambra.GND --> pico2.GND

Abrimos el tio. El programa hello_uart envía la cadena cuando se enciende la placa, y luego se queda sin hacer nada

Este es el setup que tengo montado para las pruebas:

Y esto es lo que aparece en el terminal:

¡¡¡FUNCIONA!!! Sale correctamente el mensaje que envía la pico2: "AB, Hello UART!"

El carácter inicial es debido al encendido. Hay que quitar el cable de vcc y volver a conectarlo para resetear la pico2 y hacer que arranque el programa, por eso sale "mierda" al principio

Este setup es la caña!! Ahora ya puedo hacer pruebas con la uart de la pico!!! De momento sólo con el transmisor

2025-04-29

Envío de un carácter (no resuelto)

El siguiente objetivo es enviar un único carácter, desde mis propias rutinas en ensamblador. Para ello tengo primero que estudiar todos registros de la UART de la pico2, conocer su funcionamiento y saber qué valores usar para configurarlos

En este enlace hay información sobre la API en C de manejo de la uart: https://www.raspberrypi.com/documentation/pico-sdk/hardware.html#group_hardware_uart

Estos son los puntos a implementar:

  1. Configurar el GPIO0 para asignárselo al pin 0 de la UART (TX)
  2. Inicializar la UART: Modos de funcionamiento y velocidad. Queremos 115200 baudios
  3. Envío de un carácter

Vamos con el punto 1. Tenemos que asignar al registro de control del GPIO0 el valor correspondiente para que se seleccione el pin tx de la uart0. Ya sabemos que si asignamos el valor 5 tenemos control por software. Vamos a buscar en el datasheet el valor para usar el TX de la uart0

La información la encotramos en la página 587 (Sección 9.4). Hay que asignarle la función F2, que se corresponde con UART0 TX. Por ello hay que escribir el valor 2 en el registro de control (Página 608, sección 9.11)

Ahora a por el punto 2. Este es más complicado. Vamos a emular la función uart_init().

Esta función está definida aquí: /home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_uart/uart.c

Lo primero que hace es:

uart_reset(uart);
uart_unreset(uart);

Vamos a analizar uart_reset():

static inline void uart_reset(uart_inst_t *uart) {
    reset_block_num(uart_get_reset_num(uart));
}

/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_uart/include/hardware/uart.h

static inline uint uart_get_reset_num(uart_inst_t *uart) {
    return UART_RESET_NUM(uart);
}

Esta función devuelve el valor RESET_UART0 = 26,

Por ello, la función uart_reset() se implementa en realidad como: reset_block_num(26)

/home/obijuan/Develop/pico/pico-sdk/src/rp2_common/hardware_resets/include/hardware/resets.h

static inline void reset_block_num(uint32_t block_num) {
    reset_block_reg_mask(&resets_hw->reset, 1u << block_num);
}

```c
static __force_inline  void reset_block_reg_mask(io_rw_32 *reset, uint32_t mask) {
    hw_set_bits(reset, mask);
}

hw_set_bits() pone los bits del registro indicado (reset) a 1, según la máscara

Hay más información sobre el reset en la página 502 del datasheet

El registro RESET 0x40020000 se utiliza para hacer el reset de diferentes periféricos, entre ellos la UART. El BIT 26 de este registro hace el reset de la UART!

Por tanto para hacer el reset hay que escribir el valor 0x04000000 (Bit 26) en el registro de RESET

Lo siguiente es el unreset, que llama a esta función:

static inline void unreset_block_num_wait_blocking(uint block_num) {
    invalid_params_if(HARDWARE_RESETS, block_num > NUM_RESETS);
    unreset_block_reg_mask_wait_blocking(&resets_hw->reset, &resets_hw->reset_done, 1u << block_num);
}

Esta es la función bloqueante del reset. Se quita el bit de reset (se pone a 0) y se espera un bit del registro reset_done

static __force_inline void unreset_block_reg_mask_wait_blocking(io_rw_32 *reset, io_ro_32 *reset_done, uint32_t mask) {
    hw_clear_bits(reset, mask);
    while (~*reset_done & mask)
        tight_loop_contents();
}

El registro RESET_DONE se pone a 1 cuando ha finalizado el proceso de reset del periférico indicado

He hecho este programa de prueba para comprobar que el bit 26 de RESET_DONE se pone a 1. Si es así se enciende el led. Si es 0, se queda un bucle hasta que se ponga a 1

# -- Se debe incluir para generar una imagen válida
.include "boot.h"

.include "gpio.h"

# -----------------------------------
# -- Registro de Control del Reset 
# -----------------------------------
# https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf#tab-registerlist_resets
# 
# * Bit 26: UART0
# -----------------------------------
.equ RESET_CTRL, 0x40020000

# -----------------------------------
# -- Registro de Status del reset
# -- Pone a 1 el bit correspondiente
# -- cuando el periférico se ha reseteado 
# -----------------------------------
.equ RESET_DONE, 0x40020008


.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Configurar el LED
    jal config_led    

    #-- Configurar pin GPIO0 como UART0-TX
    li t0, GPIO00_CTRL
    li t1, FUNC_UART0_TX
    sw t1, 0(t0)

    #-- Activar el reset de la UART0
    li t0, RESET_CTRL
    li t1, BIT26  #-- UART0
    sw t1, 0(t0)

    #-- Desactivar el reset
    sw zero, 0(t0)

    #---------- Esperar a que el reset se complete
    li t0, RESET_DONE

wait_reset:
    lw t1, 0(t0)  #-- Leer estado del reset
    li t2, BIT26  #-- Mascara para lectura del reset
    and t1, t1, t2 #-- Comprobar si el reset se ha completado
    beq t1,zero, wait_reset

    # -- Encender el LED
    jal led_on


    #-- ddd

        #-- Fin
inf:    j inf

Sin embargo, el LED NUNCA se enciende. En principio es porque el bit 26 del registro Reset_done nunca se pone a 1. Es decir, que no se completa nunca el reset. O estamos leyendo de las direcciones equivocadas. Hay un error y hay que descubrir cuál es...

Lo primero que voy a hacer es validar el código con simulador del rars, para asegurarme 100% que la logica es la correcta

La lógica está ok. Compruebo en el rars con este programa:

.eqv RESET_DONE, 0x10010080
.eqv BIT26, 0x04000000

.text

    #---------- Esperar a que el reset se complete
    li t0, RESET_DONE

wait_reset:
    lw t1, 0(t0)  #-- Leer estado del reset
    li t2, BIT26  #-- Mascara para lectura del reset
    and t1, t1, t2 #-- Comprobar si el reset se ha completado
    beq t1,zero, wait_reset

    nop

Al depurar se queda en el bucle infinito. Si se cambia el valor de la posición de memoria del registro RESET_DONE (que en el rars la he puesto en la 0x10010080, entonces el bucle termina

Otra cosa que hay que probar es el borrado del bit del RESET_CTRL, que yo lo hago escribiendo directamente un 0, pero en el programa en C hacen otra cosa diferente... lo voy a investigar...

La función a rastrear es: hw_clear_bits(reset, mask);

__force_inline static void hw_clear_bits(io_rw_32 *addr, uint32_t mask) {
    *(io_rw_32 *) hw_clear_alias_untyped((volatile void *) addr) = mask;
}

sustituyendo los parámetros queda:

*(io_rw_32 *) hw_clear_alias_untyped(reset) = mask;

Esa macro se define como:

#define hw_clear_alias_untyped(addr) ((void *)(REG_ALIAS_CLR_BITS + hw_alias_check_addr(addr)))

Sustituyendo queda:

*(io_rw_32 *) ((void *)(REG_ALIAS_CLR_BITS + hw_alias_check_addr(reset))) = mask

La definición es:

#define hw_alias_check_addr(addr) ((uintptr_t)(addr))

Sustituimos:

*(io_rw_32 *) ((void *)(REG_ALIAS_CLR_BITS + (uintptr_t)(reset) )) = mask
#define REG_ALIAS_CLR_BITS (_u(0x3) << _u(12))

Donde -u(x) equivale a x (No sé el motivo de esta definición)

#define REG_ALIAS_CLR_BITS (0x3 << 12)

Es decir:

#define REG_ALIAS_CLR_BITS (0x3000)

Por tanto a reset se le suma el offset 0x3000

*(reset + 0x3000) = mask

Es decir, que se almacena mask en la posicion de memoria reset+0x3000

En el programa en ensamblador definimos:

.equ RESET_CTRL_CLR, 0x40023000

Para borrar el bit de reset se haría así:

#-- Desactivar el reset
    li t0, RESET_CTRL_CLR
    li t1, BIT26  #-- UART0
    sw t1, 0(t0)

Sin embargo tampoco se ha encendido el LED... no detectamos si se ha hecho el reset o no...

Tampoco se puede descartar que esté bien este método porque no he visto en el datasheet nada de que el registro de reset_ctrl se pueda borrar de esa forma

Sí he visto algo parecido con los GPIOs... así que igual es el momento de volver a encender LEDs pero de esta forma

Nada... joder... no saco nada en claro... Lo único que se me ocurre es hacer una rutina para comprobar los valores de un registro. Sabemos que por defecto el registro RESET_CTRL está a 1. Vamos a leer sus bits.

Lo que necesito es hacerme rutinas de depuración específicas. Así que voy a empezar por lo más básico. Necesito poder conectar un pulsador a la pico2 para mis acciones de depuración

Lectura del pin GPIO0 (no solucionado)

En este ejemplo vamos a aprender a leer un pin de entrada de la pico. Hay que aprender a configurar el pin como entrada y leer la entrada
En este ejemplo vamos a conectar un pulsador

Este es el programa... Por defecto se enciende el LED porque se ha configurado la entrada como un pull-up

# -- Se debe incluir para generar una imagen válida
.include "boot.h"

.include "gpio.h"

.section .text


# -- Punto de entrada
.global _start
_start:

    # ----------------------------------
    # -- Configuracion del GPIO0
    # ----------------------------------
    #-- Control por software
    li t0, GPIO00_CTRL  
    li t1, FUNC_SOFTWARE          
    sw t1, 0(t0)

    #-- Configuración del PAD: ENTRADA
    #-- ISO | OD | IE | DRIVE | PUE | PDE | SCHMITT | SLEWFAST 
    #--  0  | 1  | 1  | 00    | 1   | 0   |   1     | 1        
    li t0, PAD_GPIO0 
    li t1, 0x0CB
    sw t1, 0(t0)

     #-- Configurar el LED
    jal config_led  


loop:
    #-- Direccion de los GPIOs
    li t0, GPIO_IN

    #-- Leer el GPIO0
    lw t1, 0(t0)
    li t2, BIT0  #-- Bit a leer
    and t1, t1, t2

    #-- Mostrar el valor en el LED
    #-- Según el caso
    beq t1,zero, led_off

    #-- Encender el led
    jal led_on
    j loop

    #-- Apagar el LED
led_off:
    jal led_off
    j loop
    #-- Mostrar valor en el LED

PERO se lee siempre 1. Al meter GND por el GPIO0 no cambia. Si se configura con un pull-down, el LED se apaga. Esto es lógico porque se lee siempre 0 y es lo que se saca por el LED. Pero NO cambia. Conclusión: El PAD está como desconectado de la entrada... no hace caso. No funciona....

Se me ocurre hacer el programa en C primero para asegurarme que el setup funciona, y luego seguir con las pruebas...

Lectura del pin gpio0 en C (Funciona)

He creado el mismo ejemplo pero en C, usando cmake

#include "pico/stdlib.h"

// -- Pines de los recursos
#define LED 25
#define BTN 0

int main() {

  // -- Configurar LED
  gpio_init(LED);
  gpio_set_dir(LED, GPIO_OUT);

  // -- Configurar GPIO 0 como entrada
  gpio_init(BTN);
  gpio_set_dir(BTN, GPIO_IN);
  gpio_pull_up(BTN);
  
  //-- Mostrar el valor de GPIO0 en el LED
  while (1) {
    if (gpio_get(BTN)) {
      gpio_put(LED, 1);
    } else {
      gpio_put(LED, 0);
    }
  }

}

FUNCIONA!!!! menos mal... esta vez si...

Lectura del pin GPIO0 II

Ahora que ya tenemos el ejemplo en C funcionando, significa que el setup es correcto, y que el hardware funciona bien. Algo estoy haciendo mal con la configuración del pin como entrada...

Este es el programa desensamblado. Lo voy a analizar con más detenimiento

10000184 <main>:
10000184:	1141                	addi	sp,sp,-16
10000186:	4565                	li	a0,25
10000188:	c606                	sw	ra,12(sp)
1000018a:	c422                	sw	s0,8(sp)
1000018c:	c226                	sw	s1,4(sp)
1000018e:	d0000437          	lui	s0,0xd0000
10000192:	2099                	jal	100001d8 <gpio_init>
10000194:	020004b7          	lui	s1,0x2000
10000198:	4501                	li	a0,0
1000019a:	dc04                	sw	s1,56(s0)
1000019c:	2835                	jal	100001d8 <gpio_init>
1000019e:	4585                	li	a1,1
100001a0:	c02c                	sw	a1,64(s0)
100001a2:	4601                	li	a2,0
100001a4:	4501                	li	a0,0
100001a6:	2811                	jal	100001ba <gpio_set_pulls>
100001a8:	405c                	lw	a5,4(s0)
100001aa:	8b85                	andi	a5,a5,1
100001ac:	c789                	beqz	a5,100001b6 <main+0x32>
100001ae:	cc04                	sw	s1,24(s0)
100001b0:	405c                	lw	a5,4(s0)
100001b2:	8b85                	andi	a5,a5,1
100001b4:	ffed                	bnez	a5,100001ae <main+0x2a>
100001b6:	d004                	sw	s1,32(s0)
100001b8:	bfc5                	j	100001a8 <main+0x24>

Voy a quitar las cosas innecesarias. El puntero de pila no es necesario. Los 3 stores iniciales son para guardar los registros estáticos. Tampoco son necesarias. Pongo etiquetas, y quito las direcciones y el código máquina. El programa queda así:

  li	a0,25
  lui	s0,0xd0000
  jal	gpio_init
  lui	s1,0x2000
  li	a0,0
  sw	s1,56(s0)
  jal	gpio_init
  li	a1,1
  sw	a1,64(s0)
  li	a2,0
  li	a0,0
  jal	gpio_set_pulls
label:  lw a5,4(s0)
  andi	a5,a5,1
  beqz	a5,next
loop:   sw s1,24(s0)
  lw	a5,4(s0)
  andi	a5,a5,1
  bnez	a5,loop
next:   sw s1,32(s0)
  j	label

2025-05-1

Lectura del pin GPIO0 II (Funciona)

¡Ya lo tengo resuelto! Era un bug en el código, NO EN LA CONFIGURACIÓN del pin. El primer programa estaba bien!! Pero había una etiqueta duplicada: led_off, que se usaba tanto como etiqueta local como para la llamada a la función led_off()... ¡Qué bug más tonto!

.include "boot.h"
.include "gpio.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    # ----------------------------------
    # -- Configuracion del GPIO0
    # ----------------------------------
    #-- Control por software
    li t0, GPIO00_CTRL  
    li t1, FUNC_SOFTWARE          
    sw t1, 0(t0)

    #-- Configuración del PAD: ENTRADA
    #--  8  |  7 |  6 |  5    |  4  |  3  |   2     |   1
    #-- ISO | OD | IE | DRIVE | PUE | PDE | SCHMITT | SLEWFAST 
    #--  0  | 1  | 1  | 00    | 1   | 0   |   1     | 1        
    li t0, PAD_GPIO0 
    li t1, 0x0CB
    sw t1, 0(t0)

    #-- Configurar el LED
    jal config_led  

loop:
    #-- Direccion de los GPIOs
    li t0, GPIO_IN

    #-- Leer el GPIO0
    lw t1, 0(t0)
    andi t1, t1, BIT0

    #-- Mostrar el valor en el LED
    #-- Según el caso
    beq t1,zero, apagar_led

    #-- Encender el led
    jal led_on
    j loop

    #-- Apagar el led
apagar_led:
    jal led_off
    j loop

He conectado un pulsador entre GPIO0 y GND. Este es el escenario:

En esta animación se muestra el funcionamiento:

btn0-led-togle

En este ejemplo se cambia el estado del LED cuando se aprieta el pulsador

Se han hecho varias mejoras en la biblioteca led.s. Ahora se enciende y se apga el led sin afectar al resto de pines. Se usan los registros GPIO_OUT_SET y GPIO_OUT_CLR. También se ha añadido una nueva función: led_toggle() que cambia el estado del led. Se usa el registro GPIO_OUT_XOR

Se ha creado la nueva biblioteca button.s que permite configurar el GPIO0 como entrada, para conectarle un pulsador

La función button_press() se queda esperando hasta que el usuario apriete el pulsador

Este es el código completo de button.s:

#--------------------------------------------------
#-- Funciones de acceso al pulsador conectado
#-- al GPIO0
#--------------------------------------------------

#--- Funciones de interfaz
.global button_init
.global button_press

.include "gpio.h"

#-------------------------------------
#-- Inicializacion del pulsador
#-- Confgirar el GPIO0 como entrada
#-- y habilitar el pull-up
#-------------------------------------
button_init:

    #-- GPIO0: Control por software
    li t0, GPIO00_CTRL  
    li t1, FUNC_SOFTWARE          
    sw t1, 0(t0)

    #-- Configuración del PAD: ENTRADA
    #--  8  |  7 |  6 |  5    |  4  |  3  |   2     |   1
    #-- ISO | OD | IE | DRIVE | PUE | PDE | SCHMITT | SLEWFAST 
    #--  0  | 1  | 1  | 00    | 1   | 0   |   1     | 1        
    li t0, PAD_GPIO0 
    li t1, 0x0CB
    sw t1, 0(t0)

    ret

#------------------------------------------------
#-- Esperar a que se apriete el pulsador
#-- La funcion retorna cuando el pulsador se ha  
#-- apretado
#------------------------------------------------
button_press:

    # -- Cabecera funcion
    addi sp,sp, -16
    sw ra, 12(sp)   

    #-- Esperar hasta que el GPIO0 está a 1
    #-- (Boton no pulsado)
wait_1:

    li t0, GPIO_IN
    lw t1, 0(t0)   
    andi t1, t1, BIT0  #-- Leer pulsador del GPIO0

    #-- Si está pulsado, esperar a que se suelte
    beq t1,zero, wait_1

    #---- El boton NO está apretado
    #-- Espera antirrebotes
    jal delay 

    #-- Esperar hasta que el GOPIO0 esté a 0
    #-- (Boton pulsado)
wait_0:
    li t0, GPIO_IN
    lw t1, 0(t0)
    andi t1, t1, BIT0  #-- Leer pulsador del GPIO0

    #-- si no apretado, esperar
    bne t1,zero, wait_0

    #--- BOTON APRETADO
    #-- Espera antirrebotes
    jal delay

    #-- Fin de la funcion
    lw ra, 12(sp)
    addi sp,sp, 16
    ret

# -----------------------------------------------
# -- Delay
# -- Realizar una pausa para eliminar los rebotes
# -- del pulsador
# -----------------------------------------------
delay:
    # -- Usar t0 como contador descendente
    li t0, 0xFFFF
delay_loop:
    beq t0,zero, delay_end_loop
    addi t0, t0, -1
    j delay_loop

    # -- Cuando el contador llega a cero
    # -- se termina
delay_end_loop:
    ret

Este es el programa principal:

.include "boot.h"
.include "gpio.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Configurar el LED
    jal led_init  

    #-- Configurar el pulsador
    jal button_init


    #-- Bucle principal
loop:

    #-- Esperar hasta que se apriete el pulsador
    jal button_press

    #-- Cambiar el estado del LED
    jal led_toggle

    #-- Repetir
    j loop

El programa funciona muy bien. En esta animación se muestra el resultado:

Debug-led1

Necesitamos depurar para seguir avanzando. De momento lo único que tenemos es un LED, con el que podemos ver cualquier bit. La familia de funcione debug-led1 utilizan este único led para mostrar varios bits. Se utiliza el pulsador para visualizar el siguiente bit

Ya tengo implementadas la biblioteca debug.s, que contiene las funciones debug_led1_lsb() y debug_led1_MSB(). Permite mostrar un valor de 32 bits por el LED, bit a bit, comenzando por el bit de menor o mayor peso respectivamente

#-------------------------------------------
#-- Funcione de depuracion
#-------------------------------------------

#-- Funciones de interfaz
.global debug_led1_lsb
.global debug_led1_MSB

.section .text

#-------------------------------------------------
#-- DEPURACION. Mostrar un numero de 32 bits
#-- en el LED, bit a bit
#-- Se muestra el bit MAS SIGNIFICATIVOprimero
#-- Entradas:
#-- a0: Valor a mostrar
#-------------------------------------------------
debug_led1_MSB:

    #-- Preambulo de la funciona
    addi sp, sp, -16
    sw ra, 12(sp)
    sw s0, 8(sp)
    sw s1, 4(sp)

    #-- Guardar el valor a mostrar
    #-- en el registro s0
    mv s0, a0

    #-- Numero de bit a mostrar
    #-- Empezamos por el bit 31
    li s1,31

debug_led1_MSB_loop:

    #-- Mostar el bit actual en el LED
    mv a0, s0
    mv a1, s1
    jal print_led1

    #-- Esperar hasta que se apriete el pulsador
    jal button_press

    #-- Pasar al siguiente bit
    addi s1, s1, -1

    #-- Si es mayor o igual a 0, repetir
    bge s1, zero, debug_led1_MSB_loop

    #-- fin de la funcion
    lw s1, 4(sp)
    lw s0, 8(sp)
    lw ra, 12(sp)
    addi sp, sp, 16
    ret

#-------------------------------------------------
#-- DEPURACION. Mostrar un numero de 32 bits
#-- en el LED, bit a bit
#-- Se muestra el bit menos significativo primero
#-- Entradas:
#-- a0: Valor a mostrar
#-------------------------------------------------
debug_led1_lsb:

    #-- Preambulo de la funciona
    addi sp, sp, -16
    sw ra, 12(sp)
    sw s0, 8(sp)
    sw s1, 4(sp)

    #-- Guardar el valor a mostrar
    #-- en el registro s0
    mv s0, a0

    #-- Numero de bit a mostrar
    #-- Empezamos por el bit 0
    li s1,0

loop:

    #-- Mostar el bit actual en el LED
    mv a0, s0
    mv a1, s1
    jal print_led1

    #-- Esperar hasta que se apriete el pulsador
    jal button_press

    #-- Pasar al siguiente bit
    addi s1, s1, 1

    #-- Si es menor que 32, repetir
    li t0, 32
    blt s1, t0, loop

    #-- fin de la funcion
    lw s1, 4(sp)
    lw s0, 8(sp)
    lw ra, 12(sp)
    addi sp, sp, 16
    ret

#--------------------------------------------
#-- Mostrar en el LED el bit del valor dado 
#-- Entradas:
#-- a0: Valor que se quiere ver
#-- a1: Bit a mostrar
#---------------------------------------------
print_led1:

    #-- Preambulo de la funcion
    addi sp, sp, -16
    sw ra, 12(sp)

    #-- t0: máscara del bit a visualizar
    #-- (1 << a1)
    li t1, 1
    sll t0, t1, a1

    #-- t2: Valor aislado del bit
    and t2, a0, t0

    #-- t3 contiene el valor del bit
    sgtu t3, t2,zero

    #-- Mostrar t3 en el LED
    mv a0, t3
    jal led_set

    #-- Fin de la funcion
    lw ra, 12(sp)
    addi sp, sp, 16
    ret

Para probar su funcionamiento tenemos este programa principal. Lo que hace es mostrar el valor del puntero de pila (sp) en el LED. Mirando el desensamblado, sabemos que su valor es 0x20001000

Efectivamente al ejecutar el programa comprobamos que el valor mostrado en los leds es correcto

.include "boot.h"
.include "gpio.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Configurar el LED
    jal led_init  

    #-- Configurar el pulsador
    jal button_init

    #-- Mostrar el valor de la pila por el LED
    #-- Bit a bit, empezando por el bit 31
    #-- Con cada pulsacion se muestra el siguiente bit
    mv a0, sp
    jal debug_led1_MSB

    #-- Al terminar hacer parpadear el LED rápidamente
    jal led_blinky

    #-- Fin
inf:
    j inf

Ahora ya tengo el "poder" de ver cualquier registro, que es de muchísima ayuda

txchar

Volvemos a la carga con la transmisión serie. Tengo varías vías para seguir investigando. La idea principal es volver a poner en marcha el hola mundo, desensamblarlo y observar cómo se configura en ensamblador

No obstante, voy a depurar con las nuevas herramientas lo que tenía hecho hasta ahora y el misterior del reset...

El registro de reset está en la dirección 0x40020000. Al observarlo con el depurador LED obtenemos este valor en binario:

0000_1101 0111 1101 1111 1001 0011 1111
31:29 28 27 26 25 24
000 0 1 1 0 1
RES USBCTRL UART1 UART0 TRNG TIMER1
23 22 21 20 19 18 17 16
0 1 1 1 1 1 0 1
TIMER0 TBMAN SYSINFO SYSCFG SPI1 SPI0 SHA256 PWM
15 14 13 12 11 10 9 8
1 1 1 1 1 0 0 1
PLL_USB PLL_SYS PIO2 PIO1 PIO0 PADS_QSPI PADS_BANK0 JTAG
7 6 5 4 3 2 1 0
0 0 1 1 1 1 1 1
IO_QSPI IO_BANK0 I2C1 I2C0 HSTX DMA BUSCTRL ADC

Los valores tienen mucho sentido. Hay sistemas que NO están reseteados (a 0), porque han sido usados por el bootloader. La UART0 está reseteada

El programa utilizado para visualizar el registro de reset es este:

.include "boot.h"
.include "gpio.h"

# -----------------------------------
# -- Registro de Control del Reset 
# -----------------------------------
# https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf#tab-registerlist_resets
# 
# * Bit 26: UART0
# -----------------------------------
.equ RESET_CTRL,     0x40020000
.equ RESET_CTRL_CLR, 0x40023000

# -----------------------------------
# -- Registro de Status del reset
# -- Pone a 1 el bit correspondiente
# -- cuando el periférico se ha reseteado 
# -----------------------------------
.equ RESET_DONE, 0x40020008


.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Configurar el LED
    jal led_init  

    #-- Configurar el pulsador
    jal button_init

    # --------- Configurar la UART0

    #-- Configurar pin GPIO0 como UART0-TX
    li t0, GPIO00_CTRL
    li t1, FUNC_UART0_TX
    sw t1, 0(t0)


    #-- DEBUG: Observar el valor de registro de control
    li t0, RESET_CTRL
    lw a0, 0(t0)  #-- Leer el registro de control
    jal debug_led1_MSB

    #-- Al terminar hacer parpadear el LED rápidamente
    jal led_blinky

Vamos a observar ahora el registro RESET_DONE... a ver qué tiene... Este es el valor en binario:

0000_0010_1100_0010_0000_0110_1100_0000

Y aquí comparamos los dos:

xxx0_1101 0111 1101 1111 1001 0011 1111| RESET
xxx0_0010_1100_0010_0000_0110_1100_0000| RESET_DONE

Tinee bastante sentido. Salvo algún bit por

El bit que nos interesa es el 26, que se corresponde con la UART0. Está en reset, y el reset_done está a 0, como era de esperar... Vamos ahora a poner el bit 26 a 0...

Lo ponemos a 0... PERO lo seguimos leyendo como 1...

Probamos a leer el reset_done una vez puesto el reset a 1... Nada... se queda a 0....

Algo no estoy comprendiendo

2025-05-2

08-txchar-ini

Paso al plan B. Primero preparamos un ejemplo hola mundo sencillo en C. Tomamos el de los ejemplo de pico-examples, y lo recortamos para que sólo envíe el carácter A

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/uart.h"


#define UART_ID uart0
#define BAUD_RATE 115200

// We are using pins 0 and 1, but see the GPIO function select table in the
// datasheet for information on which other pins can be used.
#define UART_TX_PIN 0
#define UART_RX_PIN 1

int main() {
    // Set up our UART with the required speed.
    uart_init(UART_ID, BAUD_RATE);

    // Set the TX and RX pins by using the function select on the GPIO
    // Set datasheet for more information on function select
    gpio_set_function(UART_TX_PIN, UART_FUNCSEL_NUM(UART_ID, UART_TX_PIN));
    gpio_set_function(UART_RX_PIN, UART_FUNCSEL_NUM(UART_ID, UART_RX_PIN));

    // Use some the various UART functions to send out data
    // In a default system, printf will also output via the default UART

    // Send out a character without any conversions
    uart_putc_raw(UART_ID, 'A');

    return 0;
}

Comprobamos que efectivamente funciona... Lo desensamblamos

Llego al mismo problema que antes... la función uart_init() lo primero que hace es un reset de la uart0, activando el bit correspondiente. Y luego se queda esperando a que el bit 26 de RESET_DONE se ponga a 1.... PERO ESTO NUNCA SUCEDE!!!!!

La conclusión es que se hace algo antes de entrar en el main.... Lo que no entiendo es PORQUÉ NO SE PUEDE HACER RESET DE LA UART0!!!!

No queda más remedio que estudiar el código que se ejecuta antes del main... hay que estudiarlo al detalle

Mirando el fichero desensamblado no me queda claro cuál es el punto de entrada. Voy a suponer que es platform_entry

10000086 <platform_entry>:
10000086:	142010ef          	jal	100011c8 <runtime_init>
1000008a:	28ed                	jal	10000184 <main>
1000008c:	136010ef          	jal	100011c2 <exit>
10000090:	9002                	ebreak
10000092:	bffd                	j	10000090 <platform_entry+0xa>

Este código llama a runtime_init y luego a main... Así que la clave tiene que estar en runtime_init. Lo voy a ir incluyendo al inicio de txchar... Va a ser muy laborioso y complejo... ¡pero vamos a ello! Algo en claro sacaré... (espero)

La memoria FLASH empieza a partir de la dirección 0x1000_0000. Es una memoria flash serie, PERO está mapeada en memoria. Es lo que se denomina XIP (Execute in place)

Ya lo tengo migrado casi todo... La UART transmite algo... PERO no se muestra nada en los LEDs ni en el terminal... La velocidad no es la correcta, claramente... Al menos hace algo... A ver si mañana con más tranquilidad puedo descubrir algo... Una de las cosas que puedo probar es usar el analizador lógico... a ver qué señal sale...

2025-05-3

08-txchar-ini

¡¡YA ESTÁ FUNCIONANDO!!!! ¡¡VAAAAMOS!!!. Por razones desconocidas, está funcionando a 9600 baudios en vez de a 115200. Esos detalles los abordaré después, ahora lo importante es consolidar el programa actual y documentarlo

El programa es muy largo, por eso no lo pongo aquí. En la siguiente sección lo refactorizaremos y dejaremos sólo lo esencial

Este es el escenario:

El pin GPio0 es TX y se lleva a la Alhambra-II (cable amarillo) para sacarlo por el FTDI hacia el PC. Las masas están unidas (GND). Se usan dos cables USB: uno para la pico2 y otro para la Alhambra-II. En el pin RUN de la pico2 se ha conectado un pulsador para hacer reset

Se envían periódicamente los caracteres A de la pico2 al PC a una cadenacia de un segundo aproximadamente

Si abrimos un terminal seria a 9600 baudios veremos el resultado

tio /dev/ttyUSB1 -b 9600

En este animación lo vemos en funcionamiento:

09-txchar

El proyecto 08-txchar es el programa "en bruto". En 09-txchar lo vamos a ir destilando y minimizando, entiendo todas sus partes. Hay que llegar a la configuración mínima para poner la uart en marcha. Será un proceso lento, pero necesario

Para la refactorización es importante conocer los alias de los registros. Se puede encontrar su definición en este fichero de pico-sdk: src/rp2350/hardware_regs/include/hardware/regs/addressmap.h

// Register address offsets for atomic RMW aliases
#define REG_ALIAS_RW_BITS  (_u(0x0) << _u(12))
#define REG_ALIAS_XOR_BITS (_u(0x1) << _u(12))
#define REG_ALIAS_SET_BITS (_u(0x2) << _u(12))
#define REG_ALIAS_CLR_BITS (_u(0x3) << _u(12))

Suponiendo que REG es la dirección de un registro tenemos que:

  • REG: Es un registro de lectura/escritura. Se escribe el valor directametne en el registro
  • REG + 0x1000: Se hace la operación XOR de los bits indicados y los del registro: Es decir, que todos los bits que estén a 1 en el valor indicado hacen que sus bits correspondientes del registro cambien
  • REG + 0x2000: Sólo activan los bits de registro indicados en el valor
  • REG + 0x3000: Desactivan los bits del registro indicados en el valor

2025-05-7

09-txchar

Ya por fin tengo listo el ejemplo. Me ha costado bastante. Se envía el carácter A a la velocidad de 115200 baudios Toda la inicialización está en el fichero runtime.s. Tengo pendiente extraer lo mínimo para poner en marcha la uart, porque en la inicialización hay más cosas. Y tengo mucho que entender todavía, pero al menos ya tengo un ejemplo funcional

El programa principal ha quedado así:

# ----------------------------------------------
# -- Envío de un carácter por la UART
# ----------------------------------------------
.include "boot.h"
.include "gpio.h"
.include "regs.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init  

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

main_loop:

    #-- Transmitir una 'A'
    li a0, 'A'
    jal putchar

    #-- Cambiar de estado el LED
    jal led_toggle

    #-- Esperar!
    jal delay

    #-- Repetir
    j main_loop


# -----------------------------------------------
# -- Delay
# -- Realizar una pausa de medio segundo aprox.
# -----------------------------------------------
delay:
    # -- Usar t0 como contador descendente
    li t0, 0xFFFFFF
delay_loop:
    beq t0,zero, delay_end_loop
    addi t0, t0, -1
    j delay_loop

    # -- Cuando el contador llega a cero
    # -- se termina
delay_end_loop:
    ret

En el terminal vemos cómo aparecen los caracteres A, a una cadencia de 1 por segundo aproximadamente

10-btn-txchar

En este ejemplo se envía un asterisco * cada vez que se aprieta el pulsador conectado al GPIO15. He puesto todos los registros en regs.h

# ----------------------------------------------
 # -- Envío de un carácter por la UART
 # -- Con cada pulsación del pulsador GPIO15
 # ----------------------------------------------
.include "boot.h"
.include "regs.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init

    #-- Configurar el pulsador
    jal button_init15

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on

main_loop:

    #-- Transmitir un asterisco inicial
    li a0, '*'
    jal putchar

    #-- Esperar a que se apriete el pulsador
    jal button_press15

    #-- Cambiar de estado el LED
    jal led_toggle

    #-- Repetir
    j main_loop


# -----------------------------------------------
# -- Delay
# -- Realizar una pausa de medio segundo aprox.
# -----------------------------------------------
delay:
    # -- Usar t0 como contador descendente
    li t0, 0xFFFFFF
delay_loop:
    beq t0,zero, delay_end_loop
    addi t0, t0, -1
    j delay_loop

    # -- Cuando el contador llega a cero
    # -- se termina
delay_end_loop:
    ret

Este es el escenario

Y este es el resultado:

11-rxchar

Ejemplo de recepción de caracteres por la UART. Con cada carácter recibido se cambia el estado del LED
En el primer intento no he conseguido hacer que funcione. Me falta algún bit de configuración... así que voy a seguir la misma técnica de las otras veces. Primero hacer el programa en C. Luego desensamblarlo y analizarlo

He encontrado un fallo garrafal!!! No tengo conectado el cable de RX entre la pico2 y la Alhambra-II!!. Así es imposible que funcione ni el programa en C ni el de ensamblador... Voy a solucionar esto primero. Tengo que modificar el circuito de Icestudio para tirar un cable desde el RX de la FPGA hasta el pin de salida D12. Desde el D12 tiro un cable hasta la pata 2 (GPIO1) de la pico 2

Este es el circuito de Icestudio

Este es el escenario. El cable rojo es el que se ha añadido

Este es el programa en C:

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/uart.h"


#define UART_ID uart0
#define BAUD_RATE 115200

// We are using pins 0 and 1, but see the GPIO function select table in the
// datasheet for information on which other pins can be used.
#define UART_TX_PIN 0
#define UART_RX_PIN 1

int main() {
    char c;

    // Set up our UART with the required speed.
    uart_init(UART_ID, BAUD_RATE);

    // Set the TX and RX pins by using the function select on the GPIO
    // Set datasheet for more information on function select
    gpio_set_function(UART_TX_PIN, UART_FUNCSEL_NUM(UART_ID, UART_TX_PIN));
    gpio_set_function(UART_RX_PIN, UART_FUNCSEL_NUM(UART_ID, UART_RX_PIN));

    // Use some the various UART functions to send out data
    // In a default system, printf will also output via the default UART

    while (1) {
        c = uart_getc(UART_ID);

        // Send out a character without any conversions
        uart_putc_raw(UART_ID, c);
    }

Lo compilamos y lo cargamos... ¡FUNCIONA! Se puede ver el eco en el terminal. ¡Hay transmisión y recepción!

Listo!! Ya está funcionando en ensamblador. El problema original estaba bien, pero la dirección del GPIO1_CTRL estaba mal definido en las constantes (con la dirección equivocada)

Este es el programa:

# ----------------------------------------------
 # -- Recepción por la UART
 # -- Cada vez que se recibe un carácter se cambia
 # -- de estado el LED
 # ----------------------------------------------
.include "boot.h"
.include "regs.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on

main_loop:

    #-- Esperar a recibir un caracter
    jal getchar

    #-- Cambiar de estado el led
    jal led_toggle

    #-- Repetir
    j main_loop 

12-eco

Vamos ahora a por un clásico: el eco. Es prácticamente igual que el anterior, pero añadiendo una llamada a putchar

# ----------------------------------------------
 # -- Programa de ECO
 # -- Todo lo recibido se reenvía de nuevo al PC
 # -- Además se cambia el estado del LED con cada
 # -- caracter recibido
 # ----------------------------------------------
.include "boot.h"
.include "regs.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on

main_loop:

    #-- Esperar a recibir un caracter
    jal getchar

    #-- Eco
    jal putchar

    #-- Cambiar de estado el led
    jal led_toggle

    #-- Repetir
    j main_loop 

Ejecutamos el terminal con estos parámetros: tio /dev/ttyUSB1 --map ICRNL,INLCRNL

Este es el resultado:

2025-05-8

13-counter-raw8

En este ejemplo se usa la función print_raw8 para imprimir en la consola un byte (0-255). Es necesario arrancar el tio en modo hexadecimal para ver los bytes recibidos

Este es el programa

# ----------------------------------------------
 # -- Contador de 8-bits
 # -- Se imprime en la consola hexadecimal del tio
 # -- un contador que arranca en 00
 # ----------------------------------------------
.include "boot.h"
.include "regs.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on

    #-- Inicializar contador
    li s0, 0

main_loop:

    #-- Imprimir el contador
     mv a0, s0
    jal print_raw8

    #-- Cambiar de estado el led
    jal led_toggle

    #-- Incrementar contador
    addi s0, s0, 1

    #-- Esperar
    jal delay

    #-- Repetir
    j main_loop 


# -----------------------------------------------
# -- Delay
# -- Realizar una pausa de medio segundo aprox.
# -----------------------------------------------
delay:
    # -- Usar t0 como contador descendente
    li t0, 0xFFFFFF
delay_loop:
    beq t0,zero, delay_end_loop
    addi t0, t0, -1
    j delay_loop

    # -- Cuando el contador llega a cero
    # -- se termina
delay_end_loop:
    ret

Para probarlo arrancamos el tio en modo hexadecimal, con el comando:

tio /dev/ttyUSB1 --output-mode hex

Este es el resultado. Si apretamos el pulsador de reset empieza la cuenta de nuevo en 00

14-print-raw16

Vamos a hacer la rutina print_raw16 para imprimir una media palabra (16-bits) en la consola hexadecimal. La impresión se hace en BIG ENDIAN. Es decir, primero se imprime el byte de mayor peso y luego el de menor

Así es como queda la funcion print_raw16:

# ---------------------------------------------------------
# -- print_raw16:
# --   Imprmir una media palabra (16-bits) en la consola
# --  Se envia en BIG ENDIAN
# ---------------------------------------------------------
print_raw16:
    addi sp,sp,-16
    sw ra, 12(sp)
    sw s0, 8(sp)

    mv s0, a0

    #-- La media palabra está computa de 2 bytes: byte1 byte0
    #-- El byte1 es el de mayor peso

    #----- Print byte1:
    #-- Desplazar valor 8 bits a la derecha
    #-- para obtener byte1
    srli a0, s0, 8
    jal print_raw8

    #-- Print byte0
    mv a0, s0
    jal print_raw8

    lw s0, 8(sp)
    lw ra, 12(sp)
    addi sp,sp,16
    ret

El programa principal simplemente imprime 4 valores fijos de 16-bits. Si se aprieta el pulsador se vuelven a imprimir. Además se cambia el estado del led con cada pulsación

.include "boot.h"
.include "regs.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init

    #-- Configurar el boton
    jal button_init15

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on


loop:
    #-- Imprimir varios numeros de 16bits
    li a0, 0x1234
    jal print_raw16

    li a0, 0xABCD
    jal print_raw16

    li a0, 0xBACA
    jal print_raw16

    li a0, 0x0
    jal print_raw16

    #-- Esperar a que se apriete el pulsador
    jal button_press15

    #-- Cambiar estado del led
    jal led_toggle

    j loop

Este es el resultado:

Esto es tremendamente útil porque ya podemos examinar el contenido de los registros!!! Nos será de mucha ayuda para aprender el funcionamiento de los periféricos, y depurar más fácilmente

15-print-raw32

Dado que el riscv de la pico2 es de 32-bits, todos los registros de los periféricos son de 32-bits. Por eso es muy util crear la funcion print_raw32 para imprimir una palabra de 32-bits

# ---------------------------------------------------------
# -- print_raw32:
# --   Imprmir una palabra (32-bits) en la consola
# --  Se envia en BIG ENDIAN
# ---------------------------------------------------------
print_raw32:
    addi sp,sp,-16
    sw ra, 12(sp)
    sw s0, 8(sp)

    mv s0, a0

    #-- Las palabras están compuestas por 2 medias palabras
    #-- half1 half0

    #-- Imprimir half1
    mv a0, s0
    srl a0, s0, 16
    jal print_raw16

    #-- Imprimir half0
    mv a0, s0
    jal print_raw16

    lw s0, 8(sp)
    lw ra, 12(sp)
    addi sp,sp,16
    ret

Este es el programa principal:

.include "boot.h"
.include "regs.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init

    #-- Configurar el boton
    jal button_init15

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on


loop:
    #-- Imprimir varios numeros de 32bits

    li a0, 0xcafebaca
    jal print_raw32

    li a0, 0xbebecafe
    jal print_raw32

    li a0, 0x1234567
    jal print_raw32

    li a0, 0
    jal print_raw32

    #-- Esperar a que se apriete el pulsador
    jal button_press15

    #-- Cambiar estado del led
    jal led_toggle

    j loop

Y este es el resultado:

Ahora ya sí que sí tenemos mucho poder...

Más sobre relojes

Ahora que tengo el poder de inspeccionar los registros, voy a aprender más sobre los relojes de la pico2. Según leo en el datasheet, parece que cuando arranca el core se selecciona el reloj clk_ref

Hay un elemento que llaman el AON-Timmer (Always-on Timmer). Tengo que aprender más sobre él, porque me puede servir para comprobar los relojes. También es muy interesante el Low Power oscillater, que está dentro del chip. Creo que es el que usaré para hacer pruebas

  • Low power oscillator (LPOSC): Apartado 8.1.1.1. Página 511 del datashet

Tanto el AON-Timmer como el LPOSC están en lo que se llama el dominio Always-on power domain. Están siempre funcionando. Por eso es la parte con la que voy a comenzar

  • Always-On Timer: Apartado 12.10. Página 1194

Por defecto el AON Timer está governado por el LPOSC que tiene una frecuencia de 32Khz, que son 31.25 µs. Si esta señal la pasamos por un divisor entre 32, obtenemos aproximadamente 1ms. Esta se supone que es el periodo del temporizador: Se incrementa cada 1ms

Vamos a comenzar con el primer experimento: Lectura del temporizador. Tiene un valor de 64-bits, divididos en la parte alta y baja. De momento despreciamos la parte alta. Se accede a través del registro READ_TIME_LOWER (que inicialmente vale 0). Tiene un offset de 0x74

La dirección base es: 0x40100000 (POWMAN_BASE)

16-LPOSC-readfreq

He hecho el primer experimento en el que estoy leyendo los 32-bits de menor peso del temporizador... pero se lee todo 0s. Puede ser porque esté haciendo algo mal, o bien porque el temporizador esté deshabilitado o algo así....

He hecho otro intento, leyendo el registro antes de ninguna otra configuración... pero nada... se lee 0...

Voy a echar un vistazo a la parte de reset... no sea que este temporizador esté en modo RESET.. No encuentro nada... parece que el AON-Timer (a partir de ahora solo timer) no puede ser reseteado por software...

Voy a leer el capítulo entero del Timer. He visto que hay un registro llamado TIMER que incluye un bit para arrancar el temporizador. Y parece que por defecto está apagado

Voy a empezar por el principio. El oscilador que quiero es el LPOSC, cuya frecuencia es de 32Khz. Esta frecuencia se puede configurar. Por defecto vale 32. Se lee de este registro: LPOSC_FREQ_KHZ_INT. Voy a hacer un programa para leerlo, a ver qué tiene...

Ok. Lo puedo leer perfectamente: Obtengo 0x20, que es el valor esperado

En realidad la frecuencia del oscilador es de 32.768Khz. La parte entera está en el registro LPOSC_FREQ_KHZ_INT. La fraccional está en el registro LPOSC_FREQ_KHZ_FRAC. El valor leido es: 0xc49c, que es efectivamente lo que tiene que ser

Como recordatorio, vamos a recordar que efectivamente 0xc49c es 0.768. El numero en binario es este:

#-- consola ipython
In [21]: bin(0xc49c)
Out[21]: '0b1100010010011100'

Que como se corresponde con la parte fraccional en realidad es 0.1100010010011100

Desde python lo convertimos a decimal aplicando las exponenciales negativas de 2, sólo en los términos con 1

In [2]: 2**-1 + 2**-2 + 2**-6 + 2**-9 + 2**-12 + 2**-13 + 2**-14
Out[2]: 0.76800537109375

Si nos quedamos con los 3 primeros decimales vemos que efectivamente es el número 0.768

Este es el programa que lee ambos registros y los imprime en la consola hexadecimal:

.include "boot.h"
.include "regs.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init

    #-- Configurar el boton
    jal button_init15

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on


loop:
   
    li t0, LPOSC_FREQ_KHZ_INT
    lw a0, 0(t0)
    jal print_raw32

    li t0, LPOSC_FREQ_KHZ_FRAC
    lw a0, 0(t0)
    jal print_raw32

    #-- Esperar a que se apriete el pulsador
    jal button_press15

    #-- Cambiar estado del led
    jal led_toggle

    j loop

Este es el resultado de la ejecución:

17-print_hex4

La depuración usando la consola hexadecimal es un gran avance, y nos ayuda mucho... sin embargo si imprimimos mucha información es difícil decodificarla. No podemos, por ejemplo, hacer que cada número se muestre en una línea separada

Antes de continuar avanzando vamos a construir más rutinas de impresión, para poder mostrar números en una consola normal. Comenzamos primero con la función print_hex4, que imprime un nibble (4-bits) en hexadecimal. Un nibble se representa mediante un único carácter hexadecimal: 0-9 y de A-F. Es la primera función que vamos a construir

Esta es la funcion:

# ------------------------------------------------
# -- Print_hex4
# -- Imprimir un numero de 4 bits en hexadecimal  
# ------------------------------------------------
# -- ENTRADAS:
# -- a0: Numero a imprimir (nibble)
# ------------------------------------------------
print_hex4:
    addi sp, sp, -16
    sw ra, 12(sp)

    #-- Eliminamos todos los bits de mayor peso,
    #-- para aislar el nibble
    andi a0, a0, 0xF

    #-- Comprobar si el numero está entre 0 - 9
    li t0, 9
    bgt a0, t0, _range_a_f

    #-- El numero está en el rango [0,9]
    #-- Para imprimirlo hay que sumar '0'
    addi a0, a0, '0'

    #-- Imprimr el digito!
    j _print

_range_a_f:
    #-- El caracter esta en el rango a - f
    #-- Hay que sumarle 'A'
    addi a0, a0, ('A'-10)

_print:
    #-- Imprimir el digito
    jal putchar

    lw ra, 12(sp)
    addi sp, sp, 16
    ret

El programa principal imprime los nibbles desde el 0 hasta el 15, en una línea, separados por un espacio. Al llegar al último se realiza un salto de línea. Cada vez que apretemos el pulsador se imprime la línea de nuevo

# ------------------------------------------------
# -- Print_hex4
# -- Imprimir un numero de 4 bits en hexadecimal  
# ------------------------------------------------
# -- ENTRADAS:
# -- a0: Numero a imprimir (nibble)
# ------------------------------------------------
print_hex4:
    addi sp, sp, -16
    sw ra, 12(sp)

    #-- Eliminamos todos los bits de mayor peso,
    #-- para aislar el nibble
    andi a0, a0, 0xF

    #-- Comprobar si el numero está entre 0 - 9
    li t0, 9
    bgt a0, t0, _range_a_f

    #-- El numero está en el rango [0,9]
    #-- Para imprimirlo hay que sumar '0'
    addi a0, a0, '0'

    #-- Imprimr el digito!
    j _print

_range_a_f:
    #-- El caracter esta en el rango a - f
    #-- Hay que sumarle 'A'
    addi a0, a0, ('A'-10)

_print:
    #-- Imprimir el digito
    jal putchar

    lw ra, 12(sp)
    addi sp, sp, 16
    ret

Esta es la ventaja de utilizar la consola de texto: ahora podemos formatear la información como queramos. Este es el programa principal:

.include "boot.h"
.include "regs.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init

    #-- Configurar el boton
    jal button_init15

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on


    #-- Inicializar contador
    li s0, 0
loop:

    mv a0, s0
    jal print_hex4

    li a0, ' '
    jal putchar

    #-- Incrementar contador
    addi s0, s0, 1

    li t0, 16
    blt s0, t0, loop

    #-- Hemos llegado a F
    #-- Imprimir un salto de linea
    li a0, '\n'
    jal putchar 

    li a0, '\r'
    jal putchar

    #-- Cambiar estado del led
    jal led_toggle

    #-- Reiniciar contador
    li s0, 0

    #-- Esperar a que se apriete el pulsador
    jal button_press15

    j loop

Y este es el resultado:

18-print_hex8

Lo siguiente es imprimir un byte. Lo hacemos con la funcion print_hex8. Esta es la implementación:

# ------------------------------------------------
# -- Print_hex8
# -- Imprimir un numero de 8 bits en hexadecimal  
# ------------------------------------------------
# -- ENTRADAS:
# -- a0: Numero a imprimir (byte)
# ------------------------------------------------
print_hex8:
    addi sp, sp, -16
    sw ra, 12(sp)
    sw s0, 8(sp)

    #-- Guardar el numero original
    mv s0, a0

    #------ Imprimir el nibble1
    # -- Imprimir a0 >> 4
    srli a0,a0,4
    jal print_hex4
    
    #-- Imprimir el nibble0
    mv a0, s0
    jal print_hex4

    lw s0, 8(sp)
    lw ra, 12(sp)
    addi sp, sp, 16
    ret

Este es el programa principal, que imprime una línea de 16 bytes, separados por un espacio. Al apretar el botón se imprime la siguiente línea. Lo que se imprime es el valor de un contador que comienza en 0 y se incrementa de 1 en 1

.include "boot.h"
.include "regs.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init

    #-- Configurar el boton
    jal button_init15

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on

    #-- Inicializar contador
    li s0, 0

loop:

    #-- Imprimir el byte
    mv a0, s0
    jal print_hex8

    li a0, ' '
    jal putchar

    #-- Incrementar contador
    addi s0, s0, 1

    #-- Comprobar si se han impreso 16 bytes
    #-- correspondientes a una línea
    #-- Ailar los 4 bits de menor peso
    andi t1, s0, 0xF

    #-- Cuando sean cero, hemos terminado la linea
    #-- De lo contrario seguimos imprimiendo bytes
    bne t1, zero, loop

    #-- Hemos llegado a 16 bytes (linea completa)
    #-- Imprimir un salto de linea
    li a0, '\n'
    jal putchar 

    li a0, '\r'
    jal putchar

    #-- Esperar a que se apriete el pulsador
    jal button_press15

    #-- Cambiar estado del led
    jal led_toggle

    j loop

Este es el resultado (Tras apretar varias veces el pulsador):

Esto se parece mucho a un volcado de memoria... y eso me da la idea de hacerlo... podría ser interesante ver partes de la memoria de la pico 2...

19-dump16

NO me he podido resistir a hacer una función de volcado: dump16, que muestra los bytes que hay a partir de una dirección de memoria. Como parámetros se le pasa la dirección a volcar y la cantidad de bloques de 16 bytes a mostrar (4 palabras)

Esta es la función dump16:

# ---------------------------------------------------------------------------
# dump16: Volcado de memoria en bloques de 16 bytes (4 palabras)
# ---------------------------------------------------------------------------
# ENTRADAS:
#  -a0: Direccion donde empezar a volcar (digito de menor peso debe ser 0)
#  -a1: Numero de bloques de 16 bytes a volcar (a1 >= 1)  
#
# DEVUELVE:
#  -a0: La direccion siguiente al ultimo bloque mostrado
#    (esto permite encadenar llamadas a dump16)
# ---------------------------------------------------------------------------
dump16:
    addi sp,sp, -16
    sw ra, 12(sp)
    sw s0, 8(sp)
    sw s1, 4(sp)

    #-- Poner a 0 los 4 bits de menor peso de la dirección
    #-- para que sea múltiplo de 16
    li t0, 0xFFFFFFF0

    #-- Meter en s0 la direccion inicial
    and s0, a0, t0

    #-- Meter en s1 el numero de bloques a volcar
    mv s1, a1

_dump16_loop:
    #-- Imprimir el byte de la direccion actual
    lb a0, 0(s0)
    jal print_hex8

    li a0, ' '
    jal putchar

    #-- Incrementar direccion
    addi s0, s0, 1

    #-- Comprobar si se han impreso 16 bytes
    #-- correspondientes a una línea
    #-- Ailar los 4 bits de menor peso
    andi t1, s0, 0xF

    #-- Cuando sean cero, hemos terminado la linea
    #-- De lo contrario seguimos imprimiendo bytes
    bne t1, zero, _dump16_loop

    #-- Hemos llegado a 16 bytes (linea completa)
    #-- Imprimir un salto de linea
    li a0, '\n'
    jal putchar 

    li a0, '\r'
    jal putchar

    #-- Se ha completado un bloque. Queda uno menos
    addi s1, s1, -1

    #-- Si quedan todavia bloques por volcar, repetir
    bne s1, zero, _dump16_loop

    #-- No quedan bloques... hemos terminado
    #-- Devolver la siguiente direccion
    mv a0, s0

    lw s1, 4(sp)
    lw s0, 8(sp)
    lw ra, 12(sp)
    addi sp,sp, 16
    ret

Este es el programa principal. Realiza un volcado de 4 bloques. Cada vez que se recibe un carácter se vuelcan los siguientes 4 bloques

.include "boot.h"
.include "regs.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on

    #-- Direccion de volcado
    li s0, 0x10000000

loop:

    #-- Realizar volcado
    mv a0, s0  #-- Dirección
    li a1, 4   #-- Número de bloques
    jal dump16

    #-- s0: Siguiente direccion
    mv s0, a0

    #-- Esperar a que se reciba un caracter
    jal getchar

    #-- Cambiar estado del led
    jal led_toggle

    j loop

El volcado se hace de la dirección 0x10000000 que es donde comienza la memoria FLASH, y es por tanto ahí donde se guarda nuestro programa binario

Este es el resultado:

Este es el volcado en modo texto:

D3 DE FF FF 42 01 01 11 44 03 00 00 20 00 00 10 
00 10 00 20 FF 04 00 00 00 00 00 00 79 35 12 AB 
17 11 00 10 13 01 01 FE EF 00 C0 0D EF 00 C0 45 
EF 00 00 6C EF 00 00 49 37 04 00 10 13 05 04 00 
93 05 40 00 EF 00 90 07 13 04 05 00 EF 00 40 77 
EF 00 C0 49 6F F0 9F FE 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Vamos a hacer un experimento. Vamos a desensamblar el fichero, ver su código máquina y comprobar qué es lo que hay guardado en la flash

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/19-dump16$ ./dasm.sh

Se genera el fichero main.dasm:

main.elf:     file format elf32-littleriscv


Disassembly of section .text:

10000000 <image_def>:
10000000:	ffffded3          	.insn	4, 0xffffded3
10000004:	0142                	.insn	2, 0x0142
10000006:	1101                	.insn	2, 0x1101
10000008:	0344                	.insn	2, 0x0344
1000000a:	0000                	.insn	2, 0x
1000000c:	0020                	.insn	2, 0x0020
1000000e:	1000                	.insn	2, 0x1000
10000010:	1000                	.insn	2, 0x1000
10000012:	2000                	.insn	2, 0x2000
10000014:	04ff 0000 0000 0000 	.insn	10, 0x357900000000000004ff
1000001c:	3579 
1000001e:	ab12                	.insn	2, 0xab12

10000020 <_start>:
10000020:	10001117          	auipc	sp,0x10001
10000024:	fe010113          	addi	sp,sp,-32 # 20001000 <_stack_top>
10000028:	0dc000ef          	jal	10000104 <runtime_init>
1000002c:	45c000ef          	jal	10000488 <led_init>
10000030:	6c0000ef          	jal	100006f0 <uart_init>
10000034:	490000ef          	jal	100004c4 <led_on>
10000038:	10000437          	lui	s0,0x10000

1000003c <loop>:
1000003c:	00040513          	mv	a0,s0
10000040:	00400593          	li	a1,4
10000044:	079000ef          	jal	100008bc <dump16>
10000048:	00050413          	mv	s0,a0
1000004c:	774000ef          	jal	100007c0 <getchar>
10000050:	49c000ef          	jal	100004ec <led_toggle>
10000054:	fe9ff06f          	j	1000003c <loop>
	...

10000104 <runtime_init>:
10000104:	ff010113          	addi	sp,sp,-16
10000108:	00112623          	sw	ra,12(sp)
1000010c:	400102b7          	lui	t0,0x40010
[...]

Nos fijamos que lo primero que hay es la firma. Es lo que hemos puesto en el boot.h. Y efectivamente esos bytes los vemos en el volcado, en las dos primeras filas (la firma ocupa 8 palabras). Esto nos da la certeza de que el volcado está bien

Para hacer mejor la comparación pasamos el fichero main.elf a main.bin y lo hexaminamos con la herramienta hd:

PICO_TOOLCHAIN_PATH=/home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin
$PICO_TOOLCHAIN_PATH/riscv32-corev-elf-objcopy -O binary main.elf main.bin
hd main.bin
00000000  d3 de ff ff 42 01 01 11  44 03 00 00 20 00 00 10  |....B...D... ...|
00000010  00 10 00 20 ff 04 00 00  00 00 00 00 79 35 12 ab  |... ........y5..|
00000020  17 11 00 10 13 01 01 fe  ef 00 c0 0d ef 00 c0 45  |...............E|
00000030  ef 00 00 6c ef 00 00 49  37 04 00 10 13 05 04 00  |...l...I7.......|
00000040  93 05 40 00 ef 00 90 07  13 04 05 00 ef 00 40 77  |..@...........@w|
00000050  ef 00 c0 49 6f f0 9f fe  00 00 00 00 00 00 00 00  |...Io...........|
00000060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000100  00 00 00 00 13 01 01 ff  23 26 11 00 b7 02 01 40  |........#&.....@|

2025-05-9

20-print_hex16

Rutina para imprimir medias palabras en la consola: print_hex16

# ------------------------------------------------
# -- Print_hex16
# -- Imprimir un numero de 16 bits en hexadecimal  
# ------------------------------------------------
# -- ENTRADAS:
# -- a0: Numero a imprimir (media palabra)
# ------------------------------------------------
print_hex16:
    addi sp, sp, -16
    sw ra, 12(sp)
    sw s0, 8(sp)

    #-- Guardar el numero original
    mv s0, a0

    #---- Una palabra está formada por dos bytes: byte 1 | byte 0
    #------ Imprimir el byte1
    # -- Imprimir a0 >> 8
    srli a0,a0,8
    jal print_hex8
    
    #-- Imprimir el byte1
    mv a0, s0
    jal print_hex8

    lw s0, 8(sp)
    lw ra, 12(sp)
    addi sp, sp, 16
    ret

El programa principal imprime en la consola los valores 0xCAFE, 0xBACA, 0x1234

.include "boot.h"
.include "regs.h"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on

    #-- Direccion de volcado
    li s0, 0x10000000

loop:

    #--- Imprimir medias palabras
    li a0, 0xCAFE
    jal print_hex16

    li a0, ','
    jal putchar

    li a0, ' '
    jal putchar

    li a0, 0xBACA
    jal print_hex16

    li a0, ','
    jal putchar

    li a0, ' '
    jal putchar

    li a0, 0x1234
    jal print_hex16
   
    #--- Salto de linea
    li a0, '\n'
    jal putchar

    li a0, '\r'
    jal putchar

    #-- Esperar a que se reciba un caracter
    jal getchar

    #-- Cambiar estado del led
    jal led_toggle

    j loop

Este es el resultado:

obijuan@JANEL:~$ tio /dev/ttyUSB1
[07:40:37.522] tio v3.9
[07:40:37.522] Press ctrl-t q to quit
[07:40:37.523] Connected to /dev/ttyUSB1
CAFE, BACA, 1234
CAFE, BACA, 1234
CAFE, BACA, 1234
CAFE, BACA, 1234

21-print_hex32

Ya tengo la rutina para imprimir palabras en la consola. Esta es la que utilizaremos para ver el contenido de los registros de los perifericos de la pico2. Las depuraciones y pruebas serán ahora mucho más fáciles

No pongo el código porque es similar a los anteriores. De hecho, todas las funciones print_hexn tienen la misma estructura y se pueden generalizar en una nueva rutina a la que se le pase como parámetro el tamaño del elemento a imprimir (4,8,16,32). De momento no lo hago porque no hace falta optimizar: tenemos espacio de sobra. Prefiero avanzar en otros frentes. Pero se deja para el futuro

El resultado de la ejecución de este ejemplo es:

obijuan@JANEL:~$ tio /dev/ttyUSB1
[07:42:00.625] tio v3.9
[07:42:00.625] Press ctrl-t q to quit
[07:42:00.625] Connected to /dev/ttyUSB1
CAFEBACA, BEBECAFE, 12345678
CAFEBACA, BEBECAFE, 12345678
CAFEBACA, BEBECAFE, 12345678
CAFEBACA, BEBECAFE, 12345678
CAFEBACA, BEBECAFE, 12345678

22-vars-rodata

Es el momento de acceder a variables. Tiene su complicación, porque hay que tener claro de qué variables se trata...

Si hacemos un programa hola mundo que simplemente defina una palabra como variable, con un valor asignado (0xCAFEBACA) y la imprimimos... hay que tener cuidado. Debemos saber EN QUE SEGMENTO meter esa variable

Vamos a verlo con este ejemplo, donde definimos la variable v1 en la sección .data

.include "boot.h"
.include "regs.h"

.section .data
v1:  .word 0xBEBECAFE 

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on

loop:
    #--- Imprimir variable v1 (Palabra)
    la t0, v1
    lw a0, 0(t0)
    jal print_hex32

    #--- Salto de linea
    jal print_nl

    #-- Esperar a que se reciba un caracter
    jal getchar

    #-- Cambiar estado del led
    jal led_toggle

    j loop

El comportamiento esperado de este programa es que lea la variable y la imprima en la consola. Como es una variable, situadada en el segmento .data, se debe situar en la memoria RAM. PERO lo que nosotros estamos haciendo hasta ahora es grabar el programa en la memoria FLASH. Por tanto, en la RAM hay porquería. El código hace referencia a una variable en RAM, PERO que tiene un valor aleatorio

Si ensamblamos el programa, nos aparece este mensaje de ERROR:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/sandbox$ ./build.sh 
RP2350-E10: Adding absolute block to UF2 targeting 0x10ffff00
ERROR: ELF contains memory contents for uninitialized memory at 0x20000000
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/sandbox$

Los ficheros main.elf y main.uf2 se han generado, PERO el error está indicando que el código accede a variables de memoria NO INICIALIZADAS

Si cargamos el programa en la pico2, el bootloader se da cuenta del problema y NO graba nada en la flash. El dispositivo pen-drive pico2 no se desmonta. Sigue estando porque no ha podido grabar ni ejecutar nada

Como el fichero main.elf sí que está generado, lo podemos desensamblar para ver lo que está ocurriendo:

[...]
10000038 <loop>:
10000038:	10000297          	auipc	t0,0x10000
1000003c:	fc828293          	addi	t0,t0,-56 # 20000000 <SRAM_BASE>
10000040:	0002a503          	lw	a0,0(t0)
10000044:	091000ef          	jal	100008d4 <print_hex32>
10000048:	0bd000ef          	jal	10000904 <print_nl>
1000004c:	774000ef          	jal	100007c0 <getchar>
10000050:	49c000ef          	jal	100004ec <led_toggle>
10000054:	fe5ff06f          	j	10000038 <loop>
[...]

Vemos que efectivamente estamos leyendo la variable de la dirección 0x20000000, es decir, de la RAM. Pero el bootloader sólo ha guardado cosas en la flash

¿Cómo lo solucionamos?

Vamos paso a paso... Lo primero es determinar el tipo de variable. En este caso queremos usar una VARIABLE DE SOLO LECTURA. Porque no la estamos modificando. Por ello la definimos dentro de la sección .rodata. Así es como queda modificado el programa:

[...]
.section .rodata
v1:  .word 0xBEBECAFE 
[...]

Al linker hay que decirle que sitúe la sección .rodata en la flash:

  OUTPUT_ARCH(riscv)
    ENTRY(_start)

    MEMORY {
        FLASH (rx)  : ORIGIN = 0x10000000, LENGTH = 2M
        RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
    }

    SECTIONS {
        .text : {
            *(.text*)
        } > FLASH

        .rodata : {
            *(.rodata*)
        } >  FLASH

        .data : {
            *(.data*)
        } > RAM

        .bss : {
            *(.bss*)
        } > RAM

        .stack (NOLOAD) : {
            . = ALIGN(8);
            _stack_top = . + 4K; /* 4KB stack */
        } > RAM
    }

Este programa ya sí que se ensambla sin errores, y lo podemos cargar en la pico2

El resultado es que vemos el valor 0xBEBECAFE

obijuan@JANEL:~$ tio /dev/ttyUSB1
[08:54:29.526] tio v3.9
[08:54:29.526] Press ctrl-t q to quit
[08:54:29.527] Connected to /dev/ttyUSB1
BEBECAFE
BEBECAFE

23-vars-bss

Si lo que queremos es definir una variable para escribirla, hay que declararla en la sección .bss. PERO NO PUEDE TENER UN VALOR asignado. Es decir, son variables NO INICIALIZADAS. Por tanto es nuestro programa el que las debe inicializar

.include "boot.h"
.include "regs.h"

#---------------------------------------
#-- VARIABLES NO INICIALIZADAS
#---------------------------------------
    .section .bss
    .align 2  #-- Alinear a palabra
v1: .space 4


    .section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Configurar el LED
    jal led_init

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on

    #-- Inicializar variable v1
    la s0, v1
    li t1, 0xCAFEBACA
    sw t1, 0(s0)

loop:
    #--- Imprimir variable v1 (Palabra)
    lw a0, 0(s0)
    jal print_hex32

    #--- Salto de linea
    jal print_nl

    #-- Esperar a que se reciba un caracter
    jal getchar

    #-- Cambiar estado del led
    jal led_toggle

    j loop

Este código lo primero que hace es inicializar la variable v1 con el valor 0xCAFEBACA. Luego se lee esta variable y se imprime. Podemos realizar lecturas y escrituras sobre ella, sin ningún problema. PERO no puede contener un valor inicial (se lo tenemos que asignar nosotros en el programa)

El resultado de la ejecución es este:

obijuan@JANEL:~$ tio /dev/ttyUSB1
[09:30:09.834] tio v3.9
[09:30:09.834] Press ctrl-t q to quit
[09:30:09.834] Connected to /dev/ttyUSB1
CAFEBACA
CAFEBACA

24-vars-data

Entonces... ¿Cómo puedo meter variables de lectura/escritura con un valor inicial?. Estas variables se definen dentro del segmento .data. Pero hay que indicarle al linker que aunque estas variables residen en la RAM, sus valores iniciales están en la Flash. El linker se encarga de generar las etiquetas con las direcciones de comienzo de los datos en la flash, y las direcciones destino en la RAM

Nuestro programa, lo primero que tiene que hacer es copiar los valores iniciales desde la FLASH a la ram...

Voy a tomar como modelo este el script de linkado definido aquí: https://github.com/dougsummerville/Bare-Metal-Raspberry-Pi-Pico-2/tree/dot

Lo reproduzco aquí entero:

MEMORY
{
  FLASH (rx)  : ORIGIN = 0x10000000,  LENGTH = 4M 
  RAM   (rwx) : ORIGIN = 0x20000000,  LENGTH = 520K
}

/*Map sections to memory*/
SECTIONS
{
	.boot : {
		KEEP(_flash_init.o(.vector_table))
		KEEP(_flash_init.o(.start_block))
		KEEP(_flash_init.o(.init))
	} > FLASH

	TEXT : {
		*(.text*)
		*(.rodata*)
		__etext = .;
	} > FLASH

	DATA : {
		__data_lma = LOADADDR(DATA);
		__data = .;
		*(.data)
		*(.data*)
		__edata = .;
	} > RAM AT> FLASH

	BSS  : {
		__bss = .;
		*(.bss*)
	} > RAM
	__ebss = .;
	__end = .;

    __stack_top = ORIGIN(RAM) + LENGTH(RAM);
    
}

Le he preguntado a chatgpt que me explique la parte del segmento lógico DATA. Esta sección está situada EN LA FLASH (Comando AT> FLASH), PERO en tiempo de ejecución los valores se copian a la RAM. Por tanto las direcciones hacen referencia a la RAM

Lo voy a probar con un ejemplo sencillo... ok.. ya lo tengo funcionando... Ya, por fin, estoy entendiendo cómo funciona el linker script, y todo el tema de las secciones... Este es el programa simplemente imprime el valor de una variable inicializada situada en .data

.include "boot.h"
.include "regs.h"

.section .data
v1:  .word 0xCACABACA

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Copiar los valores iniciales
    #-- de las variables

    #-- Configurar el LED
    jal led_init

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on

    la s0, __data_flash_ini
    la s1, __data_ram_ini
    la s2, __data_ram_end

    mv a0, s0
    jal print_hex32
    jal print_nl

    mv a0, s1
    jal print_hex32
    jal print_nl

    mv a0, s2
    la a0, __data_ram_end
    jal print_hex32
    jal print_nl

    #-- Inicializar las variables
init_vars:
    #-- Comprobar si la direccion actual destino ha alcanzado el limite
    #-- Si es asi hemos terminado
    beq s1, s2, main

    #-- Leer palabra de la flash
    lw t3, 0(s0)

    #-- Copiarla en la RAM
    sw t3, 0(s1)

    #-- Incrementar las direcciones
    addi s0, s0, 4
    addi s1, s1, 4

    #-- Repetir
    j init_vars


main: 
    #--- Imprimir variable v1 (Palabra)
    la t0, v1
    lw a0, 0(t0)     #-- Leer variable
    jal print_hex32  #-- Imprimir

    #--- Salto de linea
    jal print_nl

    #-- Esperar a que se reciba un caracter
    jal getchar

    #-- Cambiar estado del led
    jal led_toggle

    j main

La variable v1 está guardada en flash, pero para utilizarla tiene que estar en RAM. Las direcciones correspondientes se obtienen del linker script. El programa lo primero que tiene que hacer es copiar los valores de las inicializaciones de la flash a la ram... y luego ya se ejecuta el programa principal que hace el bucle de siempre: Imprime la variable v1 y espera a que se pulse una tecla (para repetir el proceso)

Para depurar primero se imprime la dirección inicial de la flash donde están las variables, luego la dirección inicial de ram y por último la dirección final de la ram. Tras esto se imprime la veriable v1. Esto es lo que se obtiene:

obijuan@JANEL:~$ tio /dev/ttyUSB1
[10:53:38.652] tio v3.9
[10:53:38.652] Press ctrl-t q to quit
[10:53:38.653] Connected to /dev/ttyUSB1
100009A8
20000000
20000004
CACABACA
CACABACA
CACABACA

Este es el linker script, que en los ejemplos sucesivos lo iremos mejorando y refinando

OUTPUT_ARCH(riscv)
    ENTRY(_start)

    MEMORY {
        FLASH (rx)  : ORIGIN = 0x10000000, LENGTH = 2M
        RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
    }

    SECTIONS {
        .text : {
            *(.text*)
        } > FLASH

        .rodata : {
            *(.rodata*)
        } >  FLASH

        DATA : {
		    __data_flash_ini = LOADADDR(DATA);
		    __data_ram_ini = .;
		    *(.data)
		    __data_ram_end = .;
	    } > RAM AT> FLASH


        .bss : {
            *(.bss*)
        } > RAM

        .stack (NOLOAD) : {
            . = ALIGN(8);
            _stack_top = . + 4K; /* 4KB stack */
        } > RAM
    }

25-print

Y ya por fin vamos a construir la rutina de print, para imprimir cadenas. Esto nos permite incluir mensajes de depuración mucho más elaborados, así como mejorar la calidad de los programas

Esta es la rutina de print:

# ---------------------------------------------
# -- Print: Imprimir una cadena en la consola 
# ---------------------------------------------
# -- ENTRADAS:
# --   a0: Direccion de la cadena a imprimir
# ---------------------------------------------
print:
    addi sp,sp, -16
    sw ra, 12(sp)
    sw s0, 8(sp)

    #-- PUntero a la cadena
    mv s0, a0

_print_loop:
    #-- Leer caracter actual
    lb a0, 0(s0)

    #-- Si es 0, hemos terminado
    beq a0, zero, _print_end

    #-- Imprimir el caracter
    jal putchar

    #-- Apuntar al siguiente byte
    addi s0, s0, 1

    #-- Repetir
    j _print_loop


_print_end:
    lw s0, 8(sp)
    lw ra, 12(sp)
    addi sp,sp, 16
    ret

Este es el programa principal, que simplemente escribe "Hola Mundo!" en el terminal

.include "boot.h"
.include "regs.h"

.section .data
msg:   .string "Hola Mundo!!!!\n"

.section .text

# -- Punto de entrada
.global _start
_start:

    #-- Inicializar la pila
    la sp, _stack_top

    #-- Inicializar el sistema
    jal	runtime_init

    #-- Inicializar las variables
    jal runtime_init_vars

    #-- Configurar el LED
    jal led_init

    #-- Inicializar la UART
    #-- Velocidad: 115200 (con runtime actual)
    jal uart_init 

    #-- Encender el LED
    jal led_on

main: 
    #-- Imprimir cadena!
    la a0, msg
    jal print

    #-- Esperar a que se reciba un caracter
    jal getchar

    #-- Cambiar estado del led
    jal led_toggle

    j main

Este es el resultado:

obijuan@JANEL:~$ tio /dev/ttyUSB1 --map ICRNL,INLCRNL
[11:41:17.426] tio v3.9
[11:41:17.426] Press ctrl-t q to quit
[11:41:17.426] Connected to /dev/ttyUSB1
Hola Mundo!!!!
Hola Mundo!!!!
Hola Mundo!!!!
Hola Mundo!!!!
Hola Mundo!!!!
Hola Mundo!!!!

El terminal tio hay que arrancarlo con este comando: tio /dev/ttyUSB1 --map ICRNL,INLCRNL para que el caracter \n produzca un salto de linea y un retorno de carro, y se comporte como los terminales típicos

Ejemplo 26: Monitor-V 0.1

Voy a empezar a hacer un programa monitor que me permita dialogar con la pico2 y mostrar información sobre ella: volcar zonas de memoria, ver los contenidos de los registros de periféricos, leer/escribir en zonas de memoria, configurar leds, relojes, etc... Así puedo meter todo el conocimiento en esa aplicación

Ya que tengo la rutina de print, he aprovechado para implementar la rutina de CLS usando la secuencia ANSI

También he documentado el linker script, y empiezo a entender su funcionamiento. Así es como ha quedado:

OUTPUT_ARCH(riscv)
    ENTRY(_start)

    /*  Direcciones de memoria */
    MEMORY {
        FLASH (rx)  : ORIGIN = 0x10000000, LENGTH = 2M
        RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
    }


    /*  Secciones de salida  */
    SECTIONS {

        /* Codigo. Se situa en la memoria Flash */
        /* Contiene codigo y valores de variables de solo lectura */
        TEXT : {

            /* Etiqueta que contiene la direccion inicial de la flash */
            __flash_ini = .;

            /* Colocar primero las secciones .text de todos los ficheros */
            *(.text*)

            /* Obtener la direccion del comienzo de las variables de
               solo lectura                                          */
            __flash_ro_vars = .;

            /* Tras el codigo poner los valores de las           */
            /* variables de solo lectura de todos los ficheros   */
            *(.rodata*)

            /* Obtener la direccion del final del programa */
            __flash_end = .;  
        } > FLASH

        /* Datos inicializados
           Los valores de las variables estan guardados en la flash
           Antes de empezar el programa se deben copiar a la ram
           El linker calcula las direccion de memoria RAM
        */
        DATA : {

            /* Obtener la direccion de la flash donde comienzan los datos 
               que deben ser copiados en la ram  */
		    __data_flash_ini = LOADADDR(DATA);

            /* Obtener la direccion de la ram donde comienzan los datos */
		    __data_ram_ini = .;

            /* Situar las secciones .data de todos los ficheros */
		    *(.data)

            /* Obtener la direccion de ram donde finalizan los datos */
		    __data_ram_end = .;

	    } > RAM AT> FLASH
        /* La seccion DATA está en la memoria RAM, PERO su origen es la flash */


        /* Seccion con las VARIABLES NO INICIALIZADAS */
        BSS : {

            /* Obtener la direccion de comienzo de las variables no inicializadas  */
            __var_no_init = .;

            /* Colocar las secciones .bss de todos los ficheros */
            *(.bss*)

            /* Obtener la direccion del final de la ram */
            __data_end = .;
        } > RAM

        /* Seccion para la pila. Está solo en la RAM, y no tiene que ser 
           inicializada con ningun valor. Por eso tiene NOLOAD            */
        STACK (NOLOAD) : {

            /* Debe comenzar en una direccion alineada a doble palabra
               Es decir, que su direccion sea multiplo de 8             */
            . = ALIGN(8);

            /* Obtener la direccion de la cima de la pila */
            _stack_top = . + 4K; /* 4KB stack */
        } > RAM
    }

He definido varias símbolos que se pueden leer desde los programas en ensamblador para conocer ciertas direcciones de memoria. Estos símbolos los uso para imprimir las direcciones de memoria

Esta es la primera versión del programa monitor-v (0.1). Esto es lo que se obtiene al ejecutarlo:

2025-05-11

Ejemplo 27: Monitor-V 0.2

En esta version del monitor-v se incluyen colores. Se han implementando mediante secuencias ANSI. También he aprovechado para crear macros y hacer que la programación sea más concisa, sobre todo a la hora de llamar a las funciones de impresión. Otra de las mejoras ha sido crear una sección nueva: .boot donde está almacenada la firma del programa. El linker script la sitúa en la primera posición de memoria

Este es el resultado:

Se ejecuta en el tio, con este comando:

https://github.com/Obijuan/Learn-raspberry-pico2/blob/main/wiki/Log/Images/2025-05-11-img01-tio-monitor-V-0.2.png

El monitor lo primero que hace es un CLS, por eso no se ven los mensajes del tio

Ejemplo 28: Monitor-V 0.3

En esta nueva versión se ha incluido un menu de opciones. Esto será muy útil más adelante para hacer pruebas fácilmente

Este es el aspecto que tiene nada más arrancar. Al apretar la tecla 1 se muestran las direcciones relevantes

Este es el volcado de la flash:

Y este el volcado de las variables de solo lectura

Ejemplo 29: Monitor-V 0.4-LPOSC

Voy a retomar donde lo había dejado en el ejemplo 16-LPOSC-readfreq. He añadido una nueva opción en el monitor para leer los registros de la frecuencia del oscilador

Voy a seguir investigando sobre el temporizador AON_TIMER. El registro principal para configurar es el POWMAN_TIMER (offset 0x88). Con el Monitor-V-0.4 lo estoy leyendo, y se lee todo 0s

Estoy configurando el timer, pero no consigo escribir ningún valor en los bits... Escribo, y luego leo, pero no ha cambiado nada. No puedo tampoco arrancar el timer... hay algo que se me escapa... tal vez sea por algo de la seguridad... o vete tu a saber... Tengo que leer el datasheet con más detenimiento...

ok... Ya lo he encontrado! Para escribir en los registros hay que poner el valor 0x5afe en los 16 bits de mayor peso. Si ahora se arranca el Timer poniendo a 1 el BIT1, podemos leer su valor. Ya funciona!!. Al pararlo el valor actual queda "congelado". Al arrancarlo empieza de nuevo desde 0... Con esto ya puedo rutinas de temporización más precisas

Así es como queda la versión 0.4 de monitor. Primero leemos el temporizador. Está a 0. Activamos el temporizador y lo volvemos a leer

Ahora lo paramos:

Al estar parado se lee siempre el mismo valor

2025-05-12

Ejemplo 30: Delay_ms()

Es el momento de hacer una rutina de delay con más exactitud. Vamos a usar el AON-Timer Ya tengo la rutina funcionando

Esta es la función delay_ms():

.include "regs.h"

.section .text

#---------------------------------------
#-- delay_ms: Funcion de retardo, en ms
#---------------------------------------
#-- ENTRADAS:
#--  a0: ms a esperar (>=1)
#---------------------------------------
delay_ms:

    #-- Encender el AON-Timer
    li t0, POWMAN_TIMER_SET
    li t1, RUN
    sw t1, 0(t0)

    #-- Esperar hasta que transcurra el tiempo
    #-- establecido

_timer_wait:
    
    #-- Leer temporizador (en ms)
    li t0, READ_TIME_LOWER
    lw t1, 0(t0) 

    #-- Ha transcurrido el tiempo?
    bltu t1, a0, _timer_wait

    #-- Ha pasado el tiempo indicado

    #-- Apagar temporizador
    li t0, POWMAN_TIMER_CLR
    li t1, RUN
    sw t1, 0(t0)

    ret

Para utilizarla de manera más fácil se ha definido esta MACRO:

#---------------
#-- MACROS
#---------------

.macro DELAY_MS ms
    li a0, \ms
    jal delay_ms
.endm

Este es el programa principal que hace parpadear un LED

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "delay.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top

main:
    #-- Configurar perifericos
    jal led_init

    #-- Encender el LED
    jal led_on

loop: 

    #-- Esperar
    DELAY_MS 200

    #-- Cambiar de estado el LED
    jal led_toggle

    j loop

Este es el programa mínimo. No se inicializa el runtime. El timer está funcionando con el oscilador interno de bajo consumo. El código no tengo claro con qué oscilador está funcionando...

Modo privilegiado

Es el momento de profundizar en la programación privilegiada del Risc-V. Hasta ahora lo hemos estado haciendo en un RV32I. La extensión que soporta el modo privilegiado es zicsr

Voy a leer el datasheet: "3.8. Hazard3 Processor. Página 233"

En esta web: https://five-embeddev.com/ hay mucha información, y muy buena, sobre RISC-V

Este es el enlace de la arquitectura privilegiada del RISC-V:

https://five-embeddev.com/riscv-priv-isa-manual/Priv-v1.12/priv-intro.html#introduction

El modo privilegiado se llama M-Mode y se condifica como 11. La extension del modo privilegiado es Zicsr. La Z significa que es una extensión estándar. Y la i que se trata de instrucciones

El risc-v de la pico2 (hazard) tiene 2 modos: M y U (Machine, User)

La instrucción que usamos para leer los CSR es csrrw. Hay que seleccionar un registro para hacer pruebas. Voy a probar primero con el MVENDORID, con offset 0xF11 Según la documentación el valor de este registro debe ser: 100_1001_0011 = 0x493

Parto del programa que hace parpadear un LED (ejemplo anterior). Si leemos el registro mvendorid, peta:

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "delay.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top

main:
    #-- Configurar perifericos
    jal led_init

    #-- Encender el LED
    jal led_on

    #-- Leer el registro MVENDORID
    csrrw a0, 0xF11, zero
    


loop: 

    #-- Esperar
    DELAY_MS 200

    #-- Cambiar de estado el LED
    jal led_toggle

    j loop

Sabemos que peta porque el led NO parpadea. Si se comenta la instrucción csrrw entonces sí funciona... ummmmm. Voy a probar a leer otros registros...

ok... el registro mstatus (0x300) sí que se puede leer. Este es el programa que usamos:

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    #-- Encender el LED
    jal led_on

    CLS
    PRINT "READY\n"

    PRINT "mstatus: "

    #-- Leer el registro MVENDORID
    csrrw a0, 0x300, zero
    jal print_0x_hex32
    NL    


loop: 

    #-- Esperar
    DELAY_MS 200

    #-- Cambiar de estado el LED
    jal led_toggle

    j loop

Este es el resultado en el terminal:

READY
mstatus: 0x00001800

Vamos a analizar su lectura:

0    0    0    0    1    8    0    0  
0000 0000 0000 0000 0001 1000 0000 0000  

  Res      TW  Res  MPRV  Res   MPP  Res  MPIE Res  MIE   Res  
0000000000__0___000____0___0000__11___000___0___000___0____000  

Ejemplo 31-csr

En este ejemplo se imprimen y procesan los registros mstatus, misa, print_mie y print_mtvec

Este es el programa principal:

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    #-- Encender el LED
    jal led_on

    #-- Imprimir los registros privilegiados
    CLS
    jal print_mstatus
    NL
    jal print_misa
    NL
    jal print_mie
    NL
    jal print_mtvec
    NL

    #-- Registros CSR a tener en cuenta
    #-- MCOUNTINHIBIT 
    #-- MEPC (Machine exception program counter.)
    #-- MCAUSE Register
    #-- MIP (Machine interrupt pending)
    #-- MCYCLE 
    #-- MCYCLEH Register
    #-- MVENDORID (no me deja leerlo...)
    #-- MARCHID (Architecture ID)  
    #-- MIMPID (Implementation ID. On RP2350 this reads as 0x86fc4e3f,
      #-- which is release v1.0-rc1 of Hazard3.
    #-- MHARTID 


loop: 

    #-- Esperar
    DELAY_MS 200

    #-- Cambiar de estado el LED
    jal led_toggle

    j loop

Este es el resultado:

Ejemplo 32-trap (No resuelto)

Es el momento de empezar con las interrupciones. Lo primero es documentarse. Vamos a comenzar con las interrupciones software. Lo primero es activar el bit msie del registro mie a 1: mie.msie = 1

OK. Esto lo tenemos:

MIE: 0x00000000  (Interrupt Enable)
  MEIE:  0 (External interrupt enable)
  MTIE:  0 (Timer interrupt enable)
  MSIE:  0 (Software interrupt enable)
MIE: 0x00000008  (Interrupt Enable)
  MEIE:  0 (External interrupt enable)
  MTIE:  0 (Timer interrupt enable)
  MSIE:  1 (Software interrupt enable)

Tengo un programa cambia el vector de interrupción y habilita las interrupciones... PERO no funciona

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    CLS

    #--------- ESTABLECER VECTOR DE INTERRUPCION
    #------- MTVEC
    #-- Antes
    jal print_mtvec

    la t0, trap_handle
    csrrw zero, mtvec, t0

    #-- Despues
    jal print_mtvec

    #--------- ACTIVAR LAS INTERRUPCIONES
    #------- MIE.SIE=1
    #-- Antes
    jal print_mie

    #-- mie.msie = 1
    li t0, BIT3  #-- 0x08
    csrrw zero, mie, t0

    #-- Despues
    jal print_mie
    NL

    #------- MSTATUS.MIE=1
    #-- Antes
    jal print_mstatus

    li t0, 0x1808
    csrrw zero, mstatus, t0

    #-- Despues
    jal print_mstatus

    #-------- MIP.MSIP=1
    #-- Antes
    jal print_mip

    li t0, BIT3
    csrrw zero, mip, t0

    jal print_mip

    #--- GENERAR INTERRUPCION!!
    li t0, 1
    lw t1, 0(t0)  #-- TRAP! Lectura de una direccion NO alineada
    ecall

loop: 

    #-- Esperar
    DELAY_MS 200

    #-- Cambiar de estado el LED
    jal led_toggle

    j loop
    
trap_handle:
    #-- Encender el LED
    li t0, GPIO_OUT_SET
    li t1, BIT25    #-- Activar el bit 25
    sw t1, 0(t0)
    j .

La idea es que el LED parpadea cuando NO hay interrupciones. Esto funciona. Activamos los bits para habilitar las interrupciones y cambiamos el vector de interrupción... Esto se hace. Salvo en el caso de MIP, que en principio lo activa el propio riscv al producirse la interrupción, pero que por si acaso lo activo yo

Esto es lo que se obteiene:

MTVEC: 0x000075A0  (Trap handler base address)
  BASE:  0x000075A0 (Trap vector address)
  MODE:  00 (Trap mode)
MTVEC: 0x100000A4  (Trap handler base address)
  BASE:  0x100000A4 (Trap vector address)
  MODE:  00 (Trap mode)
MIE: 0x00000000  (Interrupt Enable)
  MEIE:  0 (External interrupt enable)
  MTIE:  0 (Timer interrupt enable)
  MSIE:  0 (Software interrupt enable)
MIE: 0x00000008  (Interrupt Enable)
  MEIE:  0 (External interrupt enable)
  MTIE:  0 (Timer interrupt enable)
  MSIE:  1 (Software interrupt enable)

MSTATUS: 0x00001800
  TW:   0  (Timeout Wait)
  MPRV: 0  (Modify PRiVilegde)
  MPP:  11 (Previous Privilegde level)
  MPIE: 0  (Previous Interrupt Enable)
  MIE:  0  (Interrupt enable)
MSTATUS: 0x00001808
  TW:   0  (Timeout Wait)
  MPRV: 0  (Modify PRiVilegde)
  MPP:  11 (Previous Privilegde level)
  MPIE: 0  (Previous Interrupt Enable)
  MIE:  1  (Interrupt enable)
MEIP: 0x00000000  (External interrupt pending)
  MEIE:  0 (External interrupt enable)
  MTIP:  0 (Timer interrupt pending)
  MSIP:  0 (Software interrupt pending)
MEIP: 0x00000000  (External interrupt pending)
  MEIE:  0 (External interrupt enable)
  MTIP:  0 (Timer interrupt pending)
  MSIP:  0 (Software interrupt pending)

Provocamos una interrupción accediendo a una dirección NO alineada... La rutina de captura de la interrupción debería encender el LED, pero no es así... NO tengo claro qué es lo que está pasando. NO ignora la interrupción. La hace (porque el LED deja de parpadear), pero ni idea de qué está ejecutando... Desensamblado vemos que la dirección que se mete como vector de interrupción es correcto

[...]
100000a4 <trap_handle>:
100000a4:	d00002b7          	lui	t0,0xd0000
100000a8:	01828293          	addi	t0,t0,24 # d0000018 <GPIO_OUT_SET>
100000ac:	02000337          	lui	t1,0x2000
100000b0:	0062a023          	sw	t1,0(t0)
100000b4:	0000006f          	j	100000b4 <trap_handle+0x10>
[...]

La dirección guardada en el vector de interrupción es correcta... ¿Qué está pasando? ¿Cómo lo depuro?

joder... no encuentro nada... sólo se me ocurre intentar activar alguna interrupción... a ver qué pasa....

Voy a probar a pasar al modo usuario, y ejecutar un ecall desde ahí... No consigo entrar en el modo usuario... la instrucción mret tampoco parece funcionar... joder

Lo único que se me ocurre es poner en marcha un programa en C con interrupciones, y desensamblarlo, hasta dar con la tecla...

Sigo sin verlo claro. He analizado el programa uart-advanced.c que usa interrupciones. En el desensamblado he visto que se utiliza una tabla de vectores de interrupcion, PERO SE SITUA EN MEMORIA RAM! Yo tengo el programa en la Flash... no debería influir... pero ... quien sabe... habrá que probarlo...

En el runtime init se llama a esta una función que activa las interrupciones. Es la única que configura el registro mie

1000146a <runtime_init_per_core_h3_irq_registers>:
1000146a:	450d                	li	a0,3
1000146c:	be251073          	csrw	0xbe2,a0
10001470:	157d                	addi	a0,a0,-1
10001472:	fe055de3          	bgez	a0,1000146c <runtime_init_per_core_h3_irq_registers+0x2>
10001476:	6505                	lui	a0,0x1
10001478:	80050513          	addi	a0,a0,-2048 # 800 <HeapSize>
1000147c:	30451073          	csrw	mie,a0
10001480:	30046073          	csrsi	mstatus,8
10001484:	34001073          	csrw	mscratch,zero
10001488:	8082                	ret

2025-05-13

32-Traps (No resuelto)

No tengo claro como seguir. Así que voy a decantarme por buscar un ejemplo en C de interrupciones, desensamblarlo y estudiarlo.

Voy a estudiar este enlace: https://github.com/Chalandi/Blinky_Pico2_dual_core_nosdk/tree/master

He clonado el proyecto. He modificado el makefile añadiendo esta línea:

TOOLCHAIN=/home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf

Compilo con esta línea

obijuan@JANEL:~/Develop/Blinky_Pico2_dual_core_nosdk/Build$ ./Rebuild.sh RISC-V

Los ejecutables están el directorio Output:

obijuan@JANEL:~/Develop/Blinky_Pico2_dual_core_nosdk/Output$ ls
riscv_baremetal_pico2_dual_core_nosdk.bin  riscv_baremetal_pico2_dual_core_nosdk.hex      riscv_baremetal_pico2_dual_core_nosdk.sym
riscv_baremetal_pico2_dual_core_nosdk.dis  riscv_baremetal_pico2_dual_core_nosdk.map      riscv_baremetal_pico2_dual_core_nosdk.uf2
riscv_baremetal_pico2_dual_core_nosdk.elf  riscv_baremetal_pico2_dual_core_nosdk.readelf  riscv_obj
obijuan@JANEL:~/Develop/Blinky_Pico2_dual_core_nosdk/Output$ 

Lo primero es cargarlo en la pico, ver qué ocurre

OK... he conseguido cargarlo y que funcione. El LED parpadea. Y se supone que mediante interrupciones... PERO por alguna razón que desconozco, no siempre arranca. Le doy al reset y NO arranca siempre... esto es complicado de gestionar.... Me da poca confianza la verdad

Este programa funciona. Usa los dos cores. El core1 es el que ejecuta las interrupciones. Antes de empezar a simplificarlo y cambiar cosas, voy a estudiar su código ensamblador

#include "Platform_Types.h"
#include "Cpu.h"
#include "Gpio.h"
#include "SysTickTimer.h"

void main_Core0(void);
void main_Core1(void);
void BlockingDelay(uint32 delay);

int main(void)
{
  main_Core0();

  /* Synchronize with core 1 */
  RP2350_MulticoreSync((uint32_t)HW_PER_SIO->CPUID.reg);

  /* endless loop on the core 0 */
  for(;;);

  /* never reached */
  return(0);
}

void main_Core0(void)
{

  /* Output disable on pin 25 */
  LED_GREEN_CFG();


  /* Start the Core 1 and turn on the led to be sure that we passed successfully the core 1 initiaization */
  if(TRUE == RP2350_StartCore1())
  {
    LED_GREEN_ON();
  }
  else
  {
    /* Loop forever in case of error */
    while(1)
    {
      __asm volatile("NOP");
    }
  }

}


volatile uint64_t* pMTIMECMP = (volatile uint64_t*)&(HW_PER_SIO->MTIMECMP.reg);
volatile uint64_t* pMTIME    = (volatile uint64_t*)&(HW_PER_SIO->MTIME.reg);

void main_Core1(void)
{

  /* Note: Core 1 is started with interrupt enabled by the BootRom */
  /* Clear the stiky bits of the FIFO_ST on core 1 */
  HW_PER_SIO->FIFO_ST.reg = 0xFFu;

  /* Synchronize with core 0 */
  RP2350_MulticoreSync((uint32_t)HW_PER_SIO->CPUID.reg);

  /* configure the machine timer for 1Hz interrupt window */
  #include "riscv.h"

  /* enable machine timer interrupt */
  riscv_set_csr(RVCSR_MIE_OFFSET, 0x80ul);

  /* enable global interrupt */
  riscv_set_csr(RVCSR_MSTATUS_OFFSET, 0x08ul);

  /* configure machine timer to use 150 MHz */
  HW_PER_SIO->MTIME_CTRL.bit.FULLSPEED = 1;

  /* set next timeout (machine timer is enabled by default) */
  *pMTIMECMP = *pMTIME + 150000000ul; //1s

  while(1)
  {
    __asm("nop");
  }
}


__attribute__((interrupt)) void Isr_MachineTimerInterrupt(void);
  
void Isr_MachineTimerInterrupt(void)
{
  *pMTIMECMP = *pMTIME + 150000000ul;
  
  LED_GREEN_TOGGLE();
}

Voy a ir poco a poco entendiendo el código ensamblador. Lo he compilado sin la extensión C, y ha funcionado. Voy a eliminar algunas extensiones más, para no encontrar instrucciones "raras"

Lo primero que vemos es que la tabla de vectores de interrupción está en la primera dirección de la FLASH. El primero está en la dirección 0, el siguiente en la 0xC, luego en la 0x1C y por último 0x2C. El primer vector es de las excepciones. El siguiente no está definido (yo creo que es el de las interrupciones software). Luego el del Timer y por último el de las interrupciones externas. 4 en total. Cada parte de la tabla ocupa 16 bytes

10000000 <_VectoredInterruptVectorTable>:
10000000:	4a10006f          	j	10000ca0 <AllExceptionsHandler>
10000004:	48d0006f          	j	10000c90 <Isr_UndefinedHandler>
10000008:	4890006f          	j	10000c90 <Isr_UndefinedHandler>
1000000c:	4a10006f          	j	10000cac <UndefinedHandler>
10000010:	4810006f          	j	10000c90 <Isr_UndefinedHandler>
10000014:	47d0006f          	j	10000c90 <Isr_UndefinedHandler>
10000018:	4790006f          	j	10000c90 <Isr_UndefinedHandler>
1000001c:	1b00006f          	j	100001cc <Isr_MachineTimerInterrupt>
10000020:	4710006f          	j	10000c90 <Isr_UndefinedHandler>
10000024:	46d0006f          	j	10000c90 <Isr_UndefinedHandler>
10000028:	4690006f          	j	10000c90 <Isr_UndefinedHandler>
1000002c:	4890006f          	j	10000cb4 <Isr_MachineExternalInterrupt>

Lo que viene a continuación es muy interesante. Son los vectores de interrupción del core0 y del core1! Esto no tengo ni idea de lo que es... Hay almacenadas 2 direcciones. Una en la flash y otra en la ram...

10000030 <__INTVECT_Core0>:
10000030:	0810 2000 0c30 1000                         ... 0...

10000038 <__INTVECT_Core1>:
10000038:	1010 2000 0c60 1000

Ahora analizamos la "Firma". Las 3 primeras palabras son el preámbulo. El punto de entrada está en la dirección 0x1000_0c30 y la pila en 0x2000_0810. Justo esos mismos valores son los que están almacenados en el vector de interrupción del Core0. Interesante. Aplicando esta idea deducimos el el punto de entrada para el core1 es 0x1000_0c60 y su pila en 0x2000_1010

10000040 <image_definition_Block>:
10000040:	ded3 ffff 0142 1101 0344 0000 0c30 1000     ....B...D...0...
10000050:	0810 2000 04ff 0000 0000 0000 3579 ab12     ... ........y5..

El punto de entrada del core0 (c0) es este:

10000c30 <_start_c0>:
10000c30:	ff700293          	li	t0,-9
10000c34:	3002b073          	csrc	mstatus,t0
10000c38:	30401073          	csrw	mie,zero
10000c3c:	10000117          	auipc	sp,0x10000
10000c40:	bd410113          	addi	sp,sp,-1068 # 20000810 <__CORE0_STACK_TOP>
10000c44:	fffff297          	auipc	t0,0xfffff
10000c48:	3bc28293          	addi	t0,t0,956 # 10000000 <_VectoredInterruptVectorTable>
10000c4c:	00128293          	addi	t0,t0,1
10000c50:	30529073          	csrw	mtvec,t0
10000c54:	cc5ff0ef          	jal	10000918 <Startup_Init>
10000c58:	00000013          	nop
10000c5c:	00000013          	nop

Las primeras instrucciones son:

li t0, 0xfffffff7
csrc mstatus, t0  #-- Poner a 0 todos los bits, excepto MIE, que se deja como estaba
csrw mie,zero     #-- Parece que se inhiben todas las interrupciones
li sp, 0x20000810 #-- Inicializar la pila del core 0
li t0, 0x10000001 
csrw mtvec, t0    #-- Configurar el vector de interrupciones del core0
jal startup_init  #-- Seguir inicializando cosas

La inicialización del core1 es similar. LA TABLA DE VECTORES DE INTERRUPCIÓN ES LA MISMA!

Después el core0 llama a startup_init (y NUNCA vuelve de ahí, porque se llama a main y se queda en el bucle principal) Startup init realiza todas las inicializaciones necesarias y llama a main

10000918 <Startup_Init>:
10000918:	ff010113          	addi	sp,sp,-16
1000091c:	00112623          	sw	ra,12(sp)
10000920:	2f4000ef          	jal	10000c14 <Startup_InitCore>
10000924:	2d4000ef          	jal	10000bf8 <Startup_InitSystemClock>
10000928:	01c000ef          	jal	10000944 <Startup_InitRam>
1000092c:	230000ef          	jal	10000b5c <Startup_InitCtors>
10000930:	294000ef          	jal	10000bc4 <Startup_RunApplication>
10000934:	00000013          	nop
10000938:	00c12083          	lw	ra,12(sp)
1000093c:	01010113          	addi	sp,sp,16
10000940:	00008067          	ret

Voy a echar un vistazo a estas inicializaciones

<Startup_InitCore> --> Llama a RP2350_InitCore y retorna

  • RP2350_InitCore: Hace muchas cosas... pero NADA relacionado con los registros csr
  • <Startup_InitSystemClock> --> Llama a RP2350_ClockInit --> Tampoco hace nada con los registros csr
  • <Startup_InitRam>: Copia cosas (es larga...)
  • <Startup_InitCtors> --> Nada de csr..
  • <Startup_RunApplication> --> Llama a main

En el core1, que es donde se usan las interrupciones, veo cosas intereantes:

10000138 <main_Core1>:
[...]
1000015c:	    08000793          	li	a5,128
10000160:	    3047a073          	csrs	mie,a5
10000164:	    30046073          	csrsi	mstatus,8
[...]

Esto es lo que hace el codigo:

li a5,0x80
csrs mie,a5      #--- Set MTIE: Timer interrupt enable
csrsi mstatus,8  #--- Activar bit MPIE: Previous interrupt enable

Y esta es la rutina de atencion a la interrupción

100001cc <Isr_MachineTimerInterrupt>:
100001cc:	fe010113          	addi	sp,sp,-32
100001d0:	00a12e23          	sw	a0,28(sp)
100001d4:	00b12c23          	sw	a1,24(sp)
100001d8:	00c12a23          	sw	a2,20(sp)
100001dc:	00d12823          	sw	a3,16(sp)
100001e0:	00e12623          	sw	a4,12(sp)
100001e4:	00f12423          	sw	a5,8(sp)
100001e8:	01012223          	sw	a6,4(sp)
100001ec:	01112023          	sw	a7,0(sp)
100001f0:	200007b7          	lui	a5,0x20000
100001f4:	0047a783          	lw	a5,4(a5) # 20000004 <pMTIME>
100001f8:	0007a703          	lw	a4,0(a5)
100001fc:	0047a783          	lw	a5,4(a5)
10000200:	200006b7          	lui	a3,0x20000
10000204:	0006a803          	lw	a6,0(a3) # 20000000 <pMTIMECMP>
10000208:	08f0d537          	lui	a0,0x8f0d
1000020c:	18050513          	addi	a0,a0,384 # 8f0d180 <__STACK_SIZE_CORE0+0x8f0c980>
10000210:	00000593          	li	a1,0
10000214:	00a70633          	add	a2,a4,a0
10000218:	00060893          	mv	a7,a2
1000021c:	00e8b8b3          	sltu	a7,a7,a4
10000220:	00b786b3          	add	a3,a5,a1
10000224:	00d887b3          	add	a5,a7,a3
10000228:	00078693          	mv	a3,a5
1000022c:	00060713          	mv	a4,a2
10000230:	00068793          	mv	a5,a3
10000234:	00e82023          	sw	a4,0(a6)
10000238:	00f82223          	sw	a5,4(a6)
1000023c:	d00007b7          	lui	a5,0xd0000
10000240:	0287a683          	lw	a3,40(a5) # d0000028 <__CORE1_STACK_TOP+0xaffff018>
10000244:	d00007b7          	lui	a5,0xd0000
10000248:	02000737          	lui	a4,0x2000
1000024c:	00e6e733          	or	a4,a3,a4
10000250:	02e7a423          	sw	a4,40(a5) # d0000028 <__CORE1_STACK_TOP+0xaffff018>
10000254:	00000013          	nop
10000258:	01c12503          	lw	a0,28(sp)
1000025c:	01812583          	lw	a1,24(sp)
10000260:	01412603          	lw	a2,20(sp)
10000264:	01012683          	lw	a3,16(sp)
10000268:	00c12703          	lw	a4,12(sp)
1000026c:	00812783          	lw	a5,8(sp)
10000270:	00412803          	lw	a6,4(sp)
10000274:	00012883          	lw	a7,0(sp)
10000278:	02010113          	addi	sp,sp,32
1000027c:	30200073          	mret

Es curioso porque la rutina de atención a la interrupción NO usa ningún csr...

Ahora voy a intentar reducir el programa a lo mínimo, sobre todo... a ver si consigo que la interrupción sea sólo del core0

Esta es la versión reducida, que funciona, y que genera interrupciones sólo con el core0:

#include "Platform_Types.h"
#include "Cpu.h"
#include "Gpio.h"
#include "riscv.h"

void main_Core1(void);
__attribute__((interrupt)) void Isr_MachineTimerInterrupt(void);

volatile uint64_t* pMTIMECMP = (volatile uint64_t*)&(HW_PER_SIO->MTIMECMP.reg);
volatile uint64_t* pMTIME    = (volatile uint64_t*)&(HW_PER_SIO->MTIME.reg);

int main(void)
{
  LED_GREEN_CFG();

  /* enable machine timer interrupt */
  riscv_set_csr(RVCSR_MIE_OFFSET, 0x80ul);

  /* enable global interrupt */
  riscv_set_csr(RVCSR_MSTATUS_OFFSET, 0x08ul);

  /* configure machine timer to use 150 MHz */
  HW_PER_SIO->MTIME_CTRL.bit.FULLSPEED = 1;

  /* set next timeout (machine timer is enabled by default) */
  *pMTIMECMP = *pMTIME + 150000000ul; //1s

  for(;;);
  return(0);
}

void main_Core1(void)
{
  for(;;);
}

  
void Isr_MachineTimerInterrupt(void)
{
  *pMTIMECMP = *pMTIME + 150000000ul;
  
  LED_GREEN_TOGGLE();
}

La idea ahora es ver el ensamblador y aislarlo tal cual, sin documentar ni simplificar...

En este fichero está la inicialización directamente en ensamblador. Está guay porque se entiende muy bien:

/home/obijuan/Develop/Blinky_Pico2_dual_core_nosdk/Code/Startup/Core/RISC-V/boot.s

Las dependencias son las siguientes:

boot.s --> Startup.c --> main.c

2025-05-14

32-Traps

Sigo con el plan... mierda... estoy en el peor escenario posible... Ya tengo el programa entero en ensamblador. Lo ensamblo, lo cargo y nada... no consigo que funcione.....

Estoy encendiendo el LED desde el comienzo, para ver en qué parte PETA... de momento parece que tras la función Startup_InitSystemClock ya empieza a hacer cosas raras...

Tras muchas horas de depuración... HE LLEGADO A ALGO!! Por motivos que desconozco, el programa actual (sandbox) ejecuta una rutina de atención a las interrupciones que hace parpadear un led (dentro de ella). Ahora bien, no sé si es una interrupción, o una excepción o que. Parece que al darle al reset o arrancar se genera una trap... Al cargar desde el bootloader se ejecuta el programa principal, pero al darle al reset salta la interrupción... Tengo que investigar más....

2025-05-14

32-Traps

YA LO TENGO!! Ya he conseguido un ejemplo "Hola mundo" que captura excepciones. Ahora lo tengo mucho más claro, pero no llego a entender por qué no me funcionó la primera versión... Así que voy a empezar por esta versión y poco a poco ir añadiendo la funcionalidad que tenía en el programa original, a ver si descubro qué estaba haciendo mal

Este es el programa hola mundo que funciona. El programa principal enciende un LED. PERO como se realiza una llamada al sistema (ecall) se ejecuta la rutina de atención a la interrupción y se pone a parpadear el LED

Si comentamos el ecall entonces el LED se enciende

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.section .text

# -- Punto de entrada
_start:

    #-- Configurar la pila
    la sp, __stack_top
       
    #-- Configurar el vector de interrupcion
    #-- (Trap vector). Modo directo
    la t0, isr
    csrw mtvec, t0

    #-- Inicializar LED
    jal led_init

    #-- Generar una llamada al sistema
    ecall

    #-- Encender el LED
    #-- Aquí no llega, porque se ha saltado a la 
    #-- Rutina de interrupcion (que nunca retorna)
    jal led_on
    j .


#----------------------------------
#-- Atención a las Interrupciones
#----------------------------------
isr:
    #-- Hacer parpadear el LED
    #-- No se retorna nunca
    jal led_blinky2

Las excepciones se producen cuando se ejecuta una instrucción ilegal, se accede a una dirección no alineada o se ejecuta un ecall. Son cosas que no se pueden enmascarar. Se producen aunque las interrupciones estén deshabilitadas. Lo único que hay que hacer para que funcionen es establecer el vector de interrupción en el registro mtvec

33-Traps

HE ENCONTRADO EL FALLO!!! Madre mía... no me lo puedo creer... la propia rutina print_mtvec ESTA MODIFICANDO EL REGISTROS!!! Al leerlo lo estoy modificando!!! Y Claro, se lee y se imprime correctamente. Y pienso que tiene ese valor... pero en realidad lo ha modificado. Se comprueba fácilmente llamando a la rutina dos veces... y se obteiene esto:

MTVEC: 0x1000005C  (Trap handler base address)
  BASE:  0x1000005C (Trap vector address)
  MODE:  00 (Trap mode)
MTVEC: 0x00000000  (Trap handler base address)
  BASE:  0x00000000 (Trap vector address)
  MODE:  00 (Trap mode)

La segunda vez tiene 0!!!!!!

El problema de raiz es que no se manejar todavía las instrucciones csr... Creo que debería primero practicar con ellas...

Este es el programa original, ya arreglado. Así que puedo continuar por donde lo había dejado

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    CLS

    #--------- ESTABLECER VECTOR DE INTERRUPCION
    #------- MTVEC
    #-- Antes
    jal print_mtvec

    la t0, trap_handle
    csrrw zero, mtvec, t0

    #-- Despues
    jal print_mtvec

    #--------- ACTIVAR LAS INTERRUPCIONES
    #------- MIE.SIE=1
    #-- Antes
    jal print_mie

    #-- mie.msie = 1
    li t0, BIT3  #-- 0x08
    csrrw zero, mie, t0

    #-- Despues
    jal print_mie
    NL

    #------- MSTATUS.MIE=1
    #-- Antes
    jal print_mstatus

    li t0, 0x1808
    csrrw zero, mstatus, t0

    #-- Despues
    jal print_mstatus

    #-------- MIP.MSIP=1
    #-- Antes
    jal print_mip

    li t0, BIT3
    csrrw zero, mip, t0

    jal print_mip

    #--- GENERAR INTERRUPCION!!
    li t0, 1
    lw t1, 0(t0)  #-- TRAP! Lectura de una direccion NO alineada
    ecall

loop: 

    #-- Esperar
    DELAY_MS 200

    #-- Cambiar de estado el LED
    jal led_toggle

    j loop
    
trap_handle:
    #-- Encender el LED
    li t0, GPIO_OUT_SET
    li t1, BIT25    #-- Activar el bit 25
    sw t1, 0(t0)
    j .

34-csr

Voy a leer más sobre las instrucciones para manejo de los csr. Tengo que entenderlas perfectamente

Todas estas instrucciones SON ATOMICAS. Realizan dos operaciones a la vez sobre un registro de control. La lectura se realiza sobre un registro destino (rd), que puede ser zero. En ese caso la lectura se ignora: es decir, no se hace lectura, solo se hace la otra operación

  • READ + WRITE: csrrw rd, reg, rs: Se hacen estas transferencias: rd = reg reg = rs La forma que tengo de entender mejor esta instrucción es dibujarla así: rd <-- reg <-- rs (como si fuesen registro de desplazamiento a la izquierda)

  • READ + SET: csrrs rd, reg, rs: En este caso el registro fuente hace de máscara. Los bits indicados en rs se ponen a 1 en reg

  • READ + CLR: csrrc rd, reg, rs: Los bits indicados en rs se ponen a 0

El registro fuente se puede sustituir por un valor inmediato de 5 bits. Se tienen las siguientes 3 instrucciones:

  • READ, WRITE INMM, csrrw rd, reg, imm
  • READ, SET IMM, csrrs rd, reg, imm
  • READ, CLEAR IMM, csrrc rd, reg, imm

Las operaciones de Lectura, Escritura, Set y Clr se pueden realizar de manera independiente usando pseudoinstrucciones

  • READ, csrr rd, reg: Lectura de un registro
  • WRITE, csrw reg, rs: Escritura
  • WRITE IMM, csrwi reg, imm: Escritura inmediata
  • SET, csrs reg, rs: Poner bits a 1 (los indicados por el registro rs)
  • SET IMM, csrsi reg, imm: Poner bits a 1 (los indicados por el numero imm de 5 bits)
  • CLR, csrc reg, rs: Poner bits a 0 (los indicados por el registro rs)
  • CLR IMM, csrci reg, imm: Poner bits a 0 (los indicados por el numero imm de 5 bits)

En este ejemplo se muestra el funcionamiento:

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.macro PRINT_MSCRATCH
    csrrs a0, mscratch, zero
    jal print_0x_hex32
    NL
.endm

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    CLS

    #---------- Instrucciones CSR con valores inmediatos

    #-- Escribir un valor inmediato en el 
    #-- registro scratch (registro temporal)
    #-- Solo se pueden escribir valores inmediatos
    #-- de 5 bits
    csrrwi zero, mscratch, 0x1F

    #-- Leer el registro scratch y mostrarlo
    #-- Se usa READ-SET, pero como el registro fuente
    #-- es zero, no se modifica scratch
    csrrs a0, mscratch, zero
    jal print_0x_hex32
    NL

    #-- Borrar el bit 0
    csrrci zero, mscratch, 1
    PRINT_MSCRATCH

    #-- Borrar bits 1 y 2
    csrrci zero, mscratch, BIT1 | BIT2
    PRINT_MSCRATCH

    #-- Activar bits 0, 1 y 2
    csrrsi zero, mscratch, BIT0 | BIT1 | BIT2
    PRINT_MSCRATCH

    #---------- Instrucciones CSR con registro fuente

    #-- Escribir el valor 0xFFFFFFFF
    li t0, -1
    csrrw zero, mscratch, t0
    PRINT_MSCRATCH

    #-- Borrar bits
    li t0, BIT31 | BIT30 | BIT29 | BIT28 | BIT3 | BIT2 | BIT1 | BIT0
    csrrc zero, mscratch, t0
    PRINT_MSCRATCH

    #-- Activar bits
    li t0, BIT31 | BIT30
    csrrs zero, mscratch, t0
    PRINT_MSCRATCH

    #-------------- PseudoInstrucciones
    #---- WRITE IMM
    csrwi mscratch, 0x5

    #---- READ
    csrr a0, mscratch
    jal print_0x_hex32
    NL

    #--- WRITE
    li t0, 0xCAFEBACA
    csrw mscratch, t0 
    PRINT_MSCRATCH

    #-- SET IMM
    csrsi mscratch, 0x5
    PRINT_MSCRATCH

    #-- SET
    li t0, 0x30000000
    csrs mscratch, t0
    PRINT_MSCRATCH

    #-- CLEAR IMM
    csrci mscratch, 0x5
    PRINT_MSCRATCH

    #-- CLEAR
    li t0, 0x30000000
    csrc mscratch, t0
    PRINT_MSCRATCH

#-- HALT!
halt:    j .

Esta es la salida en la consola:

0x0000001F
0x0000001E
0x00000018
0x0000001F
0xFFFFFFFF
0x0FFFFFF0
0xCFFFFFF0
0x00000005
0xCAFEBACA
0xCAFEBACF
0xFAFEBACF
0xFAFEBACA
0xCAFEBACA

35-traps

En este ejemplo se generan diferentes tipos de excepciones, y en la rutina de atención a la interrupción se imprime la causa de la interrupción. Lo único que no he conseguido es pasar al modo usuario para generar un ecall desde el modo usuario. Al pasar a modo usuario se genera otra excepción, de fallo de instrucción o algo así... en fin... que hay algo que no entiendo

PERO todo lo demás funciona muy bien. Las excepciones... DOMINADAS!

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

menu:
    CLS
    PRINT "Test de interrupciones\n"
    PRINT "1.- Ecall desde modo M\n"
    PRINT "2.- Instruccion ilegal\n"
    PRINT "3.- BREAKPOINT\n"
    PRINT "4.- LOAD en direccion NO alineada\n"
    PRINT "5.- STORE en direccion NO alineada\n"
    PRINT "6.- Pasar a modo usuario\n"

prompt:
    PRINT "> "
    jal getchar

    #-- Salvar la tecla
    mv s0, a0

    #-- Eco de al tecla
    jal putchar
    PUTCHAR '\n'

    #-- Recuperar tecla
    mv a0, s0

    li t0, ' ' 
    beq a0, t0, menu

    li t0, '1'
    beq a0, t0, opcion1

    li t0, '2'
    beq a0, t0, opcion2

    li t0, '3'
    beq a0, t0, opcion3

    li t0, '4'
    beq a0, t0, opcion4

    li t0, '5'
    beq a0, t0, opcion5

    li t0, '6'
    beq a0, t0, opcion6

    j prompt

opcion1:
    ecall
    j prompt

opcion2:
    .word 0  #-- Instruccion ilegal
    j prompt

opcion3:
    ebreak
    j prompt

opcion4:
    li t0, 1
    lw t0, 0(t0)  #-- Acceso a direccion no alineada
    j prompt

opcion5: 
    li t0, 1
    sw zero, 0(t0) #-- Acceso a direccion no alineada
    j prompt

opcion6:
    #-- Pasar a modo usuario
    jal print_mstatus

    #-- Poner a 0 bits del campo MPP
    #-- Para que el modo de privilegio sea de usuario
    li t0, MPP
    csrc mstatus, t0

    #-- Meter en mepc la direccion de retorno
    la t0, halt
    csrw mepc, t0

    #-- Saltar a prompt
    #-- en modo USUARIO
    mret 

#-- HALT!
halt:
    nop
    j .

#------------------------------------------
#-- Rutina de atencion a la interrupcion 
#------------------------------------------
isr:
    PRINT "----> Interrupción!\n"
    #--- Lo primero que hacemos es averiguar la causa de la 
    #--- interrupción
    #--- eso se encuentra en el registro mcause
    jal print_mcause

    #-- Leer causa
    csrr a0, mcause

    #-- Comprobar si la causa es por una excepcion
    #-- o por una interrupcion
    #-- Esto se hace comprobando el bit INT (el de mayor peso)
    bge a0, zero, es_excepcion

    #-- Es una interrupcion
    PRINT "TIPO: Interrupcion\n"
    PRINT "Desconocida...\n"
    jal led_blinky3

es_excepcion:
    PRINT "TIPO: Excepcion\n"

    #-- Comprobar qué tipo de excepcion es
    csrr t0, mcause

    #-- Aislar la causa
    andi t0, t0, 0xF
    
    li t1, ECALL
    beq t0, t1, es_ecall

    li t1, ILEGAL_INST
    beq t0, t1, excep_ilegal_inst

    li t1, BREAKPOINT
    beq t0, t1, es_ebreak

    li t1, NOT_ALIGN_LOAD
    beq t0, t1, excep_not_align_load

    li t1, NOT_ALIGN_STORE
    beq t0, t1, excep_not_align_store

    li t1, INST_FAULT
    beq t0, t1, excep_inst_fault

    #-- Causa desconocida
    PRINT "Desconocida\n"
    jal led_blinky3

#---- La excepcion es debida a ecall
es_ecall:

    PRINT "ECALL\n\n"
    j isr_end

es_ebreak:
    PRINT "EBREAK\n\n"
    j isr_end

#--- La excepcion es por instruccion ilegal
excep_ilegal_inst:
    PRINT "INSTRUCCION ILEGAL\n\n"
    j isr_end

#--- La excepcion es por error de alineamiento
excep_not_align_load:
    PRINT "ERROR DE ALINEAMIENTO EN LECTURA\n\n"
    j isr_end

#--- Error de alineamiento en store
excep_not_align_store:
    PRINT "ERROR DE ALINEAMIENTO EN ESCRITURA\n\n"
    j isr_end

#-- Instruction fault
excep_inst_fault:
    PRINT "FALLO EN INSTRUCCION\n"

    #-- Imprimir mepc: es la instruccion que causo el fallo
    csrr a0, mepc
    jal print_0x_hex32
    NL
    jal led_blinky3

isr_end:
    #-- TERMINAR---------------------------
    #--- Leer la direccion de retorno
    csrr t0, mepc

    #-- Incrementar la direccion en 4 para retornar a la
    #-- siguiente instruccion a ecall
    addi t0, t0, 4
    csrw mepc, t0

    #-- Retornar
    mret

Este es el resultado:

Test de interrupciones
1.- Ecall desde modo M
2.- Instruccion ilegal
3.- BREAKPOINT
4.- LOAD en direccion NO alineada
5.- STORE en direccion NO alineada
6.- Pasar a modo usuario
> 1
----> Interrupción!
MCAUSE: 0x0000000B
  INTERRUPT: 0  (Exception(0)/Interrupt(1))
  CODE:      B  (Cause of the trap)
TIPO: Excepcion
ECALL

> 2
----> Interrupción!
MCAUSE: 0x00000002
  INTERRUPT: 0  (Exception(0)/Interrupt(1))
  CODE:      2  (Cause of the trap)
TIPO: Excepcion
INSTRUCCION ILEGAL

> 3
----> Interrupción!
MCAUSE: 0x00000003
  INTERRUPT: 0  (Exception(0)/Interrupt(1))
  CODE:      3  (Cause of the trap)
TIPO: Excepcion
EBREAK

> 4
----> Interrupción!
MCAUSE: 0x00000004
  INTERRUPT: 0  (Exception(0)/Interrupt(1))
  CODE:      4  (Cause of the trap)
TIPO: Excepcion
ERROR DE ALINEAMIENTO EN LECTURA

> 5
----> Interrupción!
MCAUSE: 0x00000006
  INTERRUPT: 0  (Exception(0)/Interrupt(1))
  CODE:      6  (Cause of the trap)
TIPO: Excepcion
ERROR DE ALINEAMIENTO EN ESCRITURA

> 

Esto es lo que ocurre cuando se intenta pasar a modo usuario:

> 6
MSTATUS: 0x00000080
  TW:   0  (Timeout Wait)
  MPRV: 0  (Modify PRiVilegde)
  MPP:  00 (Previous Privilegde level)
  MPIE: 1  (Previous Interrupt Enable)
  MIE:  0  (Interrupt enable)
----> Interrupción!
MCAUSE: 0x00000001
  INTERRUPT: 0  (Exception(0)/Interrupt(1))
  CODE:      1  (Cause of the trap)
TIPO: Excepcion
FALLO EN INSTRUCCION
0x10000148

2025-05-16

Modo usuario (no resuelto)

El paso al modo usuario no lo tengo resuelto. Como hemos visto, al ejecutar el mret para empezar a ejecutar en modo usaurio, se produce una excepción... No tengo claro lo que hes... pero tengo una hipótesis: Tal vez en modo usuario NO SE PUEDE EJECUTAR DE LA FLASH. Esto tiene sentido. Para comprobar si esta hiṕotesis es correcta hay habría que especificar una de mepc que esté en la RAM

Para hacerlo fácil, se me ocurre guardar la palabra correspondiente halt (j .) en la ram, y hacer que mepc contenga esta dirección, y comprobar si se produce la excepción o no. Si se produce la excepción, hay que seguir invetigando. Si no se produce entonces ya estaría resuelto el problema

Este es el programa que falla. Pasa a modo usuario con código en flash. Se lanza la excepción y se pone a parpadear el LED muy rápidamente. Si hubiese funcionado no debería lucir el led

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    # Configurar el valor del registro mepc: dirección de entrada del modo usuario
    la t0, user_mode_entry    # Cargar dirección de la etiqueta user_mode_entry en t0
    csrw mepc, t0             # Guardar esa dirección en el registro mepc

    # Limpiar y establecer el bit MPP en mstatus para seleccionar modo usuario (00)
    csrr t1, mstatus          # Leer mstatus
    li t2, ~0x1800             # MPP está en bits 11:12 (0b11 << 11 = 0x1800)

    and t1, t1, t2      # Limpiar los bits MPP (establecerlos a 00 = U-mode)
    csrw mstatus, t1          # Escribir de nuevo en mstatus

    # Saltar al modo usuario
    mret                     # Retorno desde modo máquina → salta a mepc en modo usuario

# Código en modo usuario
user_mode_entry:
    # Aquí comienza la ejecución en modo usuario
    j .


#------------------------------------------
#-- Rutina de atencion a la interrupcion 
#------------------------------------------
#-- En caso de excepcion el LED parpadea rapidamente
isr:
    jal led_blinky3

Ahora lo modificamos para ejecutar el código en RAM (Dir. 0x20000000)

El código máquina de halt es: 0x0000006f

Este es el programa:

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    #-- Almacenar "j ." en la dirección de la RAM (0x2000_0000)
    li t0, 0x20000000
    li t1, 0x0000006f  #-- Instruccion j .
    sw t1, 0(t0)

    # Configurar el valor del registro mepc: dirección de entrada del modo usuario
    csrw mepc, t0

    # Limpiar y establecer el bit MPP en mstatus para seleccionar modo usuario (00)
    csrr t1, mstatus   # Leer mstatus
    li t2, ~0x1800     # MPP está en bits 11:12 (0b11 << 11 = 0x1800)

    and t1, t1, t2      # Limpiar los bits MPP (establecerlos a 00 = U-mode)
    csrw mstatus, t1    # Escribir de nuevo en mstatus

    # Saltar al modo usuario
    mret                     # Retorno desde modo máquina → salta a mepc en modo usuario

#------------------------------------------
#-- Rutina de atencion a la interrupcion 
#------------------------------------------
#-- En caso de excepcion el LED parpadea rapidamente
isr:
    jal led_blinky3

Al ejecutarlo se activa también la excepción.... En principio entonces el problema NO es debido a esto... así que nada... hay que leer más documentación....

36-csr-pmm

Leyendo la documentación de este apartado: "3.8.3.3. Accesses Spanning Multiple PMP Regions" (Pag. 281), se explica brevemente la excepción de causa (1): fallo de instrucción. Está relacionado con la protección de la memoria. Puede ser que por defecto en el modo usuario NO se tenga permiso de ejecución. La cuestión es: ¿Dónde están estos permisos? ¿Cómo se cambian?

PMP = Physical Memory Protection

Lo que se me ocurre es encontrar los registros de configuración de las regiones e imprimirlos, a ver qué valores tienen, a ver si saco algo en claro

En la causa de la excepción en modo usuario (1), la documentación de la pico2 dice: "INSTR_FAULT: Instruction access fault. Instruction fetch failed a PMP check, or encountered a downstream bus fault, and then passed the point of no speculation.". Me quedo con la primera parte: La captura de la instrucción no pasó la comprobación de protección de memoria física... Por ello tengo que ver el valor de estos registros

Voy a leer sobre la protección de memoria, en el datasheet: "3.8.3. Memory Protection" (Pag. 279).
Estos son algunos de los registros: PMPADDR0 y PMPCFG0. Voy a imprimir su valor a ver si saco algo en claro

Lo primero es ver los registros de configuración. Se muestran con estes programa:

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    CLS

    #---- Mostrar registro PMPADDR0
    PRINT "PMPADDR0: "
    csrr a0, pmpaddr0
    jal print_0x_hex32
    NL

    #---- Mostrar registro PMPCFG0
    PRINT "PMPCFG0:  "
    csrr a0, pmpcfg0
    jal print_0x_hex32
    NL

    #---- Mostrar registro PMPCFG1
    PRINT "PMPCFG1:  "
    csrr a0, pmpcfg1
    jal print_0x_hex32
    NL

    #---- Mostrar registro PMPCFG2
    PRINT "PMPCFG2:  "
    csrr a0, pmpcfg2
    jal print_0x_hex32
    NL

    #---- Mostrar registro PMPCFG3
    PRINT "PMPCFG3:  "
    csrr a0, pmpcfg3
    jal print_0x_hex32
    NL

    j .

Este es el resultado:

PMPADDR0: 0x00000003
PMPCFG0:  0x00000000
PMPCFG1:  0x00000000
PMPCFG2:  0x001F1F1F
PMPCFG3:  0x00000000

Los registros de configuración 0,1 y 3 están a 0. Esto significa que la protección NO está habilitada
PERO Sí para el registro de configuración 2. Se definen las regiones de la 8 a la 11 Analizamos los bits:

L Res A  R W X L Res  A R W X L  Res A  R W X L  Res   A  R W X
0_00__00_0_0_0_0_00__11_1_1_1_0__00__11_1_1_1_0___00___11_1_1_1

Las 4 regiones tienen todas permisos de Ejecución, Lectura y Escritura

R8_A: 11. Modo NAPOT: Naturally aligned power-of-two (8 bytes to 4 GiB)
R8_R: 1. Permiso de lectura
R8_W: 1. Permiso de escritura
R8_X: 1. Permiso de ejecución. Región 8

Leemos las direcciones de las regiones 8 a 11, añadiendo estas instrucciones:

   #---- Mostrar registro PMPADDR8
    PRINT "PMPADDR8: "
    csrr a0, pmpaddr8
    jal print_0x_hex32
    NL

    #---- Mostrar registro PMPADDR9
    PRINT "PMPADDR9: "
    csrr a0, pmpaddr9
    jal print_0x_hex32
    NL

    #---- Mostrar registro PMPADDR10
    PRINT "PMPADDR10: "
    csrr a0, pmpaddr10
    jal print_0x_hex32
    NL

    #---- Mostrar registro PMPADDR11
    PRINT "PMPADDR11: "
    csrr a0, pmpaddr11
    jal print_0x_hex32
    NL

Este es el resultado:

PMPCFG0:  0x00000000
PMPCFG1:  0x00000000
PMPCFG2:  0x001F1F1F
PMPCFG3:  0x00000000
PMPADDR8: 0x01FFFFFF
PMPADDR9: 0x13FFFFFF
PMPADDR10: 0x35FFFFFF
PMPADDR11: 0x00000000

Ahora hay que saber a qué direcciones se corresponden esos valores Según leo en la documentación, la región 8 se corresponde con las direcciones de la ROM: 0x00000000 through 0x0fffffff, justo antes de la flash. Hay más información en la página 322

La región 9 da acceso a los periféricos: 0x40000000 through 0x5fffffff

La región 10 da acceso a este rango: 0xd0000000 through 0xdfffffff (Sio)

La región 11 NO está implementada

Así que, el modo usuario tiene permisos para acceder a la rom, periféricos y sio. La pregunta es... qué pasa por defecto? Se tiene acceso o no?. Lo que tendría que intentar configurar es dar acceso explícito rwx a la flash... ¿Cómo lo hago?

Quiero dar acceso RWX a una región que empiece en 0x1000_0000 y llegue al menos a 0x1000_20fe

Voy a leer este artículo: https://incoresemi.com/risc-v-memory-protection-diving-deep-into-the-complexities/

Le he preguntado a chatgpt, me dice que hay que meter el valor 0x05FFFFFF en el registro de address0, y 0x1F en el de configuración

Voy a hacer la prueba...

37-u-mode

En este programa, en principio, se cambia al modo usuario, aunque todavía hay cosas extrañas.

Partimos de este programa, donde activamos los permisos de ejecución para la memoria flash. Aunque todavía no entiendo bien la codificación, voy a usar los valores indicados por chatgtp

PERO en este primera versión comentamos la escritura en el registro PMPCFG para forzar la excepción. Al ejecutarlo el LED parpadea rápido (porque llega la excepción)

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    CLS

    #-- Configurar los permisos
    li t0, 0x5FFFFFFF
    csrw pmpaddr0, t0

    li t1, 0x1F
    #csrw pmpcfg0, t1

    #---- Mostrar registro PMPCFG0
    PRINT "PMPCFG0:   "
    csrr a0, pmpcfg0
    jal print_0x_hex32
    NL

    #---- Mostrar registro PMPADDR8
    PRINT "PMPADDR0:  "
    csrr a0, pmpaddr0
    jal print_0x_hex32
    NL

    #-- Poner a 0 bits del campo MPP
    #-- Para que el modo de privilegio sea de usuario
    #li t0, MPRV
    csrw mstatus, zero

    #-- Meter en mepc la direccion de retorno
    la t0, user_mode_entry
    csrw mepc, t0

    #-- Saltar a modo usuario
    mret 


# Código en modo usuario
user_mode_entry:
    j .

#------------------------------------------
#-- Rutina de atencion a la interrupcion 
#------------------------------------------
#-- En caso de excepcion el LED parpadea rapidamente
isr:
    jal led_blinky3

Experimento: Ahora descomentarmos la instrucción. La parte de asignar los permisos es esta:

    #-- Configurar los permisos
    li t0, 0x5FFFFFFF
    csrw pmpaddr0, t0

    li t1, 0x1F
    csrw pmpcfg0, t1

Ahora NO SE EJECUTA LA EXCEPCIÓN! Esto es cojonudo!. El LED se queda apagado porque la parte del usuario entra en un bucle infinito

PERO ahora aparece otro problema. Para estar seguros de que se ejecuta el modo usuario, vamos a encender un LED. El nuevo código es este:

# Código en modo usuario
user_mode_entry:
    jal led_on
    j .

Sin embargo el LED NO SE ENCIENDE!! ¿Qué cojones se está ejecutando?. En la excepción no entra... He estado probando y probando y no consigo encontrar nada...

La sensación que tengo es que sí que estoy en modo usuario y que por la razon que sea, el LED no se enciende. Tal vez porque tampoco tenga permisos para encender... aunque si fuese por eso debería saltar una excepción por intentar acceder a algo que no se puede acceder

En cualquier caso, si añado una instrucción privilegiada desde este "modo usuario"... sí que salta la excepción

# Código en modo usuario
user_mode_entry:
    jal led_on
    csrw mtvec, zero
    j .

Y además lo he puesto tras el led... por lo que parece que sí ejecuta las instrucciones del LED pero no se enciende. Luego ejecuta la instrucción privilegiada y salta la excepción

Se ocurre que dentro de la excepción voy a imprimir el registro mstatus, para ver sus campos

PMPCFG0:   0x0000001F
PMPADDR0:  0x1FFFFFFC
MSTATUS: 0x00000000
  TW:   0  (Timeout Wait)
  MPRV: 0  (Modify PRiVilegde)
  MPP:  00 (Previous Privilegde level)
  MPIE: 0  (Previous Interrupt Enable)
  MIE:  0  (Interrupt enable)

Efectivamente venimos del modo usuario!! Estábamos en modo usuario!!

Se me ocurren más pruebas... Desde el modo usuario podría intentar escribir valores en la memoria ram. Y luego desde la excepción hacer un volcado de esa parte de la ram... a ver si están los valores...

Siiiiiiiiiiiiiiiii!! Confirmadísimo! Estamos en modo usuario!!!!

Este es el programa:

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    CLS

    #-- Configurar los permisos
    li t0, 0x5FFFFFFF
    csrw pmpaddr0, t0

    li t1, 0x1F
    csrw pmpcfg0, t1

    #---- Mostrar registro PMPCFG0
    PRINT "PMPCFG0:   "
    csrr a0, pmpcfg0
    jal print_0x_hex32
    NL

    #---- Mostrar registro PMPADDR8
    PRINT "PMPADDR0:  "
    csrr a0, pmpaddr0
    jal print_0x_hex32
    NL

    #-- Poner a 0 memoria por si acaso
    li t0, 0x20000000
    sw zero, 0(t0)
    sw zero, 4(t0)

    #-- Volcado de memoria de la direccion 0x2000_0000
    li a0, 0x20000000
    li a1, 1
    jal dump16

    #-- Poner a 0 bits del campo MPP
    #-- Para que el modo de privilegio sea de usuario
    #li t0, MPRV
    csrw mstatus, zero

    #-- Meter en mepc la direccion de retorno
    la t0, user_mode_entry
    csrw mepc, t0

    #-- Saltar a modo usuario
    mret 


# Código en modo usuario
user_mode_entry:

    #-- Escribir valores en la RAM
    li t0, 0x20000000
    li t1, 0xCAFEBACA
    li t2, 0xABCDEF01
    sw t1, 0(t0)
    sw t2, 4(t0)

    csrw mtvec, zero
    j .

#------------------------------------------
#-- Rutina de atencion a la interrupcion 
#------------------------------------------
isr:
    jal print_mstatus
    jal print_mcause

     #-- Volcado de memoria
    li a0, 0x20000000
    li a1, 1
    jal dump16

    jal led_blinky3

Pero ahora se ha abierto otro problema: ¿Cómo encender el LED desde el modo de usuario?

38-led-u-mode

¡Ya he conseguido encender el led en el modo usuario! He tenido que activar los permisos en un registro de la zona ACCESSCTRL

Este es el código:

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    CLS

    #-- Establecer permisos para acceder al LED
    #-- desde el modo usuario
    li t0, ACCESSCTRL_GPIO_NSMASK0
    li t1, BIT25
    sw t1, 0(t0) 

    #-- Configurar los permisos de acceso a la flash
    li t0, 0x5FFFFFFF
    csrw pmpaddr0, t0

    li t1, 0x1F
    csrw pmpcfg0, t1

    #------- PASAR A MODO USUARIO

    #-- Poner a 0 bits del campo MPP
    #-- Para que el modo de privilegio sea de usuario
    csrw mstatus, zero

    #-- Meter en mepc la direccion de retorno
    la t0, user_mode_entry
    csrw mepc, t0

    #-- Saltar a modo usuario
    mret 


#------------------------------
# Código en modo usuario
#------------------------------
user_mode_entry:

    #-- Encender el LED desde 
    #-- el modo usuario
    jal led_on
    j .

#------------------------------------------
#-- Rutina de atencion a la interrupcion 
#------------------------------------------
isr:
    jal print_mstatus
    jal print_mcause

    jal led_blinky3

2025-05-18

Breakout Board for Raspberry Pi Pico

Me he comprado en Amazon la placa Breakout Board for Raspberry Pi Pico de Freenove. La placa está muy bien. Tiene LEDs en todos sus GPIOS, y las conexiones son muy fáciles. Tiene pines macho por cada pata de la placa, así como unas mini-clemas para conexiones más sólidas

Este es el escenario que tengo montado ahora:

  • Alhambra-II: para usarlo como convertidor USB-serie. Está conectado a la UART0 de la pico2 (Tx y Rx) para mostrar mensajes en la consola
  • Freenove Breakout Board: Es donde está pinchada la pico2
  • Botón de reset en Breadboard, conectado al pin RUN de la Pico2
  • Botón genérico conectado al gpio15

2025-05-23

39-Debug-Probe (No resuelto)

Me he comprado el depurador para la pico2 en Amazon: https://www.amazon.es/dp/B0C36HXMCB?ref=ppx_yo2ov_dt_b_fed_asin_title
Este es el nuevo escenario:

La sonda de prueba tiene 2 conexiones. La de la izquierda es para conectar tx y rx de la pico2, y es para usar la consola. Es lo que hemos estado utilizando hasta ahora con la Alhambra-II. La de la derecha es para la depuración propiamente dicha

La documentación del depurador está en este enlace: https://www.raspberrypi.com/documentation/microcontrollers/debug-probe.html

Este es el programa de prueba que estoy usando para probar:

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    CLS

loop:
    PRINT "HOLA!\n"
    jal getchar
    j loop

#------------------------------------------
#-- Rutina de atencion a la interrupcion 
#------------------------------------------
isr:
    jal led_blinky3

Este es el comando que usamos para lanzar la consola:

tio /dev/ttyACM0 --map ICRNL,INLCRNL

Si la sonda está conectada, NO PODEMOS ACTIVAR EL BOOTLOADER, y por tanto no se nos monta el pico2 como unidad USB

Voy a probar a cargar un programa siguiendo las instrucciones de la sonda.

Sigo las instrucciones pero NO consigo cargar nada en la placa...

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/39-debug-probe$ openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" -c "program main.elf verify reset exit"
Open On-Chip Debugger 0.12.0
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
adapter speed: 5000 kHz

Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=E663AC91D31DA534
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: Atomic commands supported
Info : CMSIS-DAP: Test domain timer supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0
Info : CMSIS-DAP: Interface ready
Info : clock speed 5000 kHz
Error: Failed to connect multidrop rp2040.dap0
in procedure 'program'
** OpenOCD init failed **
shutdown command invoked

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/39-debug-probe$

Joder... no consigo nada.... siempre el mismo error. Estoy leyendo foros y foros... en todos dicen que si no funciona es porque la conexión entre los pines no es correcta....

La versión que estoy utilizando de openocd es esta:

obijuan@JANEL:~$ openocd --version
Open On-Chip Debugger 0.12.0
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
obijuan@JANEL:~$

Estoy siguiendo las instrucciones de este foro de adafruit: https://community.sparkfun.com/t/sparkfun-pro-micro-rp2350-debug-problem/60099

En concreto este post indica algo interesante:

It is required to use rp2350.cfg for the RP2350. If you don’t have that file, you probably don’t have the latest OpenOCD installed. Please follow Raspberry Pi’s instructions in Appendix A here: https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf

Voy a compilar a mano el openocd siguiendo las instrucciones del Apéndice A

Me ha compilado todo sin problemas

Ahora pruebo esto:

obijuan@JANEL:~/Develop/openocd$ src/openocd -s tcl -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000"
Open On-Chip Debugger 0.12.0+dev-gcf9c0b41c (2025-05-23-18:55)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : Hardware thread awareness created
cortex_m reset_config sysresetreq
adapter speed: 5000 kHz
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=E663AC91D31DA534
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: Atomic commands supported
Info : CMSIS-DAP: Test domain timer supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0
Info : CMSIS-DAP: Interface ready
Info : clock speed 5000 kHz
Info : SWD DPIDR 0x4c013477
Info : [rp2350.dap.core0] Cortex-M33 r1p0 processor detected
Info : [rp2350.dap.core0] target has 8 breakpoints, 4 watchpoints
Info : [rp2350.dap.core0] Examination succeed
Info : [rp2350.dap.core1] Cortex-M33 r1p0 processor detected
Info : [rp2350.dap.core1] target has 8 breakpoints, 4 watchpoints
Info : [rp2350.dap.core1] Examination succeed
Info : starting gdb server for rp2350.dap.core0 on 3333
Info : Listening on port 3333 for gdb connections

SIIIIIIIIIIIIIIIIIIIIIIII ya no sale el error!!! Vaaaaamos!!!!

Si ahora desconecto un cable... sale error!! Siiii!!!

Si no conecto por usb la pico2 (y por tanto NO está alimentada) sale también el error:

obijuan@JANEL:~/Develop/openocd$ src/openocd -s tcl -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000"
Open On-Chip Debugger 0.12.0+dev-gcf9c0b41c (2025-05-23-18:55)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : Hardware thread awareness created
cortex_m reset_config sysresetreq
adapter speed: 5000 kHz
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=E663AC91D31DA534
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: Atomic commands supported
Info : CMSIS-DAP: Test domain timer supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0
Info : CMSIS-DAP: Interface ready
Info : clock speed 5000 kHz
Error: Error connecting DP: cannot read IDR


obijuan@JANEL:~/Develop/openocd$

La versión de openocd que he compilado y que funciona es esta:

obijuan@JANEL:~/Develop/openocd$ src/openocd --version
Open On-Chip Debugger 0.12.0+dev-gcf9c0b41c (2025-05-23-18:55)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
obijuan@JANEL:~/Develop/openocd$

Lo que tengo que hacer es desintalar el openocd antiguo e instalar este....

El nuevo se instala con esto:

obijuan@JANEL:~/Develop/openocd$ sudo make instal

JODER... ahora me ha dejado de funcionar....

Open On-Chip Debugger 0.12.0+dev-gcf9c0b41c (2025-05-23-18:55)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : Hardware thread awareness created
cortex_m reset_config sysresetreq
adapter speed: 5000 kHz
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Error: unable to find a matching CMSIS-DAP deviceash

¿Qué está pasando?... lo estoy ejecutando desde el repo original

Si lo ejecuto con sudo sí que funciona...

obijuan@JANEL:~/Develop/openocd$ sudo src/openocd -s tcl -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000"
Open On-Chip Debugger 0.12.0+dev-gcf9c0b41c (2025-05-23-18:55)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : Hardware thread awareness created
cortex_m reset_config sysresetreq
adapter speed: 5000 kHz
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=E663AC91D31DA534
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: Atomic commands supported
Info : CMSIS-DAP: Test domain timer supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0
Info : CMSIS-DAP: Interface ready
Info : clock speed 5000 kHz
Info : SWD DPIDR 0x4c013477
Info : SWD DPIDR 0x4c013477
Error: Failed to read memory at 0xe000ed00
Error: [rp2350.dap.core0] Examination failed
Warn : target rp2350.dap.core0 examination failed
Info : SWD DPIDR 0x4c013477
Error: Failed to read memory at 0xe000ed00
Error: [rp2350.dap.core1] Examination failed
Warn : target rp2350.dap.core1 examination failed
Info : starting gdb server for rp2350.dap.core0 on 3333
Info : Listening on port 3333 for gdb connections

Parece un tema de permisos...

Primero localizao el depurador:

obijuan@JANEL:~/Develop/openocd$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 3554:fc06 CX 2.4G Receiver
Bus 001 Device 003: ID 0408:5365 Quanta Computer, Inc. HP TrueVision HD Camera
Bus 001 Device 004: ID 13d3:3567 IMC Networks Wireless_Device
Bus 001 Device 020: ID 05e3:0610 Genesys Logic, Inc. Hub
Bus 001 Device 047: ID 2e8a:000c Raspberry Pi Debug Probe (CMSIS-DAP)
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 002 Device 004: ID 05e3:0626 Genesys Logic, Inc. Hub

Ahí está... es la ID 2e8a:000c Raspberry Pi Debug Probe (CMSIS-DAP)

VID:PID=0x2e8a:0x000c

Vamos a crear una regla udev

He añadido esto al fichero /etc/udev/rules.d/99-picotool.rules

SUBSYSTEM=="usb", \
    ATTRS{idVendor}=="2e8a", \
    ATTRS{idProduct}=="000c", \
    TAG+="uaccess" \
    MODE="660", \
    GROUP="plugdev"

Siiiiiiiiiiiiii!! Ahora ya sí funciona

obijuan@JANEL:~$ openocd -s tcl -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000" 
Open On-Chip Debugger 0.12.0+dev-gcf9c0b41c (2025-05-23-18:55)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : Hardware thread awareness created
cortex_m reset_config sysresetreq
adapter speed: 5000 kHz
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=E663AC91D31DA534
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: Atomic commands supported
Info : CMSIS-DAP: Test domain timer supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0
Info : CMSIS-DAP: Interface ready
Info : clock speed 5000 kHz
Info : SWD DPIDR 0x4c013477
Info : SWD DPIDR 0x4c013477
Error: Failed to read memory at 0xe000ed00
Error: [rp2350.dap.core0] Examination failed
Warn : target rp2350.dap.core0 examination failed
Info : SWD DPIDR 0x4c013477
Error: Failed to read memory at 0xe000ed00
Error: [rp2350.dap.core1] Examination failed
Warn : target rp2350.dap.core1 examination failed
Info : starting gdb server for rp2350.dap.core0 on 3333
Info : Listening on port 3333 for gdb connections

Sin embargo NO consigo grabar en la flash. Uso este comando:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/39-debug-probe$ openocd -s tcl -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000" -c "program main.elf verify reset exit"
Open On-Chip Debugger 0.12.0+dev-gcf9c0b41c (2025-05-23-18:55)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : Hardware thread awareness created
cortex_m reset_config sysresetreq
adapter speed: 5000 kHz
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=E663AC91D31DA534
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: Atomic commands supported
Info : CMSIS-DAP: Test domain timer supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0
Info : CMSIS-DAP: Interface ready
Info : clock speed 5000 kHz
Info : SWD DPIDR 0x4c013477
Info : SWD DPIDR 0x4c013477
Error: Failed to read memory at 0xe000ed00
Error: [rp2350.dap.core0] Examination failed
Warn : target rp2350.dap.core0 examination failed
Info : SWD DPIDR 0x4c013477
Error: Failed to read memory at 0xe000ed00
Error: [rp2350.dap.core1] Examination failed
Warn : target rp2350.dap.core1 examination failed
Info : starting gdb server for rp2350.dap.core0 on 3333
Info : Listening on port 3333 for gdb connections
Info : SWD DPIDR 0x4c013477
Error: Failed to read memory at 0xe000ed00
Info : SWD DPIDR 0x4c013477
Error: Failed to read memory at 0xe000ed00
Info : SWD DPIDR 0x4c013477
Info : SWD DPIDR 0x4c013477
Info : [rp2350.dap.core0] AP write error, reset will not halt
Info : SWD DPIDR 0x4c013477
Error: [rp2350.dap.core0] DP initialisation failed
** Unable to reset target **
shutdown command invoked

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/39-debug-probe$ 

Estoy buscando información por internet... pero no encuentro nada...

Tengo que probar más cosas, pero no se me ocurre nada... Me gustaría aprender más sobre el protocolo de debug a bajo nivel

Interfaz SWD

SWD Significa Serial Wire Debug. Permite que otro dipositivo ejecute, pare o resetee los cores, así como inspeccionar el estado interno de los cores (registros), acceder a memoria como si fuese un core y cargar un programa y ejecutarlo. Me interesa mucho conocer este protocolo y empezar a frikear con él. Herramientas como openocd, gbd, etc... son super genérica y con muchas abstracciones. Necesito estudiar el funcionamiento desde el bajo nivel...

Hay información en el apartado 3.5 del Datasheet (Pag 84)

La información sobre el protocolo se encuentra en la Arm Debug Interface specification, version 6: https://developer.arm.com/documentation/ihi0074/latest/

El reloj parece que funciona a 5Mhz, pero no lo tengo claro. Si lo pruebo con la FPGA usaré un relog de 1Mhz.

El protocolo se llama ADI: Arm Debug Interface (versión 6)

DAP Es el Debug Access Port
DP: Debug Port
AP: Access Port

En el capítulo B4 de la especificación se especifica el Serial Wire Debug Port (SW-DP). En el B4.3 se especifica el nivel físico del interfaz SWD

2025-05-24

39-Debug-Probe

Voy a seguir intentando poner el depurador en marcha. Se me ha encendido la luz, y es posible que haya otros scripts tcl para el openocd relativos a la rp2350. Voy a echar un vistazo:

obijuan@JANEL:~/Develop/openocd/tcl/target$ ls rp*.cfg
rp2040.cfg  rp2350-dbgkey-nonsecure.cfg  rp2350-rescue.cfg
rp2350.cfg  rp2350-dbgkey-secure.cfg     rp2350-riscv.cfg
obijuan@JANEL:~/Develop/openocd/tcl/target$

Ahí es donde veo el script rp2350-riscv.cfg!! No tengo claro porqué no funciona, pero el programa que hay cargado actualmente es para el risc-v y es posible que al conectar el depurador el programa ya está en marcha, y los cores se han configurado para RISCV. Así que voy a probar a ver si hay suerte. NO hay nada de documentación sobre esto, por más que busco. Hay sobre la rp2040, y sobre la rp2350 pero con ARMs... para el RISCV hay muy muy poco

Primero lo ejecuto en modo servidor sin conectar el depurador. Esto es lo que obtengo:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/39-debug-probe$ openocd -s tcl -f interface/cmsis-dap.cfg -f target/rp2350-riscv.cfg -c "adapter speed 5000"
Open On-Chip Debugger 0.12.0+dev-gcf9c0b41c (2025-05-23-18:55)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
adapter speed: 5000 kHz
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Error: unable to find a matching CMSIS-DAP device

Error: [rp2350.dap.core0] Unsupported DTM version: -1
Error: [rp2350.dap.core0] Could not identify target type.
Error: [rp2350.dap.core1] Unsupported DTM version: -1
Error: [rp2350.dap.core1] Could not identify target type.
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/39-debug-probe$

Lo dejo aquí como referencia. Ahora conecto SOLO el depurador, sin enchufarlo a la pico2. También lo dejo aquí como referencia:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/39-debug-probe$ openocd -s tcl -f interface/cmsis-dap.cfg -f target/rp2350-riscv.cfg -c "adapter speed 5000"
Open On-Chip Debugger 0.12.0+dev-gcf9c0b41c (2025-05-23-18:55)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
adapter speed: 5000 kHz
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=E663AC91D31DA534
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: Atomic commands supported
Info : CMSIS-DAP: Test domain timer supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0
Info : CMSIS-DAP: Interface ready
Info : clock speed 5000 kHz
Error: Error connecting DP: cannot read IDR


Error: [rp2350.dap.core0] Unsupported DTM version: -1
Error: [rp2350.dap.core0] Could not identify target type.
Error: [rp2350.dap.core1] Unsupported DTM version: -1
Error: [rp2350.dap.core1] Could not identify target type.

Ahora conecto la pico2, a ver qué pasa... el momento de la verdad...

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/39-debug-probe$ openocd -s tcl -f interface/cmsis-dap.cfg -f target/rp2350-riscv.cfg -c "adapter speed 5000"
Open On-Chip Debugger 0.12.0+dev-gcf9c0b41c (2025-05-23-18:55)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
adapter speed: 5000 kHz
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=E663AC91D31DA534
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: Atomic commands supported
Info : CMSIS-DAP: Test domain timer supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0
Info : CMSIS-DAP: Interface ready
Info : clock speed 5000 kHz
Info : SWD DPIDR 0x4c013477
Info : [rp2350.dap.core0] datacount=1 progbufsize=2
Info : [rp2350.dap.core0] Disabling abstract command reads from CSRs.
Info : [rp2350.dap.core0] Disabling abstract command writes to CSRs.
Info : [rp2350.dap.core0] Core 0 could not be made part of halt group 1.
Info : [rp2350.dap.core0] Examined RISC-V core
Info : [rp2350.dap.core0]  XLEN=32, misa=0x40901105
Info : [rp2350.dap.core0] Examination succeed
Info : [rp2350.dap.core1] datacount=1 progbufsize=2
Info : [rp2350.dap.core1] Disabling abstract command reads from CSRs.
Info : [rp2350.dap.core1] Disabling abstract command writes to CSRs.
Info : [rp2350.dap.core1] Core 1 could not be made part of halt group 1.
Info : [rp2350.dap.core1] Examined RISC-V core
Info : [rp2350.dap.core1]  XLEN=32, misa=0x40901105
Info : [rp2350.dap.core1] Examination succeed
Info : starting gdb server for rp2350.dap.core0 on 3333
Info : Listening on port 3333 for gdb connections

PINTA MUY BIEN!!!! En la sonda están encendidos los 3 leds:

  • LED rojo: Es el de power
  • LED NARANJA: DAP_TARGET_RUNNING
  • LED VERDE: DAP_CONNECTED

Así es como está el escenario:

Es el momento de probar el gdb. El openocd lo dejamos lanzado. Ahora arrancamos el gdb y ejecutamos los comandos que se muestran en el manual:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/39-debug-probe$ gdb-multiarch main.elf 
GNU gdb (Ubuntu 15.0.50.20240403-0ubuntu1) 15.0.50.20240403-git
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from main.elf...
(gdb) target remote localhost:3333
Remote debugging using localhost:3333
getchar () at uart.s:139
139         li t0, UART0_UARTF
(gdb) monitor reset init
(gdb) continue
Continuing.

Al ejecutar target remote localhost:3333 ya no se produce un error, sino que se muestra la instrucción que está en la línea 139 de la uart.s (que es correcta!!!)

Tras ejecutar todos los comandos esto es lo que ha aparecido en la consola del openocd:

Info : accepting 'gdb' connection on tcp/3333
rp2350.dap.core0 halted due to debug-request.
rp2350.dap.core1 halted due to debug-request.
Info : RP2040 Flash Probe: 33554432 bytes @0x10000000, in 8192 sectors

Info : New GDB Connection: 1, Target rp2350.dap.core0, state: halted
Warn : Prefer GDB command "target extended-remote :3333" instead of "target remote :3333"

Parece que ha funcionado!!!! El core0 se ha detenido, y estamos en modo depuración!!!! Es el momento de aprender un poco sobre gdb, y sus comandos. Me imagino que desde el terminal del gdb podré ver la memoria, acceder a los registros, etc...

GDB

Voy a empezar a usar GDB con los ejemplos del libro sobre el ESP-C3. El log está aquí: https://github.com/Obijuan/Learn-RISCV-ESP32-C3/wiki

El primer comando es print: p. Se utiliza para imprimir expresiones. Si se usa el parámetro /x la impresión es en hexadecimal

(gdb) p 170
$1 = 170
(gdb) p /x 170
$2 = 0xaa
(gdb) p $2
$3 = 170
(gdb) 

Las expresiones impresas se asignan a variables ($1, $2...) que luego se pueden utilizar en otras expresiones

Cuando se ensambla con -g se añaden todos los símbolos en el elf, y se puede depurar. Con el gdb podemos listar el fichero fuente con el comando list:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/39-debug-probe$ gdb-multiarch main.elf 
GNU gdb (Ubuntu 15.0.50.20240403-0ubuntu1) 15.0.50.20240403-git
[...]
Reading symbols from main.elf...
(gdb) list
1       #----------------------------
2       #-- FUNCIONES DE INTERFAZ
3       #----------------------------
4       .global print_mstatus
5       .global print_misa
6       .global print_mie
7       .global print_mtvec
8       .global print_mip
9       .global print_mcause
10
(gdb) 

Grabando con el depurador

Antes de seguir con el gdb voy a probar la grabación con el openocd, a ver si ahora funciona

El comando a ejecutar es este:

openocd -s tcl -f interface/cmsis-dap.cfg -f target/rp2350-riscv.cfg -c "adapter speed 5000" -c "program main.elf verify reset exit"

Lo he metido en el script exec.sh

Probamos a grabar el ejemplo 39-debug-probe:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/39-debug-probe$ ./exec.sh 
Open On-Chip Debugger 0.12.0+dev-gcf9c0b41c (2025-05-23-18:55)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
adapter speed: 5000 kHz
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=E663AC91D31DA534
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: Atomic commands supported
Info : CMSIS-DAP: Test domain timer supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0
Info : CMSIS-DAP: Interface ready
Info : clock speed 5000 kHz
Info : SWD DPIDR 0x4c013477
Info : [rp2350.dap.core0] datacount=1 progbufsize=2
Info : [rp2350.dap.core0] Disabling abstract command reads from CSRs.
Info : [rp2350.dap.core0] Disabling abstract command writes to CSRs.
Info : [rp2350.dap.core0] Core 0 could not be made part of halt group 1.
Info : [rp2350.dap.core0] Examined RISC-V core
Info : [rp2350.dap.core0]  XLEN=32, misa=0x40901105
Info : [rp2350.dap.core0] Examination succeed
Info : [rp2350.dap.core1] datacount=1 progbufsize=2
Info : [rp2350.dap.core1] Disabling abstract command reads from CSRs.
Info : [rp2350.dap.core1] Disabling abstract command writes to CSRs.
Info : [rp2350.dap.core1] Core 1 could not be made part of halt group 1.
Info : [rp2350.dap.core1] Examined RISC-V core
Info : [rp2350.dap.core1]  XLEN=32, misa=0x40901105
Info : [rp2350.dap.core1] Examination succeed
Info : starting gdb server for rp2350.dap.core0 on 3333
Info : Listening on port 3333 for gdb connections
** Programming Started **
Info : RP2040 Flash Probe: 33554432 bytes @0x10000000, in 8192 sectors

Info : Padding image section 0 at 0x10001ff0 with 16 bytes (bank write end alignment)
** Programming Finished **
** Verify Started **
** Verified OK **
** Resetting Target **
shutdown command invoked
obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/39-debug-probe$ 

Wow!!! Es muy rápido y ya no hay que apretar el botón!!! Ahora es todo mucho más fácil!!!

40-GDB-addi (no terminado)

Voy a seguir haciendo pruebas con el GDB. Voy a usar los ejemplos del curso del laboratorio de arquitectura de computadores. Estoy interesado en poder simularlos directamente con el GDB, y después depurarlos con la sonda de raspberry

2025-05-25

40-GDB-addi

No consigo simular con el gdb...

voy a ir primero recopilando enlaces:

OK. He encontrado la manera de hacerlo en el último enlace. Es necesario utilizar QEMU. Se puede instalar muy fácilmente en ubuntu con el comando:

sudo apt install qemu-user

qemu-user: Es emulación en modo usuario, de sistemas pequeños

También se puede instalar qemu-system, que emula un sistema completo, con entrada/salida, sistema operativo, etc...

sudo apt install qemu-system

Yo voy a usar qemu-user

Lanzamos el ejecutable con Qemu-user, indicando que la depuración la haremos desde el puerto 3333:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/40-gdb-addi$ qemu-riscv32 -g 3333 main.elf

Desde otro terminal lanzamos el gdb:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/40-gdb-addi$ $HOME/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-gdb main.elf 
GNU gdb ('corev-openhw-gcc-ubuntu2204-20240530') 15.0.50.20240416-git
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=riscv32-corev-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<'https://www.embecosm.com'>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from main.elf...
(gdb) 

Ahora nos conectamos al programa que se está ejecutando en qemu:

(gdb) target remote :3333
Remote debugging using :3333
_start () at main.s:9
9           addi x3, x0, 30   #-- x3 = 0 + 30 = 30
(gdb)

Desensamblamos el código:

Dump of assembler code for function _start:
=> 0x10000020 <+0>:     li      gp,30
   0x10000024 <+4>:     j       0x10000024 <_start+4>
End of assembler dump.
(gdb)

La flecha nos indica el valor actual del PC. Que también lo podemos ver imprimiendo el valor del registro $pc:

(gdb) print $pc
$1 = (void (*)()) 0x10000020 <_start>
(gdb)

Vamos a ver el contenido de todos los registros:

(gdb) info reg
ra             0x0      0x0
sp             0x2b2aa9a0       0x2b2aa9a0
gp             0x0      0x0
tp             0x0      0x0
t0             0x0      0
t1             0x0      0
t2             0x0      0
fp             0x0      0x0
s1             0x0      0
a0             0x0      0
a1             0x0      0
a2             0x0      0
a3             0x0      0
a4             0x0      0
a5             0x0      0
a6             0x0      0
a7             0x0      0
s2             0x0      0
s3             0x0      0
s4             0x0      0
s5             0x0      0
s6             0x0      0
s7             0x0      0
s8             0x0      0
s9             0x0      0
s10            0x0      0
s11            0x0      0
t3             0x0      0
t4             0x0      0
t5             0x0      0
t6             0x0      0
pc             0x10000020       0x10000020 <_start>

Los registros se muestran con sus nombres ABI... ¿Sería posible verlos con la nomenclatura x?

Parece que no... Bueno... da igual.. tampoco es muy importante

El ejemplo que voy a simular es el clásico del contador:

.global _start   #-- Punto de entrada

.section .text

# -- Punto de entrada
_start:

    #-- Inicializar contador
    li t0, 0

loop:
    #-- Incrementar contador
    addi t0, t0, 1

    j loop 

Ejecutamos el comando ./sim.sh que es un script que lanza el Qemu y luego el GDB:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/40-gdb-addi$ ./sim.sh 
GNU gdb ('corev-openhw-gcc-ubuntu2204-20240530') 15.0.50.20240416-git
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=riscv32-corev-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<'https://www.embecosm.com'>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from main.elf...
(gdb)

Primero nos conectamos al qemu con el comando:

(gdb) target remote :3333
Remote debugging using :3333
_start () at main.s:9
9           li t0, 0
(gdb) 

Mirando desde el VSCode los números de línea, establecemos un breakpoint en la instrucción j .

(gdb) b 15
Breakpoint 1 at 0x10000028: file main.s, line 15.

Ahora mostramos la instrucción apuntada por el PC:

(gdb) disass $pc
Dump of assembler code for function _start:
=> 0x10000020 <+0>:     li      t0,0
End of assembler dump.

Damos un paso

(gdb) si
loop () at main.s:13
13          addi t0, t0, 1

Ahora el comando continue (c). Se para en el breakpoint. Observamos el valor de t0

(gdb) c
Continuing.

Breakpoint 1, loop () at main.s:15
15          j loop 
(gdb) info reg t0
t0             0x1      1

Para que nos muestre el valor del registro t0 cada vez que se detiene ejecutamos este comando:

(gdb) display $t0
1: $t0 = 1

Ahora le camos a continuar varias veces:

(gdb) c
Continuing.

Breakpoint 1, loop () at main.s:15
15          j loop 
1: $t0 = 2
(gdb) c
Continuing.

Breakpoint 1, loop () at main.s:15
15          j loop 
1: $t0 = 3
(gdb) c
Continuing.

Breakpoint 1, loop () at main.s:15
15          j loop 
1: $t0 = 4
(gdb)

Vemos cómo el contador de t0 va aumentando...

Si queremos reiniciar la simulación no hay más remedio que salir del gdb (q) y volver a ejecutar sim.sh

Ya simulamos riscv sin el RARs!!!!!! Lo que sería aluciante es poder hacer esto pero desde el VSCode...

Simulando con VSCode

Voy a ver este vídeo: https://www.youtube.com/watch?v=NbZDowmXzZs

Ahí lo tienen funcionando... pero lo que no funciona es poner los breakpoint en ficheros ensamblador. He encontrado una solución aquí:

https://ece362-purdue.github.io/f2022-public-labs/lab-08/lab_08_0.html

Siii!! Ya funciona! He tenido que instalar la extensión Native Debug. En el enlace instalan una modificación de esta, pero yo he instalado la oficial y ha funcionado

El repositorio asociado al video de youtube es este: https://github.com/chuckb/riscv-helloworld-c

La manera de usarlo es la siguiente:

  1. Seleccionar el archivo main.s en el editor
  2. Establecer breakpoints. Aunque no haya ninguno puesto, siempre para en la primera intrucción
  3. Ir a la depuración y ejecutar DEBUG RISC-V
  4. Ya podemos depurar con normalidad:

En la parte de la izquierda abrimos lo registros y le damos a continuar para que alcance el Breakpoint:

El registro t0 vale 1. Le damos a continuar un cuantas veces más. Vemos cómo el registro t0 se ha incrementado:

Ya podemos simular ficheros ensamblador de riscv en el VisualCode!!! ¡¡Vaaaaamos!!!

Ahora el script build.sh se ejecuta al pulsar Ctrl+Shift+B. Lo he metido como una tarea en tasks.json

Así es como han quedado los archivos de configuración:

  • Tasks.json:
{
    "version": "2.0.0",
    "tasks": [
        {
          "label": "Run QEMU",
          "type": "shell",
          "command": "echo 'QEMU started'; qemu-riscv32 -g 3333 ${workspaceFolder}/${relativeFileDirname}/main.elf",
          "isBackground": true,
          "problemMatcher": [
            {
              "pattern": [
                {
                  "regexp": ".",
                  "file": 1,
                  "location": 2,
                  "message": 3
								}
							],
              "background": {
                "activeOnStart": true,
                "beginsPattern": ".",
                "endsPattern": "QEMU started",
							}
						}
					]
				},
        {
            "label": "build.sh",
            "type": "shell",                     // Indica que es un comando de shell
            "command": "./build.sh",             // El comando a ejecutar
            "options": {
                "cwd": "${workspaceFolder}/${relativeFileDirname}" // Cambia el directorio de trabajo a la subcarpeta 'scripts'
            },
            "group": {
                "kind": "build",                 // Clasifica esta tarea como una tarea de "build"
                "isDefault": true                // La convierte en la tarea de build predeterminada
            },
            "presentation": {
                "reveal": "always",              // Muestra el terminal de la tarea siempre
                "panel": "new"                   // Abre un nuevo panel de terminal para esta tarea
            },
            "problemMatcher": ["$gcc"]
        }
		]
}
  • launch.json:
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug RISC-V",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/${relativeFileDirname}/main.elf",
            "cwd": "${workspaceFolder}/${relativeFileDirname}",
            "miDebuggerPath": "/home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-gdb",
            "miDebuggerServerAddress": "localhost:3333",
            "stopAtEntry": true,
            "preLaunchTask": "Run QEMU"
        }		
    ]
}

Depurando en la pico2

Voy a probar a depurar este mismo programa pero en la pico2, en vez del simulador. Lo primero que hacemos es lanzar el openOCD, que es el software que se comunica con la sonda. Lo dejamos en modo servidor, y se queda a la espera de conectarse con el GDB

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/40-gdb-addi$ openocd -s tcl -f interface/cmsis-dap.cfg -f target/rp2350-riscv.cfg -c "adapter speed 5000"
Open On-Chip Debugger 0.12.0+dev-gcf9c0b41c (2025-05-23-18:55)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
adapter speed: 5000 kHz
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=E663AC91D31DA534
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: Atomic commands supported
Info : CMSIS-DAP: Test domain timer supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0
Info : CMSIS-DAP: Interface ready
Info : clock speed 5000 kHz
Info : SWD DPIDR 0x4c013477
Info : [rp2350.dap.core0] datacount=1 progbufsize=2
Info : [rp2350.dap.core0] Disabling abstract command reads from CSRs.
Info : [rp2350.dap.core0] Disabling abstract command writes to CSRs.
Info : [rp2350.dap.core0] Core 0 could not be made part of halt group 1.
Info : [rp2350.dap.core0] Examined RISC-V core
Info : [rp2350.dap.core0]  XLEN=32, misa=0x40901105
Info : [rp2350.dap.core0] Examination succeed
Info : [rp2350.dap.core1] datacount=1 progbufsize=2
Info : [rp2350.dap.core1] Disabling abstract command reads from CSRs.
Info : [rp2350.dap.core1] Disabling abstract command writes to CSRs.
Info : [rp2350.dap.core1] Core 1 could not be made part of halt group 1.
Info : [rp2350.dap.core1] Examined RISC-V core
Info : [rp2350.dap.core1]  XLEN=32, misa=0x40901105
Info : [rp2350.dap.core1] Examination succeed
Info : starting gdb server for rp2350.dap.core0 on 3333
Info : Listening on port 3333 for gdb connections

Ahora lanzamos el gdb para conectarnos al openocd:

obijuan@JANEL:~/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/40-gdb-addi$ /home/obijuan/Develop/pico/corev-openhw-gcc-ubuntu2204-20240530/bin/riscv32-corev-elf-gdb -q -ex 'target remote :3333' main.elf
Reading symbols from main.elf...
Remote debugging using :3333
loop () at main.s:13
13          addi t0, t0, 1
(gdb) 

Ponemos un breakpont al comienzo y lanzamos la ejecución:

(gdb) b _start
Breakpoint 1 at 0x10000020: file main.s, line 9.
Note: automatically using hardware breakpoints for read-only addresses.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/obijuan/Develop/Learn-raspberry-pico2/wiki/Log/Ejemplos/40-gdb-addi/main.elf 
[New Thread 1]
[Switching to Thread 1]

Thread 2 "rp2350.dap.core0" hit Breakpoint 1, _start () at main.s:9
9           li t0, 0
(gdb)

Ahora ponemos otro breakpoint al final del bucle y le damos a continuar

(gdb) b 15
Breakpoint 2 at 0x10000028: file main.s, line 15.
(gdb) c
Continuing.

Thread 2 "rp2350.dap.core0" hit Breakpoint 2, loop () at main.s:15
15          j loop 
(gdb) info reg t0
t0             0x1      1
(gdb)

El regitro t0 vale 1. Le damos a continuar varias veces, y vemos cómo t0 efectivamente se incrementa

(gdb) c
Continuing.

Thread 2 "rp2350.dap.core0" hit Breakpoint 2, loop () at main.s:15
15          j loop 
(gdb) c
Continuing.

Thread 2 "rp2350.dap.core0" hit Breakpoint 2, loop () at main.s:15
15          j loop 
(gdb) info reg t0
t0             0x3      3
(gdb)

¡¡Ya puedo depurar "dentro" del chip!! ¡¡Vaaaaaamos!!!

Depurando en la pico2 desde VSCode

Si hago lo mismo que con la simulación, no consigo que funcione con el vscode. Tengo que buscar más información sobre el tema

Voy a instalar la extensión oficial de pico2: The official VS Code extension for Raspberry Pi Pico development. It includes several features to simplify project creation and deployment. (Single folder workspaces only)

Voy a buscar en el github de la extensión, a ver si veo ficheros launch.json: https://github.com/raspberrypi/pico-vscode

No encuentro nada...

Voy a leer este post para poner en marcha la extensión de VSCode

https://www.raspberrypi.com/news/get-started-with-raspberry-pi-pico-series-and-vs-code/

He creado el ejemplo del Blink...

La verdad es que se pone en marcha muy rápidamente... y todo ha ido bien... se puede depurar paso a paso... PERO no se pueden ver los registros desde la parte de depuración... Sí que se pueden ver desde la consola de depuración, con los comandos de gdb correspondinentes

Lo bueno es que sí que puedo ver el launch.json. Voy a analizarlo a ver saco algo en claro

Así se lanza el openocd:

openocd -c "gdb_port 50000" -c "tcl_port 50001" -c "telnet_port 50002" -s /home/obijuan/.pico-sdk/openocd/0.12.0+dev/scripts -f /home/obijuan/.vscode/extensions/marus25.cortex-debug-1.12.1/support/openocd-helpers.tcl -f interface/cmsis-dap.cfg -f target/rp2350-riscv.cfg -c "adapter speed 5000"

Y así es como se lanza el GDB

riscv32-unknown-elf-gdb -q --interpreter=mi2

OK... ya tengo una primera versión, que he llamado Test (de momento), que me permite depurar. Aunque con la limitación de no ver los registros en el depurador

{
            "name": "Test",
            "cwd": "${workspaceFolder}/${relativeFileDirname}",
            "executable": "main.elf",
            "request": "launch",
            "type": "cortex-debug",
            "servertype": "openocd",
            //"serverpath": "openocd",
            "gdbPath": "/home/obijuan/.pico-sdk/toolchain/RISCV_RPI_2_0_0_5/bin/riscv32-unknown-elf-gdb",
            "device": "rp2350",
            "configFiles": [
                "interface/cmsis-dap.cfg",
                "target/rp2350-riscv.cfg"
            ],
            "svdFile": "${userHome}/.pico-sdk/sdk/2.1.1/src/rp2350/hardware_regs/RP2350.svd",
            "runToEntryPoint": "_start",
            // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected
            // Also works fine for flash binaries
            "overrideLaunchCommands": [
                "monitor reset init",
                "load main.elf"
            ],
            "openOCDLaunchCommands": [
                "adapter speed 5000"
            ]
        },	

2025-05-26

41-GDB-blinky

Voy a hacer el programa Blinky con la idea de simularlo en qemu y en la pico2, y así documentar y probar todos los scripts que estoy haciendo

He hecho una primera versión del blinky, que sólo configura el LED y lo enciende. Claro, al probarlo en el simulador PETA: se produce un segmentation fault

Esto es debido a que los LEDs están en una región de memoria en la que no hay permisos de escritura. La pregunta ahora es: ¿Cuál es el mapa de memoria? He estado googleando pero no encuentro nada. Parece que para definir el mapa de memoria y perfiéricos hay que usar qemu-system-riscv32. Pero de momento no lo voy a cambiar

Mirando la documentación de qemu-riscv32 he visto que con el parámetro -d page se vuelcan las direcciones de memoria asignadas. Así que lo voy a añadirlo a la ejecución de qemu en el fichero tasks.json:

{
   "label": "Run QEMU",
   "type": "shell",
   "command": "echo 'QEMU started'; qemu-riscv32 -g 3333 -d page ${workspaceFolder}/${relativeFileDirname}/main.elf",
//--...
}

Al comenzar la simulación en la parte de la derecha vemos que tras arrancar QEMU aparece información sobre la memoria

Nos fijamos que hay una región donde la memoria tiene permisos de lectura y escritura. Comienza en: 0x2aaac000

Vamos a hacer un mini-programa para comprobar que efectivamente podemos escribir en esa dirección:

.global _start   #-- Punto de entrada

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top

main:

    #-- Simulacion: Comprobar si podemos escribir un valor en la memoria
    li t0, 0x2aaac000
    li t1, 0xCAFEBACA
    sw t1, 0(t0)

    j .

Lo simulamos:

Y comprobamos que efectivamente en esa dirección podemos escribir

Este es el programa que vamos a simular:

.global _start   #-- Punto de entrada

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top

main:

    #-- Simulacion: Comprobar si podemos escribir un valor en la memoria
    li t0, 0x2aaac000
    li t1, 0xCAFEBACA
    sw t1, 0(t0)

    #-- Configurar perifericos
    jal sim_led_init

loop:
    jal sim_led_on
    jal sim_led_off
    j loop

Las funciones que comienzan por sim_ son iguales que las normales, PERO que escriben en una posición de memoria con permisos, para que no pete. Este programa, al simularlo, no hace nada, claro... no tenemos leds... pero simula perfectamente

Para enviar comando directamente al gdb hay que poner primero la orden -exec. Así, por ejemplo, para ver el registro t0 pondríamos esto: -exec info reg t0

Para ver una dirección de memoria pondríamos esto:

-exec x/1wx 0x0x2aaac000

Para escribir un valor en la dirección se hace con:

-exec set *(unsigned int*)0x2aaac000 = 0xcafebaca

Ahora vamos a probar este mismo programa pero en hardware real, depurándolo

Si lo cargamos directamente en la pico2 sólo vemos que el LED está encendido. Es porque NO hemos metido pausas entre el encendido y apagado del LED

Lo depuramos. Si vamos paso a paso vemos cómo efectivamente el LED se enciende y se apaga... Esto es muy poderoso!!!

El programa que estamos usando es este:

.global _start   #-- Punto de entrada

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top

main:

    #-- Configurar perifericos
    jal led_init

loop:
    jal led_on
    jal led_off
    j loop

En este pantallazo se muestra la depuración justo tras encender el LED:

Lo interesante es que con el gdb podemos cambiar también el LED, escribiendo/lenyendo de la dirección 0xD0000010 (GPIO_OUT)

Con este comando se enciende el LED:

set *(unsigned int *)0xd0000010 = -1

Y con este se apaga:

set *(unsigned int *)0xd0000010 = 0

También lo podemos leer con:

x/1wx 0xd0000010

42-user-uart-putchar

Vamos ahora a intentar resolver el problema de transmitir un carácter por el puerto serie en modo usuario

Priimero ponemos en marcha este programa, que enciende el LED, imprime el mensaje "HOLA" en rojo y lo reimprime con cada pulsación de la tecla Como he instalado el plugin de la pico2, TENGO UN TERMINAL SERIE INTEGRADO EN VSCODE!! Es la caña!! Esto va a facilitar mucho más las cosas

Ahora pasamos a modo usuario, como ya sabemos. El LED se debería encender sin problemas. Vamos a comprobarlo

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    CLS

    #-- Establecer permisos para acceder al LED
    #-- desde el modo usuario
    li t0, ACCESSCTRL_GPIO_NSMASK0
    li t1, BIT25
    sw t1, 0(t0) 

    #-- Configurar los permisos de acceso a la flash
    li t0, 0x5FFFFFFF
    csrw pmpaddr0, t0

    li t1, 0x1F
    csrw pmpcfg0, t1

    #------- PASAR A MODO USUARIO

    #-- Poner a 0 bits del campo MPP
    #-- Para que el modo de privilegio sea de usuario
    csrw mstatus, zero

    #-- Meter en mepc la direccion de retorno
    la t0, user_mode_entry
    csrw mepc, t0

    #-- Saltar a modo usuario
    mret 


#------------------------------
# Código en modo usuario
#------------------------------
user_mode_entry:

    #-- Encender el LED desde 
    #-- el modo usuario
    jal led_on
    j .

#------------------------------------------
#-- Rutina de atencion a la interrupcion 
#------------------------------------------
isr:
    jal print_mstatus
    jal print_mcause

    jal led_blinky3

Funciona todo perfecto. Voy a depurarlo para familiarizarme con ello y aprender algunas cosas más por el camino, como por ejemplo cómo mostrar la información de los registros csr

Para programar la pico2 con el depurador (grabar un ejecutable) he creado el atajo Ctrl+shift+9 (que estaba libre). Hay que tener el foco en el fichero main, pulsar el atajo y se graba directamente... ¡Muy rápido!. He tenido que editar el fichero keybindings.json buscándolo desde el panel (Ctrl+Shift+p). El fichero NO se guarda en el proyecto actual... (no se donde está...)

He añadido la impresión de un carácter:

# [...]
#------------------------------
# Código en modo usuario
#------------------------------
user_mode_entry:

    #-- Encender el LED desde 
    #-- el modo usuario
    jal led_on

    #-- Enviar un carácter por la UART
    li a0, 'H'  #-- Carácter a enviar
    jal putchar
[...]

Y como era de esperar, se ejecuta la excepción

MSTATUS: 0x00000000
  TW:   0  (Timeout Wait)
  MPRV: 0  (Modify PRiVilegde)
  MPP:  00 (Previous Privilegde level)
  MPIE: 0  (Previous Interrupt Enable)
  MIE:  0  (Interrupt enable)
MCAUSE: 0x00000005
  INTERRUPT: 0  (Exception(0)/Interrupt(1))
  CODE:      5  (Cause of the trap)

Voy a ejecutarlo paso a paso para ver exactamente donde peta...

Exactamente al leer el flag de status de la UART:

 li t0, UART0_UARTF
 lw t1, 0(t0)  

Siguiendo el manual, para acceder a la uart0 hay que escribir algo como esto:

#-- Establecer permisos para acceder a la UART 
    li t0, ACCESSCTRL_UART0
    li t1, 0x3
    sw t1, 0(t0)

Sin embargo, al hacerlo (en modo privilegiado) se produce otra excepción, esta vez de tipo 7:

MSTATUS: 0x00001800
  TW:   0  (Timeout Wait)
  MPRV: 0  (Modify PRiVilegde)
  MPP:  11 (Previous Privilegde level)
  MPIE: 0  (Previous Interrupt Enable)
  MIE:  0  (Interrupt enable)
MCAUSE: 0x00000007
  INTERRUPT: 0  (Exception(0)/Interrupt(1))
  CODE:      7  (Cause of the trap)

Según leo en el manual esta es la causa:

0x7 → STORE_FAULT: Store/AMO access fault. A store/AMO failed a PMP
check, or encountered a downstream bus error. Also set if an AMO is
attempted on a region that does not support atomics (on RP2350, anything
but SRAM).

Vamos, que es un error en el store por NO TENER permisos...

Antes de que se produzca el trap leemos el registro de acceso de la uart:

x/1wx 0x400600a0
0x400600a0:	0x000000fc

Tiene el valor 0xfc... yo estoy escribiendo el 0x03... ¿Qué tal si escribo 0xff?

NADA... joder.... no funciona... su puta madre.... Voy a leer más en el datasheet a ver si descubro algo

¡¡¡VAAAAMOS!!! Ya he conseguido establecer los bits de ACCESSCTRL_UART0. En el datasheet dice que en todos los registros SALVO los GPIOS, hay que escribir en los 16 bits de mayor peso el valor 0xacce... y es que JODER... esto está lleno de TRAMPAS!!

Este código funciona

#-- Establecer permisos para acceder a la UART 
    li t0, ACCESSCTRL_UART0
    li t1, 0xacce00ff
    sw t1, 0(t0)

Todavía no he probado si se envía algo por la uart o no... de momento he solventado este primer error. Voy a depurar a ver qué pasa ahora con la UART...

SIIIIIIIIIIIIIIIIIIIIIIIIIIII Se imprime la H en el terminal.....
Voy a probar fuera del modo debug...

SIIII!!! Estupendo!!!

43-user-uart

Este es el mismo ejemplo anterior, pero ahora se imprimen más caracteres y se reciben, a ver si todo funciona bien

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal led_off
    jal uart_init

    CLS

    #-- Configurar los permisos de acceso a la flash
    li t0, 0x5FFFFFFF
    csrw pmpaddr0, t0

    li t1, 0x1F
    csrw pmpcfg0, t1

    #-- Establecer permisos para acceder al LED
    #-- desde el modo usuario
    li t0, ACCESSCTRL_GPIO_NSMASK0
    li t1, BIT25
    sw t1, 0(t0) 

    #-- Establecer permisos para acceder a la UART 
    li t0, ACCESSCTRL_UART0
    li t1, 0xacce00ff
    sw t1, 0(t0)

    

    #------- PASAR A MODO USUARIO

    #-- Poner a 0 bits del campo MPP
    #-- Para que el modo de privilegio sea de usuario
    csrw mstatus, zero

    #-- Meter en mepc la direccion de retorno
    la t0, user_mode_entry
    csrw mepc, t0

    #-- Saltar a modo usuario
    mret 


#------------------------------
# Código en modo usuario
#------------------------------
user_mode_entry:

    #-- Encender el LED desde 
    #-- el modo usuario
    jal led_on


loop:
    PRINT "HOLA!\n"
    jal  getchar

    j loop

#------------------------------------------
#-- Rutina de atencion a la interrupcion 
#------------------------------------------
isr:
    jal print_mstatus
    jal print_mcause

    jal led_blinky3

En la consola vemos las cadenas "HOLA!", y se reciben correctamente las teclas pulsadas

43-Monitorv-traps

Esta es una nueva versión del programa que imprime las TRAPs que ocurren. Ya están cubiertas TODAS las excepciones. Se ha dividido en el programa principal y la aplicación monitorv-trap, que está en otro fichero

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr_monitor
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal led_off
    jal uart_init

    #-------- Configurar los permisos para el modo usuario -----------
    #-- ACCESO A LA MEMORIA FLASH desde modo usuario
    li t0, 0x5FFFFFFF
    csrw pmpaddr0, t0

    li t1, 0x1F
    csrw pmpcfg0, t1

    #--- ACCESO A LOS GPIOS desde modo usuario
    li t0, ACCESSCTRL_GPIO_NSMASK0
    li t1, BIT25
    sw t1, 0(t0) 

    #--- ACCESO A LA UART desde modo usuario
    li t0, ACCESSCTRL_UART0
    li t1, 0xacce00ff
    sw t1, 0(t0)

    #-- Ejecutar la aplicacion
    j monitorv_trap

45-mtime

Voy a estudiar el temporizador del RISC-V. Este enlace que he encontrado está MUY BIEN: https://danielmangum.com/posts/risc-v-bytes-timer-interrupts/

El temporizador del RISC-V NO ESTÁ ACCESIBLE MEDIANTE REGISTROS CSR, sino que está mapeado en memoria. Y por tanto NO está estandarizado, sino que depende de la implementación. En el caso de la placa pico2 está mapeado en el SIO

He hecho un programa para leer el TIMER MTIME... pero siempre se lee 00000000. ¿Habrá algún bit por ahí de start/stop?

He visto que en el registro MTIME_CTRL sí que hay un bit de enable... pero ¡Está activo en el reset!! Lo voy a imprimir a ver....

Leo el registro y se obtiene 0xD... el bit enable está a '1'!!!! Mierda....

He probado a escribir 0x1 en MTIME_CTRL, pero nada... no funciona.... Joder... no consigo que vaya...

Nada... joder... no consigo que vaya....

SIIIII!! JODER!! Ya lo tengo!! He tenido que activar el Bit 1 (FULLSPEED). Ahora se actualiza el temporizador en cada ciclo (que creo que es el comportamiento normal)

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr_monitor
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    #-- Encender LED
    jal led_on

    CLS

    #--- Activar el temporizador del RISCV
    #--- Que se actualice en cada ciclo
    li t0, MTIME_CTRL
    li t1, 0x3
    sw t1, 0(t0)

loop:

    #-- Leer temporizador del RISCV
    PRINT "Timer L: "
    li t0, MTIME
    lw a0, 0(t0)
    jal print_hex32
    NL

    #-- Esperar (espera activa)
    jal delay

    #-- Repetir 
    j loop

2025-05-27

45-mtime

Antes de activar las interrupciones vamos a comprobar qué pasa con el flag MIP.MTIP que indica que hay una interrupción pendiente del temporizador. Usamos este programa de prueba:.

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr_monitor
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    #-- Apagar LED
    jal led_off

    CLS

    #--- Activar el temporizador del RISCV
    #--- Que se actualice en cada ciclo
    li t0, MTIME_CTRL
    li t1, 0x3
    sw t1, 0(t0)

    #-- Configurar el comparador
    li t0, MTIMECMPH
    sw zero, 0(t0)
    li t0, MTIMECMP
    li t1, 0x40000000
    sw t1, 0(t0)

    #-- Activar las interrupciones del temporizador
    li t0, MIE_MTIE
    csrs mie, t0


    #-- Activar las interrupciones globales
    #li t0, MSTATUS_MIE
    #csrs mstatus, t0

loop:

     #-- Leer temporizador del RISCV
    PRINT "Timer: "
    li t0, MTIMEH
    lw a0, 0(t0)
    jal print_hex32
    PRINT "-"
    li t0, MTIME
    lw a0, 0(t0)
    jal print_hex32
    NL

    #-- Imprimir el comparador
    PRINT "CMP:   "
    li t0, MTIMECMPH
    lw a0, 0(t0)
    jal print_hex32
    PRINT "-"
    li t0, MTIMECMP
    lw a0, 0(t0)
    jal print_hex32
    NL

    jal print_mip
    NL
    NL

    #-- Esperar (espera activa)
    jal delay
    jal delay

    #-- Repetir 
    j loop

Este es el resultado. El contador se va incrementando y cuando supera al comparador, se activa el bit MTIP. No salta la interrupción porque no están habilitadas las globales todavía (MSTATUS.MIE)

Timer: 00000000-00032C93
CMP:   00000000-40000000
MEIP: 0x00000000  (External interrupt pending)
  MEIE:  0 (External interrupt enable)
  MTIP:  0 (Timer interrupt pending)
  MSIP:  0 (Software interrupt pending)

[...]

Timer: 00000000-39A0145F
CMP:   00000000-40000000
MEIP: 0x00000000  (External interrupt pending)
  MEIE:  0 (External interrupt enable)
  MTIP:  0 (Timer interrupt pending)
  MSIP:  0 (Software interrupt pending)


Timer: 00000000-41DB10D6
CMP:   00000000-40000000
MEIP: 0x00000080  (External interrupt pending)
  MEIE:  0 (External interrupt enable)
  MTIP:  1 (Timer interrupt pending)
  MSIP:  0 (Software interrupt pending)


Timer: 00000000-4A160D4C
CMP:   00000000-40000000
MEIP: 0x00000080  (External interrupt pending)
  MEIE:  0 (External interrupt enable)
  MTIP:  1 (Timer interrupt pending)
  MSIP:  0 (Software interrupt pending)

El flag salta!! Bien!!! Ahora es el momento de la verdad... vamos a hacer que salte la interrupción y que se encienda el LED (y que se quede en bucle infinito dentro de la interrupción)

46-mtime-test2

Ya está funcionando el timer del riscv al 100%. En este ejemplo se producen interrupciones periódicas cuando transcurren 0x2000_0000 ticks del temporizador. Cada vez que se produce una interrupción se incrementa el comparador en la cantidad 0x2000_0000. La dificultad reside en la implementación de la suma de 64bits

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    #-- Apagar LED
    jal led_off

    CLS

    #--- Activar el temporizador del RISCV
    #--- Que se actualice en cada ciclo
    li t0, MTIME_CTRL
    li t1, 0x3
    sw t1, 0(t0)

    #-- Configurar el comparador
    jal inc_timer 

    COLOR WHITE
    PRINT "Temporizador lanzado!\n\n"

    #-- Activar las interrupciones del temporizador
    li t0, MIE_MTIE
    csrs mie, t0

    #-- Activar las interrupciones globales
    li t0, MSTATUS_MIE
    csrs mstatus, t0

loop:

    jal print_mtimer
    NL

    #-- Esperar (espera activa)
    jal delay
    jal delay

    #-- Repetir 
    j loop


#-------------------------------------------------
#-- Sumar dos numeros de 64 bits
#-------------------------------------------------
#-- ENTRADAS:
#--  -Primer numero:
#--     a1: Parte alta
#--     a0: Parte baja
#--  -Segundo numero:
#--     a3: Parte alta
#--     a2: Parte baja
#-- SALIDA:
#--  - a1: Parte alta
#--  - a0: Parte baja
#--------------------------------------------------
add64:
    #-- Sumar bytes de menor peso (a0 + a2)
    add t0, a0, a2

    #-- Calcular el acarreo
    sltu t2, t0, a0  #-- Si t0 < a0, hay acarreo

    #-- Sumar la parte alta
    add t1, a1, a3

    #-- Sumar el acarreo
    add t1, t1, t2

    #-- Devolver resultado
    mv a1, t1
    mv a0, t0

    #-- Terminar
    ret

inc_timer:
FUNC_START4
    #-------------------------------------------------
    #-- Incrementar el comparador
    #-- Comparador = Timer + 0x20000000
    #-- La lectura del timer se hace como se indica  
    #-- en la sección 3.1.8 del Datasheet (Pag. 43)
    #-------------------------------------------------

_read_timer:
    #-- La lectura se hace en 4 pasos:
    #-- 1. Leer la parte alta del timer
    li t1, MTIMEH  #-- Direccion del timer alto
    lw a1, 0(t1)  #-- Leer parte alta del timer

    #-- 2. Leer la parte baja
    li t0, MTIME  #-- Direccion del timer
    lw a0, 0(t0)  #-- Leer el timer (Parte baja)

    #-- 3. Leer la parte alta de nuevo
    lw a2, 0(t1)

    #-- 4. Loop if the two upper-half reads returned different values
    bne a1, a2, _read_timer  #-- Si las dos lecturas no son iguales, repetir
    
    #-- a1, a0 contienen el valor del timer
    
    #-- Preparar el incremento a sumar
    li a3, 0x0  #-- Parte alta
    li a2, 0x20000000  #-- Parte baja
    
    #-- Incrementar el timer
    jal add64
   
    #-- Actualizar el comparador

    #-- Tenemos el valor en t1, t0
    #-- Guardarlo en el comparador
    li t0, MTIMECMPH  #-- Direccion del comparador alto
    sw a1, 0(t0)  #-- Escribir la parte alta del comparador
    li t0, MTIMECMP  #-- Direccion del comparador bajo
    sw a0, 0(t0)  #-- Escribir la parte baja del comparador

FUNC_END4


print_mtimer:
FUNC_START4
    #-- Leer temporizador del RISCV
    PRINT "Timer: "
    li t0, MTIMEH
    lw a0, 0(t0)
    jal print_hex32
    PRINT "-"
    li t0, MTIME
    lw a0, 0(t0)
    jal print_hex32
    NL

    #-- Imprimir el comparador
    PRINT "CMP:   "
    li t0, MTIMECMPH
    lw a0, 0(t0)
    jal print_hex32
    PRINT "-"
    li t0, MTIMECMP
    lw a0, 0(t0)
    jal print_hex32
    NL
FUNC_END4


#--------------------------------------------------------------
#-- Rutina de servicio de interrupcion
#--------------------------------------------------------------
#-- Se supone que solo llega la interrupción del temporizador
#--------------------------------------------------------------
isr:
    SAVE_CONTEXT

    jal led_toggle
    CPRINT RED, "ISR: Temporizador!\n"
    COLOR WHITE

    jal inc_timer

    RESTORE_CONTEXT
    mret    

Este es el resultado. Cada pocos segundos se genera la interrupción y se imprime el mensaje correspondiente en la consola. También se cambia el estado del LED

Temporizador lanzado!

Timer: 00000000-0009222F
CMP:   00000000-20000149

Timer: 00000000-0812DA5A
CMP:   00000000-20000149

Timer: 00000000-101C91B3
CMP:   00000000-20000149

Timer: 00000000-1826490D
CMP:   00000000-20000149

ISR: Temporizador!
Timer: 00000000-20362EC1
CMP:   00000000-40062B1F

Timer: 00000000-283FE619
CMP:   00000000-40062B1F

Timer: 00000000-30499D71
CMP:   00000000-40062B1F

Timer: 00000000-385354C2
CMP:   00000000-40062B1F

ISR: Temporizador!
Timer: 00000000-406330EE
CMP:   00000000-600C4F33

Timer: 00000000-486CE846
CMP:   00000000-600C4F33

Timer: 00000000-50769F9E
CMP:   00000000-600C4F33

Timer: 00000000-588056F6
CMP:   00000000-600C4F33

ISR: Temporizador!
Timer: 00000000-609032CE
CMP:   00000000-80127369

Timer: 00000000-6899EA28

47-monitorv-trap

Integramos la interrupción del timer en la rutina de atención a la interrupción del monitorv-trap

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr_monitor
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    #-- Apagar LED
    jal led_off

    #-- Ejecutar la aplicacion
    j monitorv_trap

Este es el menú con las nuevas opciones para activar/desactivar el timer. Al apretar 9 se activa el timer y llegan las interrupciones. Al apretar a se para el temporizador

Test de interrupciones
Modo actual: MAQUINA

1.- Ecall
2.- Instruccion ilegal
3.- BREAKPOINT
4.- LOAD en direccion NO alineada
5.- STORE en direccion NO alineada
6.- Pasar a modo usuario
7.- Acceso a memoria no permitida (Lectura)
8.- Acceso a memoria no permitida (Escritura)
9.- Activar temporizador RISCV
a.- Desactivar temporizador RISCV
> 9
Activando timer RISC-V...
> ----> Interrupción!
MSTATUS: 0x00001880
  TW:   0  (Timeout Wait)
  MPRV: 0  (Modify PRiVilegde)
  MPP:  11 (Previous Privilegde level)
  MPIE: 1  (Previous Interrupt Enable)
  MIE:  0  (Interrupt enable)
MCAUSE: 0x80000007
  INTERRUPT: 1  (Exception(0)/Interrupt(1))
  CODE:      7  (Cause of the trap)
TIPO: Interrupcion
TIMER RISCV!!
Timer: 00000000-206A187D
CMP:   00000000-4066EABF
----> Interrupción!
MSTATUS: 0x00001880
  TW:   0  (Timeout Wait)
  MPRV: 0  (Modify PRiVilegde)
  MPP:  11 (Previous Privilegde level)
  MPIE: 1  (Previous Interrupt Enable)
  MIE:  0  (Interrupt enable)
MCAUSE: 0x80000007
  INTERRUPT: 1  (Exception(0)/Interrupt(1))
  CODE:      7  (Cause of the trap)
TIPO: Interrupcion
TIMER RISCV!!
Timer: 00000000-40D0FD0D
CMP:   00000000-60CDCFC5

48-mtime-measure

El temporizador mide el tiempo en ciclos. En este ejemplo medimos el tiempo que tarda la instrucción 'nop'. En el propio tiempo se mide la lectura del temporizador

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Establecer el vector de interrupcion
    la t0, isr_monitor
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init

    #-- Apagar LED
    jal led_off

    #-- Arrancar temporizador
    li t0, MTIME_CTRL
    li t1, 0x3
    sw t1, 0(t0)

    CLS

loop:

    #-- Comenzar medición
    li t0, MTIME
    lw t1, 0(t0)

    #--- Instrucciones a medir
    nop

    #-- Terminar la medición
    lw t2, 0(t0)

    #-- Calcular el número de ciclos
    sub s0, t2, t1
    PRINT "Ciclos: "
    mv a0, s0
    jal print_unsigned_int
    NL

    jal getchar

    j loop 

Al ejecutarlo primero se indica que tarda 52 ciclos, y el resto de veces 2...

Ciclos: 52
Ciclos: 2
Ciclos: 2
Ciclos: 2
Ciclos: 2
Ciclos: 2
Ciclos: 2

¡Esto es debido a la CHACHE!!! Las instrucciones se leen de la memoria flash serie, y por eso tardan mucho. Pero una vez que están cacheadas se ejecutan en 1 ciclo (1 ciclo para nop y otro para la lectura del temporizador)

A partir de ahora ya puedo medir lo que tardan las cosas...

También he implementado la función print_unsigned_int

2025-05-28

49-mtime-leds-blink

Ya tengo un ejemplo de "concurrencia". El programa principal hace parpadear 2 leds, de manera alterna. Mediante interrupciones se hace parpadear el led de test de la pico2, usando el temporizador del riscv

Toda la lógica la he metido en el módulo mtime.s

El programa principal llama a las funciones de este modulo:

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "regs.h"
.include "delay.h"
.include "led.h"
.include "uart.h"
.include "ansi.h"

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

main:
    #-- Configurar perifericos
    jal led_init
    LED_INIT(2)
    LED_INIT(3)
    jal uart_init

    #-- Estados iniciales de los LEDs
    jal led_off
    LED_ON(2)
    LED_OFF(3)

    #-- ¡Que comiencen las pruebas!
    j mtime_main_test

He añadido las nuevas funciones que permite configurar y usar cualquier LED, no solo el de test

Kernel de multiplexación de procesos

Llega el momento de frikear a otro nivel. Voy a estudiar e implementar un kernel de multiplexación de procesos, que permita ejecutar concurrentemente dos procesos independientes. El kernel pasará el control de uno a otro. Lo voy a implementar desde cero, paso a paso

2025-05-31

50-kernel-ecall

Ya tengo una primera versión de de un kernel de multiplexación entre 2 tareas. En esta primera versión el cambio de contexto se hace mediante llamadas a ecall. Esto me ha servido para que sea predecible y por tanto depurable

Hay 2 tareas: 1 y 2. Cada una hace parpadear un LED, en un bucle infinito. Cada tarea llama explícitamente al Kernel para cambiar a la tarea siguiente. ¡Está funcionando muy bien!

El siguiente objetivo es hacer que la multiplexación se realice mediante el temporizador del riscv

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "riscv.h"
.include "led.h"
.include "uart.h"
.include "ansi.h"
.include "kernel.h"

#-- Pausa en ms
.equ PAUSA, 500

# -- VARIABLES DE SOLO LECTURA
.section .rodata

#-- Tabla de punteros a los diferentes contextos
#-- de las tareas
ctx_list:
    .word ctx1
    .word ctx2
ctx_list_end:


# -- VARIABLES NO INICIALIZADAS
.section .bss

#-- Puntero al contexto actual
#-- (Apunta a un elemento en la lista de contextos)
ctx:    .word 0

    #-- Contexto de la tarea 1
    #-- Se guardan los 32 registros (en vez de x0 se guarda PC)
ctx1:   .space 32 * 4

    #-- Contexto de la tarea 2
ctx2:   .space 32 * 4

    #-- Valores iniciales de las pilas de las tareas
    #-- Se inicializan al arrancar
stack1: .word 0
stack2: .word 0

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

    #-- Cambiar el vector de interrupción
    la t0, isr_kernel
    csrw mtvec, t0

main:
    #-- Configurar perifericos
    jal led_init
    LED_INIT(2)
    LED_INIT(3)
    jal uart_init

    #-- Estados iniciales de los LEDs
    jal led_off
    LED_OFF(2)
    LED_OFF(3)

    CLS

    #-- Guardar los valores de las pilas
    #-- Configurar la pila de la tarea 1
    li t1, 0x1000
    sub sp, sp, t1
    
    #-- Almacenar el comienzo de la pila 1
    la t0, stack1
    sw sp, 0(t0)

    #-- Configurar la pila de la tarea 2
    la t0, stack2
    sub sp, sp, t1
    sw sp, 0(t0)

    #-- Inicializar la memoria del contexto 1
    la a0, ctx1
    la a1, task1
    la a2, stack1
    jal ctx_init

    #-- Inicializar la memoria del contexto 2
    la a0, ctx2
    la a1, task2
    la a2, stack2
    jal ctx_init

    #-- Inicializar puntero al contexto actual
    la t0, ctx_list
    la t1, ctx
    sw t0, 0(t1)  #-- ctx --> ctx_list[0]

    #--- Obtener el puntero al contexto actual
    #--- s0: Puntero al contexto actual
    la t0, ctx
    lw t0, 0(t0)
    lw s0, 0(t0)

    #-- Saltar a ejecutar la tarea actual
    lw t0, PC(s0)
    jalr t0


# -----------------------
# -- Tarea 1
# -----------------------
task1:

    #-- NO ELIMINAR (de momento)
    nop

    #-- Inicializar el puntero de pila de la tarea 1
    la t0, stack1
    lw sp, 0(t0)

    PRINT "--> TAREA 1: INIT\n"

tarea1_loop:
    PRINT "--> TAREA 1\n"

    #-- Encender LED de tarea
    LED_ON(2)

    li a0, PAUSA
    jal delay_ms
    
    #-- Test: Llamar al S.O
    ecall

    PRINT "--> TAREA 1\n"
    LED_OFF(2)

    li a0, PAUSA
    jal delay_ms

    #-- Llamar al S.O
    ecall

    j tarea1_loop

    PRINT "--> TAREA 1: FIN\n"

    ecall
    HALT


#--------------------------------
#-- Tarea 2
#--------------------------------
task2:

    #--- IMPORTANTE!!!! La primera instruccion NO
    #--- se ejecuta actualmente (porque en la isr se retorna a PC+4)
    nop

    #-- Inicializar el puntero de pila de la tarea 2
    la t0, stack2
    lw sp, 0(t0)

    PRINT "--> TAREA 2: INIT\n"


tarea2_loop:

    PRINT "--> TAREA 2\n"

    #-- Encender LED de tarea
    LED_ON(3)

    li a0, PAUSA
    jal delay_ms

    #-- Llamar al Kernel
    ecall

    PRINT "--> TAREA 2\n"
    LED_OFF(3)

    li a0, PAUSA
    jal delay_ms

    ecall
    j tarea2_loop

    PRINT "--> TAREA 2: FIN\n"
    HALT


#------------------------------------------------------
#-- ctx_next: Apuntar al siguiente contexto
#--   Se actualiza ctx para apuntar al siguiente
#------------------------------------------------------
#-- ENTRADAS:
#--    Ninguna
#-- SALIDAS:
#--    Ninguna
#-------------------------------------------------------
ctx_next:
   #-- Apuntar al siguiente contexto
    la t0, ctx
    lw t0, 0(t0)   #-- Puntero al contexto actual
    addi t0,t0, 4  #-- Apuntar al siguiente contexto

    la t1, ctx_list_end
    bne t0,t1, ctx_end   #-- No hemos llegado al final de la lista, terminar

    #-- Estamos en el final: apuntar al principio
    la t0, ctx_list

ctx_end:

    #--- t0 contine el nuevo contexto
    #-- Actualizar la variable ctx
    la t1, ctx
    sw t0, 0(t1)
    ret



# ----------------------------------
# -- Kernel de multiplexación
# ----------------------------------
isr_kernel:

    #-- Guardar la pila de la tarea actual
    csrw mscratch, sp

    #-- Obtener la pila del sistema
    la sp, __stack_top

    #-- Guardar registros usados en la pila del SO
    addi sp, sp, -16
    sw t0, 8(sp)
    sw t1, 4(sp)

    #--- Obtener el puntero al contexto actual
    #--- t0: Puntero al contexto actual
    la t0, ctx
    lw t0, 0(t0)  #-- Entrada de la tabla
    lw t0, 0(t0)  #-- Puntero al contexto

    #-- Guardar el PC
    csrr t1, mepc
    sw t1, PC(t0)

    #-- Guardar la pila
    csrr t1, mscratch
    sw t1, SP(t0)

    #-- Guardar resto de registros
    sw ra, RA(t0)
    sw gp, GP(t0)
    sw tp, TP(t0)

    #-- Ahora que gp está guardado, lo usamos
    #-- como puntero de contexto en vez t0
    mv gp, t0

    #-- Guardar t0 y t1. Recuperar su valor inicial
    #-- y guardarlo en el contexto
    lw t0, 8(sp)
    lw t1, 4(sp)

    #-- Guardar t0 y t1
    sw t0, T0(gp)
    sw t1, T1(gp)

    #-- Guardar resto de registros
    sw t2, T2(gp)
    sw s0, S0(gp)
    sw s1, S1(gp)
    sw a0, A0(gp)
    sw a1, A1(gp)
    sw a2, A2(gp)
    sw a3, A3(gp)
    sw a4, A4(gp)
    sw a5, A5(gp)
    sw a6, A6(gp)
    sw a7, A7(gp)
    sw s2, S2(gp)
    sw s3, S3(gp)
    sw s4, S4(gp)
    sw s5, S5(gp)
    sw s6, S6(gp)
    sw s7, S7(gp)
    sw s8, S8(gp)
    sw s9, S9(gp)
    sw s10, S10(gp)
    sw s11, S11(gp)
    sw t3, T3(gp)
    sw t4, T4(gp)
    sw t5, T5(gp)
    sw t6, T6(gp)

    jal led_on

    PRINT "******* KERNEL ********\n\n"
    #-- TEST: Imprimir contexto actual
    #------------------- Reponer el contexto de la tarea
    #--- Obtener el puntero al contexto actual
    #--- t0: Puntero al contexto actual
    la t0, ctx
    lw t0, 0(t0)  #-- Entrada de la tabla
    lw a0, 0(t0)  #-- Puntero al contexto
    #jal print_context

    #-- Cambiar al siguiente contexto
    jal ctx_next

    #------------------- Reponer el contexto de la tarea
    #--- Obtener el puntero al contexto actual
    #--- t0: Puntero al contexto actual
    la t0, ctx
    lw t0, 0(t0)  #-- Entrada de la tabla
    lw t0, 0(t0)  #-- Puntero al contexto

    #-- REPONER EL PC
    #-- Esto lo hace la instruccion mret a partir de la informacion
    #-- almacenada en mepc

    #-- NOTA: El pc se incrementa en 4 para apuntar a la siguiente instrucción
    #-- tras el ecal. No tengo claro si con interrupciones del timer hay que
    #-- hacer lo mismo
    lw t1, PC(t0)   #-- Recuperar pc
    addi t1, t1, 4  #-- Incrementar en 4 bytes
    csrw mepc, t1

    #-- El valor antiguo de t0 se guarda en scratch
    #-- (Lo teniamos guardado en la pila del SO)
    lw t1, T0(t0)
    csrw mscratch, t1

    #-- Reponer registros
    lw sp, SP(t0)
    lw ra, RA(t0)
    lw gp, GP(t0)
    lw tp, TP(t0)
    lw t1, T1(t0)
    lw t2, T2(t0)
    lw s0, S0(t0)
    lw s1, S1(t0)
    lw a0, A0(t0)
    lw a1, A1(t0)
    lw a2, A2(t0)
    lw a3, A3(t0)
    lw a4, A4(t0)
    lw a5, A5(t0)
    lw a6, A6(t0)
    lw a7, A7(t0)
    lw s2, S2(t0)
    lw s3, S3(t0)
    lw s4, S4(t0)
    lw s5, S5(t0)
    lw s6, S6(t0)
    lw s7, S7(t0)
    lw s8, S8(t0)
    lw s9, S9(t0)
    lw s10, S10(t0)
    lw s11, S11(t0)
    
    #-- Reponer el registro t0
    csrr t0, mscratch

    mret

2025-06-01

51-kernel-timeout

Ya he modificado el kernel anterior para conmutar entre 2 tareas cuando se produce un timeout del temporizador. Una tarea se encarga de hacer parpadear el LED y la otra espera a recibir un carácter por el puerto serie y cambia el estado del LED, además de imprimir un mensaje en la consola

Funciona sorprendentemente bien!!! Mi primer mini-sistema operativo con el riscv!!!!!

Este es el programa principal:

#---------------------------
#-- Funciones de interfaz
#---------------------------
.global _start   #-- Punto de entrada

.include "regs.h"
.include "riscv.h"
.include "led.h"
.include "uart.h"
.include "ansi.h"
.include "kernel.h"

#-- Pausa en ms
#-- Para el parpadeo del LED de la tarea 1
.equ PAUSA, 500

.section .text

# -- Punto de entrada
_start:

    #-- Acciones de arranque
    la sp, __stack_top
    jal runtime_init

main:
    #-- Configurar perifericos
    jal led_init
    jal uart_init
    LED_INIT(2)
    
    #-- Estados iniciales de los LEDs
    jal led_off
    LED_OFF(2)

    CLS

    #-- Inicializar el kernel 
    #-- Establecer las tareas 
    #-- y saltar a ejecutar la primera tarea
    la a0, task1
    la a1, task2
    jal kernel_init


# -----------------------
# -- Tarea 1
# -- Parpadeo del LED
# -----------------------
task1:
    PRINT "--> TAREA 1: INIT\n"

tarea1_loop:

    #-- Cambiar de estado el LED
    jal led_toggle

    #-- Esperar
    li a0, PAUSA
    jal delay_ms

    #-- Repetir
    j tarea1_loop


#-----------------------------------------------------
#-- Tarea 2
#-- Cambiar de estado el LED 2 cuando se recibe un 
#-- caracter por el puerto serie
#-----------------------------------------------------
task2:
    PRINT "--> TAREA 2: INIT\n"

tarea2_loop:
    PRINT "--> TAREA 2\n"

    #-- Cambiar de estado el LED 2
    LED_TOGGLE(2)

    #-- Esperar a que se apriete una tecla
    jal getchar

    #-- Repetir
    j tarea2_loop

(TODO)

Post a analizar: https://forums.raspberrypi.com/viewtopic.php?t=382741
Analizar este ejemplo también: https://github.com/raspberrypi/pico-examples/blob/master/gpio/hello_gpio_irq/hello_gpio_irq.c

(TODO)

  • SWD: Medir la sonda con el osciloscopio: qué señales se obtienen al arrancar?

🚧 (DEBUG) 🚧

Enlaces

⚠️ **GitHub.com Fallback** ⚠️