Interruptor de Arranque Pico-Powered: Elige tu SO Antes de Encender
Pre-elige el SO para arrancar, incluso antes de encender el ordenador, conmutando un interruptor. Ahora no tienes que esperar a seleccionar el SO (Windows) desde el menú GRUB cada vez que quieres cambiar a Windows.
Historia
Navegando por Hackaday.io, tropecé con este proyecto de Stephen Holdaway. En él, resolvió una tarea frustrante a la que se enfrenta todo usuario de dual boot: sentarse y esperar para elegir el SO (Windows) desde el menú GRUB cada vez que queremos cambiar a Windows. Logró añadir un interruptor hardware para determinar el SO a arrancar cada vez que se enciende el ordenador.
Lo logró configurando un microcontrolador STM32 como dispositivo de almacenamiento masivo USB. Ha documentado todo su viaje a través de la investigación e implementación en su post de Hackaday. Por favor, revisa su post para entender mejor las implementaciones.
En este proyecto, te mostraré cómo logré portar los cambios a Raspberry Pi Pico. Puedes encontrar mi versión en este repositorio de GitHub.
Concepto
GNU GRUB es un programa que se ejecuta antes de que se cargue cualquier Sistema Operativo. A través de este menú, podemos seleccionar qué SO cargar. GRUB ofrece módulos muy limitados para trabajar. Esto significa que no puede leer datos de un microcontrolador conectado vía USB. Pero sí puede leer datos de discos de almacenamiento.
Así que podemos engañar a GRUB para que lea datos del microcontrolador, enumerando nuestro micro como un dispositivo de almacenamiento masivo.
Por tanto, enumeramos nuestra Raspberry Pi Pico como dispositivo de almacenamiento masivo, vía la librería tinyUSB, que tendrá un archivo switch.cfg, al que el Pico escribirá la posición del interruptor: 1 para ON, 0 para OFF.
Tenemos que añadir un script en GRUB, cuyas funciones lean el archivo switch.cfg y establezcan el default a 0 (Ubuntu) / 2 (Windows).
Cuando GRUB carga, ejecuta nuestros scripts personalizados, que a su vez buscan nuestro dispositivo por sus identificadores UUID, y si existe, leen el archivo switch.cfg. Tras obtener la posición del interruptor, establece la selección de SO por defecto respectivamente.
En resumen:
- El Pico se configura como dispositivo de almacenamiento masivo.
- El menú GRUB llama a nuestro script y pide el archivo particular.
- El Pico responde a la petición de lectura añadiendo la posición del interruptor en el archivo switch.cfg.
- El script en GRUB extrae la info del archivo y establece la opción por defecto desde los datos extraídos.
Configurar el Pico como Dispositivo de Almacenamiento Masivo
He usado el ejemplo cdc_msc de tinyUSB para lograr esto. El ejemplo configura el Pico como dispositivo de almacenamiento masivo y crea un sistema de archivos FAT12 y enumera un archivo README.txt.
Cambié el README.txt a switch.cfg y añadí la línea "set os_hw_switch=0\n" al archivo.
#define SWITCH_CFG_CONTENTS \ "set os_hw_switch=0\n" ... //------------- Block3: Readme Content -------------// SWITCH_CFG_CONTENTS
Ahora hemos configurado el Pico como dispositivo de almacenamiento masivo. Tras copiar el archivo uf2 al Pico, se enumera como dispositivo de almacenamiento. Necesitaremos el UUID del dispositivo para el script de GRUB, que es UUID="0000-1234".
$ sudo blkid ... /dev/sda: SEC_TYPE="msdos" LABEL_FATBOOT="TinyUSB MSC" LABEL="TinyUSB MSC" UUID="0000-1234" BLOCK_SIZE="512" TYPE="vfat"
Circuito
Leer Posición del Interruptor y Escribir en Archivo
Ahora necesitamos leer la posición del interruptor y cambiar el contenido del archivo switch.cfg según corresponda:
- si el interruptor está ON: set os_hw_switch=1\n
- si el interruptor está OFF: set os_hw_switch=0\n
He usado GPIO_PIN 28 como pin del interruptor, configurado como pull-down.
read_switch_value devuelve la posición del interruptor: '1' es on (pull-up) y '0' es off (pull-down).
//-------------------------main.c---------------------
#define SWITCH_PIN 28
// read switch value
uint8_t read_switch_value()
{
return gpio_get(SWITCH_PIN) ? '1' : '0';
}
int main(void)
{
gpio_init(SWITCH_PIN);
// configure pin as INPUT
gpio_set_dir(SWITCH_PIN, false);
// configure pin as PULL_DOWN
gpio_set_pulls(SWITCH_PIN, false, true);
Para escribir la posición del interruptor en switch.cfg, he usado readGRUBConfig() que llama a la función read_switch_value, y establece el buffer de salida con la posición del interruptor.
Descubrí que al leer el tercer bloque3 lba se establece a 3, por lo que interceptando la llamada y llamando a readGrubConfig y pasando el buffer donde se copiará el contenido del archivo.
//-------------------------msc_disk.c---------------------
static char grubConfigStr[] = "set os_hw_switch=0\n";
static void readGrubConfig(uint8_t* output)
{
// Modify config string with current switch value
grubConfigStr[sizeof(grubConfigStr)-3] = read_switch_value();
memcpy(output, &grubConfigStr, sizeof(grubConfigStr));
}
// Callback invoked when received READ10 command.
// Copy disk's data to buffer (up to bufsize) and return number of copied bytes.
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
{
(void) lun;
// when reading the file
if(lba == 3){
readGrubConfig(buffer);
return bufsize;
}
...
...
}
Compilar el Código del Pico
Necesitamos añadir stdlib de pico a nuestro código para obtener acceso al pin gpio.
//-------------------------main.c----------------------------------- #include#include #include #include "bsp/board.h" #include "tusb.h" ... #include "pico/stdlib.h"
Para compilar el proyecto:
$ mkdir build $ cd build $ cmake .. $ make
Configurar GRUB para Leer el Contenido del Archivo
He añadido estos cambios en mi Ubuntu 20.10.
$ sudo vim /etc/grub.d/40_custom
....
# Buscar dispositivo de interruptor hardware por su ID de filesystem hardcodeado
search --no-floppy --fs-uuid --set hdswitch 0000-1234
# Si se encuentra, leer archivo de config dinámico y seleccionar entrada apropiada para cada posición
if [ "${hdswitch}" ] ; then
source ($hdswitch)/switch.cfg
if [ "${os_hw_switch}" == 0 ] ; then
# Arrancar Linux
set default="0"
elif [ "${os_hw_switch}" == 1 ] ; then
# Arrancar Windows
set default="2"
else
# Fallback a default
set default="${GRUB_DEFAULT}"
fi
else
set default="${GRUB_DEFAULT}"
fi
Primero, buscamos nuestro filesystem. GRUB tiene un subcomando search justo para esto.
- -no-floppy evita buscar dispositivos floppy
- -fs–uuid 0000-1234 busca un filesystem con UUID 0000-1234.
Si se encuentra algún dispositivo, el primer dispositivo encontrado se establece como valor de la variable de entorno.
–set hdswitchhdswitch es nuestra variable de entorno y se establece con el nombre del disco si se encuentra.
Luego, hacemos source del archivo switch.cfg si la variable hdswitch está establecida, lo que crea otra variable de entorno os_hw_switch con la posición del interruptor (0/1).
Leemos el valor de os_hw_switch y establecemos el default a 0 o 2 respectivamente. 0 porque Ubuntu está en posición 0 y Windows en posición 2 en el menú GRUB.
Por último, si hdswitch no se estableció, ponemos el default a GRUB_DEFAULT.
Ahora necesitamos actualizar nuestro grub:
$ sudo update-grub
Fuente: Hardware Boot Select Switch Using Pico
Proceso de manufactura
- Guía completa de dispositivos de depilación modernos: Diseño, historia y futuro
- Construcción de una estación meteorológica exterior con Raspberry Pi 2 y ADS‑WS1
- Sistema de control de temperatura basado en LM35 con microcontrolador AT89S52
- Monitorización meteorológica remota con Raspberry Pi 3 y PubNub
- Integración de TI SensorTag con Blynk vía Node‑RED en Raspberry Pi Zero
- Controle un LED con Sensor PIR de Movimiento en Raspberry Pi
- Construye un robot inteligente con Bridge Shield – Integración Raspberry Pi y Arduino
- Construye un Robot Raspberry Pi Controlado por Wi‑Fi con Python – Guía Paso a Paso
- Hacking de Hardware Embebido para Dispositivos IoT: Herramientas, Técnicas e Implicaciones de Negocio
- Dominando la sentencia switch en C#: Sintaxis, ejemplos y mejores prácticas
- Dominando las Sentencias Using en C#: Importaciones, Alias y Directivas Estáticas