= pico-usb-wifi :toc: macro :toclevels: 3 :idprefix: :idseparator: -
pico-usb-wifi 是适用于 Raspberry Pi Pico W 的固件,将其转变为无需驱动的 USB Wi-Fi 适配器,枚举为 USB CDC-NCM 设备。
:figure-caption: AI Slop
.pico-usb-wifi Diagram image::images/openrouter-banana2-rpi-pico.png[]
该固件作为透明的二层桥接器工作,在 Pico W 的无线接口与 USB 接口之间转发帧。 主机的 USB 接口采用 Pico W Wi-Fi 站点的 MAC 地址,从而提供端到端的单一 MAC 和 IP 身份。
无需主机端驱动、内核模块或无线协议栈;参见 <<no-host-side-wi-fi-stack,无需主机端 Wi-Fi 协议栈>>。
主机仅需要随每个现代 Linux、macOS、Windows 和移动操作系统提供的内置 cdc_ncm 和 cdc_acm 驱动。
== 功能
pico-usb-wifi 提供以下功能:
.A Real World Situation image::images/slop_2.png[]
== 为什么存在
我在即将进行的嵌入式 Linux 项目中需要一个 USB Wi-Fi 适配器。 我手头没有便宜的 USB Wi-Fi 加密狗,所以与其去实体店花五美元买一个,我花了一个长周末假期的两天时间和大约一百万个 Claude Code 代币来构建这个固件。
白一百, Author of pico-usb-wifi
Google 说这不可行:
.pico-usb-wifi "Not Feasible" image::images/gemini_says_not_possible.png[]
toc::[]
== 无需主机端 Wi-Fi 协议栈
与 USB Wi-Fi 加密狗不同,此适配器仅向主机暴露类以太网接口。Pico W 包含完整的无线侧:无线电、关联、WPA2/WPA3 请求者和监管域。
这使得系统可以避免安装 wpa_supplicant、cfg80211/mac80211 无线协议栈、监管数据库以及芯片组固件或供应商驱动。Wi-Fi 凭据的配置在设备上通过其带外管理控制台进行,而非通过任何主机端无线工具。这使得受限或设备主机、没有无线驱动的主机,或其供应商内核缺少这些驱动的主机,能够仅使用通用的 CDC 类驱动连接到无线网络。
== 工作原理
[#fig-topology] .Topology Diagram image::images/topology.svg[Transparent layer-2 bridge topology,820]
主机的 USB 接口被赋予 Pico W Wi-Fi 站点的 MAC 地址,因此端到端存在单一 MAC,Pico W 可以在 USB 和 Wi-Fi 之间逐字转发以太网帧。Wi-Fi 站点无法桥接多个 MAC 地址,因此将主机和站点合并到一个 MAC 上正是完全可能实现透明桥接的原因。完整原理、数据路径和 IPv6/多播处理在 <<architecture,架构>> 中描述。
== 主机要求
主机需要内核中的 cdc_ncm 和 cdc_acm 驱动。两者已成为主线 Linux 的一部分超过十年,因此任何当前受支持的内核都包含它们。不涉及树外模块、固件块或供应商驱动。相同的类驱动存在于 macOS、Windows 10 及更新版本、Android 和 iOS 上。
[NOTE] 未测试其他操作系统。
== 构建
该项目是标准的 pico-sdk CMake 项目。它需要 ARM 嵌入式工具链、CMake、构建后端(Ninja 或 Make)、Python 3,以及带有子模块的 pico-sdk 检出。pico-sdk 中捆绑的 TinyUSB 和 lwIP 未经修改直接使用。
=== 依赖项
在基于 Arch 的系统(Arch、CachyOS、Manjaro)上,工具链来自官方仓库:
arm-none-eabi-newlib 提供嵌入式 C 库和头文件;没有它,交叉编译器无法找到 stdint.h 和类似头文件。libusb 仅 picotool 需要,pico-sdk 在首次配置期间从源码构建它以生成 UF2;不需要单独的 picotool 包。
=== 构建步骤
git clone -b 2.2.0 --recurse-submodules https://github.com/raspberrypi/pico-sdk export PICO_SDK_PATH="$PWD/pico-sdk"
cp src/wifi_config.h.example src/wifi_config.h # then edit SSID/password, or leave blank cmake -S . -B build -G Ninja -DPICO_BOARD=pico_w -DCMAKE_BUILD_TYPE=Release cmake --build build
-G Ninja 标志是可选的;省略它则使用默认的 Make 生成器(然后 cmake --build build -j)。
wifi_config.h 包含编译时的默认凭据,并被 .gitignore 忽略。留空会生成没有内置凭据的镜像,通过管理控制台在运行时配置(<<management-console,管理控制台>>);填写则固化默认网络。
== 写入固件
以下是加载固件到板子的步骤。
. 按住 BOOTSEL 按钮,同时通过 USB 连接板子。它会挂载为 RPI-RP2 USB 大容量存储卷,通常位于 /run/media/<user>/RPI-RP2 或 /media/<user>/RPI-RP2。
. 将 pico-usb-wifi.uf2 复制到该卷上。板子会自动重启进入固件。
. 将板子连接到将要接收 Wi-Fi 连接的主机。
== 在 Linux 主机上使用
将设备插入主机,并通过管理控制台(<<management-console,管理控制台>>)配置其 Wi-Fi 凭据一次。之后,主机的接口行为就像接入点网络上的任何有线连接一样。
自动管理接口的主机(NetworkManager、systemd-networkd、dhcpcd)无需设置:它通过桥接运行 DHCP 和 SLAAC,并接收单个 IPv4 地址、IPv6 地址、接入点的网关和 DNS,与有线客户端完全一样。没有需要配置的设备端地址或网关,因为 Pico 没有任何地址。接口的 MAC 地址是 Wi-Fi 站点的 MAC,这就是如何向网络呈现单一身份的方式。
下面的 ip 命令输出显示了生成的接口:接入点自身子网上的普通 DHCP/SLAAC 客户端,带有站点的 MAC,且没有 Pico 的痕迹。
== 管理控制台
管理控制台是配置前端,位于第一个 CDC-ACM 串行功能上(通常是 /dev/ttyACM0)。设备枚举后即可访问,在 Wi-Fi 关联之前,因此配置永远不需要网络。
使用 picocom 或 screen 等串行终端打开它;波特率对于 USB CDC 无关紧要。控制台回显输入并显示提示符,每个命令都会打印完整的设备状态。Wi-Fi 认证可以是 WPA2-PSK 或 WPA3-SAE(AES)。密码是网络的密码短语,或者当密码留空时为开放网络。受密码保护的配置文件使用 WPA2/WPA3 过渡模式,因此它可以加入任何一种接入点。
控制台最多存储八个凭据配置文件;其中一个是活动配置文件,设备与之关联。set ssid/set pass 编辑活动配置文件,list/use/del 管理配置文件集,scan 发现附近网络并从编号列表中加入一个——当 SSID 包含难以键入的字符时很方便。命令词不区分大小写;控制台以小写显示它们。此处的会话通过扫描来配置网络。
$ picocom /dev/ttyACM0
更改立即生效,重新与活动配置文件关联;无需重启。重复以保存更多网络;list 显示它们,use <n> 切换活动配置文件:
save 将每个配置文件持久化到闪存;restore 通过重新加载已保存的记录丢弃未保存的编辑。下表列出了命令集。
[#tbl-config-commands] .Management-Console Commands [cols="2,3", options="header"] |=== |命令 |效果
|set ssid <text>
|设置活动配置文件的 SSID(值可以包含空格)并重新关联;如果不存在则创建第一个配置文件。
|set pass <text>
|设置活动配置文件的 WPA2/WPA3 密码短语(开放网络留空)并重新关联。
|set country <CC\|WORLDWIDE>
|设置监管国家/地区(下次启动时完全应用)。
|set debug <on\|off>
|在调试控制台上流式传输诊断信息;参见 <<debug-console,调试控制台>>。
|list
|列出已保存的配置文件,标记活动配置文件。
|use <n>
|使配置文件 n 成为活动配置文件并重新关联。
|del <n>
|删除配置文件 n。
|scan
|扫描附近网络并进入扫描子菜单(back、join <n>、scan 重复,或 live 连续流式传输未关联的流)。join 将选定的网络暂存为活动配置文件,准备进行 set pass。
|save
|将所有配置文件和设置持久化到闪存。
|restore
|通过重新加载已保存的设置丢弃未保存的更改。
|===
主机的分配地址在状态转储中显示为 host IPv4 和 host IPv6,通过被动嗅探桥接流量获得,因为 Pico 没有要报告的地址。
配置扇区位于闪存末尾,与开头的程序镜像分开,因此普通的 pico-usb-wifi.uf2 重新刷新会保留已保存的配置文件(全芯片擦除会清除它们)。例外是升级 到 v1.1.0:记录布局已更改以容纳多个配置文件,因此会丢弃 v1.1.0 之前的记录,并且必须重新输入一次网络(请参阅变更日志)。
== 调试控制台
调试控制台是第二个 CDC-ACM 串行功能(通常是 /dev/ttyACM1)上的只写诊断流。在管理控制台上发出 set debug on 之前,它保持静默,因此关闭时没有开销,并且从不干扰管理。
启用后,它会报告关联更改和定期的桥接统计信息行,如下面的会话所示。-DTRACE_FRAMES=1 构建会增加每个桥接帧的单行摘要,但在负载下会淹没控制台,因此默认关闭。
统计字段在下表中描述。
[#tbl-debug-stats] .Debug Statistics Fields [cols="1,3", options="header"] |=== |字段 |含义
|->wifi
|从主机转发到 Wi-Fi 的帧。
|->host
|从 Wi-Fi 转发到主机的帧。
|txdrop
|因站点尚未关联而丢弃的主机到 Wi-Fi 帧(主机重试)。
|rxdrop
|因 USB 端无法足够快排出而丢弃的 Wi-Fi 到主机帧。
|refl
|因它们是主机的自身传输而被接入点反射回来的 Wi-Fi 到主机帧(丢弃)。
|poolfail
|因 lwIP pbuf 池暂时耗尽而丢弃的主机到 Wi-Fi 帧。
|ringpk
|自上一个统计行以来 Wi-Fi 到主机 USB-TX 队列深度峰值(共 32),然后重置——一个实时仪表,值接近 32 表示 USB 无法与 Wi-Fi 传送一样快。(与历史最大值不同,一旦突发过去,它就会再次下降。)
|link
|站点 Wi-Fi 链接状态:up(已关联)、join/down(正在关联),或失败原因——badauth(密码错误)、nonet(未找到 SSID)、fail。
|hangs
|自上次冷上电以来看门狗从挂起中恢复固件的次数;参见 <<automatic-recovery,自动恢复>>。
|faults
|自上次冷上电以来固件恢复的硬故障次数。
|faultpc
|最近一次硬故障的地址(若无则为 0x00000000),用于与 addr2line 映射。
|freeram
|以字节为单位的空闲 RAM,用于在调整缓冲区大小时衡量余量。
|===
逐帧跟踪与桥接流量共享 USB 全速链路,因此既降低吞吐量又淹没控制台;它是一个构建时选项(-DTRACE_FRAMES=1),仅供深层调试使用。
== 自动恢复