Pico-Powered Boot Switch: OS vor dem Einschalten per Schalter wählen
OS vor dem Einschalten per Schalter vorwählen – kein Warten auf GRUB-Menü mehr beim Dual-Boot.
Story
Auf Hackaday.io stieß ich auf dieses Projekt (Link) von Stephen Holdaway. Er löste eine frustrierende Aufgabe jedes Dual-Boot-Nutzers: Sitzen und Warten auf GRUB-Menü, um OS (Windows) auszuwählen beim Wechsel. Er fügte einen Hardware-Schalter hinzu, der bei jedem Einschalten das zu bootende OS bestimmt.
Er erreichte dies, indem er STM32 Mikrocontroller als USB Mass-Storage-Device konfigurierte. Seine gesamte Reise von Forschung bis Implementierung dokumentierte er im Hackaday Post (Link). Bitte durchlesen für tieferes Verständnis.
In diesem Projekt zeige ich, wie ich die Änderungen auf Raspberry Pi Pico portierte. Meine Version finden Sie in diesem GitHub Repo (Link).
Konzept
GNU GRUB läuft vor Laden jeglicher Operating Systems. Über dieses Menü wählen wir das zu ladende OS. GRUB bietet sehr limitierte Module – es kann keine Daten von Mikrocontroller via USB lesen. Aber es kann Daten von Storage-Disks lesen.
Also tricksen wir GRUB, indem wir unseren Mikrocontroller als Mass-Storage-Device enumerieren.
Daher enumerieren wir Raspberry Pi Pico als Mass-Storage-Device via tinyUSB Library, das eine Datei switch.cfg enthält, in die Pico die Schalterposition schreibt: 1 für ON, 0 für OFF.
Wir müssen ein Skript in GRUB hinzufügen, das switch.cfg liest und default auf 0 (Ubuntu) / 2 (Windows) setzt.
GRUB lädt, führt unser Custom-Skript aus, das nach unserem Device via UUID sucht, und falls existent switch.cfg liest. Nach Erhalt der Schalterposition setzt es die Default-OS-Selektion entsprechend.
Zusammenfassend:
- Pico konfiguriert sich als Mass-Storage-Device.
- GRUB Menü ruft unser Skript auf und fragt nach der Datei.
- Pico antwortet auf Read-Request, indem es Schalterposition in switch.cfg schreibt.
- GRUB-Skript extrahiert Info aus Datei und setzt Default-Option basierend auf extrahierten Daten.
Pico als Mass-Storage-Device konfigurieren
Ich nutzte cdc_msc Example von tinyUSB. Das Example konfiguriert Pico als Mass-Storage-Device, erstellt FAT12 Filesystem und enumeriert README.txt Datei.
Ich änderte README.txt zu switch.cfg und fügte Zeile „set os_hw_switch=0\n“ zur Datei hinzu.
#define SWITCH_CFG_CONTENTS \ "set os_hw_switch=0\n" ... //------------- Block3: Readme Content -------------// SWITCH_CFG_CONTENTS
Pico ist nun als Mass-Storage-Device konfiguriert. Nach Kopieren der UF2-Datei auf Pico enumeriert es als Storage-Device. Wir brauchen UUID des Devices für GRUB-Skript: 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"
Schaltung
Schalterposition lesen und in Datei schreiben
Jetzt Schalterposition lesen und Inhalt von switch.cfg entsprechend ändern:
- Schalter ON: set os_hw_switch=1\n
- Schalter OFF: set os_hw_switch=0\n
GPIO_PIN 28 als Schalter-Pin genutzt, auf Pull-Down konfiguriert.
read_switch_value gibt Schalterposition zurück: '1' = on (pulled high), '0' = off (pulled low).
//-------------------------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);
// Pin als INPUT konfigurieren
gpio_set_dir(SWITCH_PIN, false);
// Pin als PULL_DOWN konfigurieren
gpio_set_pulls(SWITCH_PIN, false, true);
Zum Schreiben der Schalterposition in switch.cfg nutzte ich readGRUBConfig(), das read_switch_value aufruft und Output-Buffer mit Schalterposition setzt.
Ich fand heraus, dass beim Lesen des dritten Blocks lba auf 3 gesetzt ist, daher Call abfangen und readGrubConfig aufrufen mit Buffer, wohin Dateiinhalt kopiert wird.
//-------------------------msc_disk.c---------------------
static char grubConfigStr[] = "set os_hw_switch=0\n";
static void readGrubConfig(uint8_t* output)
{
// Config String mit aktuellem Schalterwert modifizieren
grubConfigStr[sizeof(grubConfigStr)-3] = read_switch_value();
memcpy(output, &grubConfigStr, sizeof(grubConfigStr));
}
// Callback bei READ10 Command.
// Disk-Daten in Buffer kopieren (bis bufsize) und kopierte Bytes zurückgeben.
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
{
(void) lun;
// beim Lesen der Datei
if(lba == 3){
readGrubConfig(buffer);
return bufsize;
}
...
...
}
Pico Code kompilieren
Pico stdlib zum Code hinzufügen für gpio Pin-Zugriff.
//-------------------------main.c----------------------------------- #include#include #include #include "bsp/board.h" #include "tusb.h" ... #include "pico/stdlib.h"
Projekt bauen:
$ mkdir build $ cd build $ cmake .. $ make
GRUB konfigurieren zum Lesen des Dateiinhalts
Änderungen in Ubuntu 20.10 hinzugefügt.
$ sudo vim /etc/grub.d/40_custom
....
# Hardware-Switch-Device via hardcoded Filesystem-ID suchen
search --no-floppy --fs-uuid --set hdswitch 0000-1234
# Falls gefunden, dynamische Config-Datei lesen und passenden Eintrag für jede Position wählen
if [ "${hdswitch}" ] ; then
source ($hdswitch)/switch.cfg
if [ "${os_hw_switch}" == 0 ] ; then
# Boot Linux
set default="0"
elif [ "${os_hw_switch}" == 1 ] ; then
# Boot Windows
set default="2"
else
# Fallback zu Default
set default="${GRUB_DEFAULT}"
fi
else
set default="${GRUB_DEFAULT}"
fi
Zuerst suchen wir unser Filesystem. GRUB hat Subcommand search dafür.
- -no-floppy Option verhindert Suche auf Floppy-Devices
- -fs–uuid 0000-1234 sucht Filesystem mit UUID 0000-1234.
Falls Device gefunden, erstes gefundenes Device als Wert der Environment-Variable gesetzt.
–set hdswitch hdswitch ist unsere Environment-Variable und wird mit Disk-Name gesetzt falls gefunden.
Als Nächstes switch.cfg sourcen falls hdswitch Variable gesetzt, was weitere Environment-Variable os_hw_switch mit Schalterposition (0/1) erstellt.
Wert von os_hw_switch lesen und Default auf 0 oder 2 setzen. 0 weil Ubuntu an 0. Position, Windows an 2. Position im GRUB-Menü.
Zuletzt, falls hdswitch nicht gesetzt, Default auf GRUB_DEFAULT.
GRUB aktualisieren:
$ sudo update-grub
Quelle: Hardware Boot Select Switch Using Pico
Herstellungsprozess
- C# Switch Statement meistern: Syntax, Beispiele & Best Practices
- C# using-Anweisungen meistern: Importe, Aliase und statische Direktiven
- Outdoor-Wetterstation mit Raspberry Pi 2 & ADS-WS1 bauen
- LM35-basiertes Temperatursteuerungssystem mit AT89S52 Mikrocontroller
- Remote-Wetterüberwachung mit Raspberry Pi 3 & PubNub
- TI SensorTag mit Blynk via Node-RED auf Raspberry Pi Zero integrieren
- LED mit PIR-Bewegungsmelder am Raspberry Pi steuern
- Intelligenten Roboter mit Bridge Shield bauen – Raspberry Pi & Arduino Integration
- WLAN-gesteuerter Raspberry Pi Roboter mit Python bauen – Schritt-für-Schritt
- Embedded Hardware Hacking für IoT-Geräte: Tools, Techniken und Business-Implikationen