Pico 驱动的启动选择开关:上电前预选操作系统
上电前通过拨动开关预选要启动的操作系统,无需等待 GRUB 菜单选择。
项目背景
在 Hackaday.io 逛项目时,我发现了 Stephen Holdaway 的这个项目(点击查看)。他解决了每个双系统用户的痛点:每次想切换到 Windows 都得坐等 GRUB 菜单选项。他通过硬件开关决定每次上电启动哪个系统。
他将 STM32 单片机配置为 USB 大容量存储设备。他在 Hackaday 帖子(点击)详细记录了从研究到实现的全过程。请阅读原帖深入理解实现细节。
本项目展示如何将该方案移植到树莓派 Pico。我的版本见此 GitHub 仓库 (点击)。
核心概念
GNU GRUB 是操作系统加载前运行的程序。通过此菜单选择启动哪个 OS。GRUB 可用模块极少,无法读取 USB 连接的单片机数据,但能读取存储磁盘数据。
因此我们可将单片机枚举为大容量存储设备,欺骗 GRUB 从中读取数据。
我们让树莓派 Pico 通过 tinyUSB 库枚举为大容量存储设备,其包含 switch.cfg 文件,Pico 将开关位置写入其中:1 代表 ON,0 代表 OFF。
需在 GRUB 中添加脚本,读取 switch.cfg 并将 default 设为 0(Ubuntu)/2(Windows)。
GRUB 加载时运行自定义脚本,脚本按 UUID 搜索设备,若存在读取 switch.cfg。获取开关位置后相应设置默认启动项。
总结:
- Pico 配置为大容量存储设备。
- GRUB 菜单调用脚本请求特定文件。
- Pico 响应读取请求,将开关位置写入 switch.cfg。
- GRUB 脚本从文件提取信息并按提取数据设置默认项。
配置 Pico 为大容量存储设备
使用 tinyUSB 的 cdc_msc 示例实现。该例将 Pico 配置为大容量存储设备,创建 FAT12 文件系统并枚举 README.txt 文件。
我将 README.txt 改为 switch.cfg 并添加行 “set os_hw_switch=0\n”。
#define SWITCH_CFG_CONTENTS \ "set os_hw_switch=0\n" ... //------------- Block3: Readme Content -------------// SWITCH_CFG_CONTENTS
现 Pico 已配置为大容量存储设备。复制 uf2 文件到 Pico 后,它将枚举为存储设备。GRUB 脚本需设备 UUID,为 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"
电路
读取开关位置并写入文件
现在需读取开关位置并相应修改 switch.cfg 内容:
- 开关 ON:set os_hw_switch=1\n
- 开关 OFF:set os_hw_switch=0\n
我用 GPIO_PIN 28 作为开关引脚,设为下拉。
read_switch_value 返回开关位置:'1' 为开 (上拉高),'0' 为关 (下拉低)。
//-------------------------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);
// 配置为输入
gpio_set_dir(SWITCH_PIN, false);
// 配置为下拉
gpio_set_pulls(SWITCH_PIN, false, true);
将开关位置写入 switch.cfg,我用 readGRUBConfig() 调用 read_switch_value 并设置输出缓冲区内容。
发现读取第三块 block3 时 lba 为 3,故拦截调用并调用 readGrubConfig 将内容拷贝至缓冲区。
//-------------------------msc_disk.c---------------------
static char grubConfigStr[] = "set os_hw_switch=0\n";
static void readGrubConfig(uint8_t* output)
{
// 根据当前开关值修改配置字符串
grubConfigStr[sizeof(grubConfigStr)-3] = read_switch_value();
memcpy(output, &grubConfigStr, sizeof(grubConfigStr));
}
// 收到 READ10 命令时的回调。
// 将磁盘数据拷贝至缓冲区 (最多 bufsize) 并返回拷贝字节数。
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
{
(void) lun;
// 读取文件时
if(lba == 3){
readGrubConfig(buffer);
return bufsize;
}
...
...
}
编译 Pico 代码
需添加 pico stdlib 获取 gpio 引脚访问。
//-------------------------main.c----------------------------------- #include#include #include #include "bsp/board.h" #include "tusb.h" ... #include "pico/stdlib.h"
构建项目:
$ mkdir build $ cd build $ cmake .. $ make
配置 GRUB 读取文件内容
我在 Ubuntu 20.10 中添加这些更改。
$ sudo vim /etc/grub.d/40_custom
....
# 通过硬编码文件系统 ID 查找硬件开关设备
search --no-floppy --fs-uuid --set hdswitch 0000-1234
# 若找到,读取动态配置文件并按位置选择相应启动项
if [ "${hdswitch}" ] ; then
source ($hdswitch)/switch.cfg
if [ "${os_hw_switch}" == 0 ] ; then
# 启动 Linux
set default="0"
elif [ "${os_hw_switch}" == 1 ] ; then
# 启动 Windows
set default="2"
else
# 回退默认
set default="${GRUB_DEFAULT}"
fi
else
set default="${GRUB_DEFAULT}"
fi
首先搜索文件系统。GRUB 有 search 子命令专为此用。
- -no-floppy 防止搜索软驱设备
- -fs–uuid 0000-1234 搜索 UUID 为 0000-1234 的文件系统。
若找到设备,第一个设备作为环境变量值。
–set hdswitchhdswitch 为环境变量名,找到时设为磁盘名。
接着若 hdswitch 变量已设置则 source switch.cfg 文件,创建另一环境变量 os_hw_switch 存放开关位置 (0/1)。
读取 os_hw_switch 值并将 default 设为 0 或 2。0 因 Ubuntu 在 GRUB 菜单第 0 项,Windows 在第 2 项。
最后若 hdswitch 未设置,default 回退至 GRUB_DEFAULT。
更新 GRUB:
$ sudo update-grub
来源: Hardware Boot Select Switch Using Pico
制造工艺
- 现代脱毛器全攻略:设计、历史与未来趋势
- 树莓派 2 搭建户外气象站:接入 ADS‑WS1 串口气象站
- 基于 AT89S52 单片机与 LM35 传感器的温控系统设计
- 树莓派 3 配合 PubNub 实现远程气象监测
- 树莓派 Zero 配合 Node‑RED 融合 TI SensorTag 与 Blynk 物联网平台
- 树莓派 PIR 运动传感器控制 LED 实战指南
- Bridge Shield扩展板:树莓派与Arduino集成打造智能机器人
- 用 Python 构建 Wi-Fi 控制的树莓派机器人:分步指南
- 借助 Eclipse Vorto 同步设备载荷:实战指南
- 物联网设备嵌入式硬件黑客技术:工具、技术与商业启示
- 掌握 C# switch 语句:语法、示例与最佳实践
- 掌握 C# using 指令:命名空间导入、别名与静态导入全解析