
Прошивка для Raspberry Pi Pico W, создающая беспроводной USB-адаптер Wi-Fi без драйверов с прозрачным мостом уровня L2, аутентификацией WPA2/WPA3 и консолью управления внеполосного доступа.
= 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 image::images/openrouter-banana2-rpi-pico.png[]
Прошивка работает как прозрачный мост на уровне 2 (L2), пересылающий кадры между беспроводным интерфейсом Pico W и её USB-интерфейсом. USB-интерфейс хоста принимает MAC-адрес Wi-Fi станции Pico W, что обеспечивает единый MAC и IP-адрес на всём пути.
Не требуется никакого драйвера, модуля ядра или стека беспроводной связи со стороны хоста; см. <<no-host-side-wi-fi-stack,Отсутствие стека Wi-Fi на стороне хоста>>.
Хосту нужны только встроенные драйверы cdc_ncm и cdc_acm, которые поставляются с каждой современной ОС Linux, macOS, Windows и мобильной ОС.
== Возможности
pico-usb-wifi предоставляет следующие возможности:
.Реальная ситуация image::images/slop_2.png[]
== Зачем это существует
Мне понадобился USB Wi-Fi адаптер для предстоящего проекта на встраиваемом Linux. У меня не было дешёвого USB Wi-Fi донгла, поэтому вместо того, чтобы пойти и купить его в обычном магазине за пять долларов, я потратил два дня длинных праздничных выходных и примерно миллион токенов Claude Code на создание этой прошивки.
白一百, Автор pico-usb-wifi
Google сказал, что это невозможно:
.pico-usb-wifi "Невозможно" image::images/gemini_says_not_possible.png[]
toc::[]
== Отсутствие стека Wi-Fi на стороне хоста
В отличие от USB Wi-Fi донгла, этот адаптер предоставляет хосту только интерфейс, подобный Ethernet. Pico W содержит всю беспроводную часть: радио, ассоциацию, клиент WPA2/WPA3 и регуляторный домен.
Это позволяет системам избежать установки wpa_supplicant, стека беспроводной связи cfg80211/mac80211, базы данных регуляции, а также прошивки чипсета или драйвера вендора.
Настройка учётных данных Wi-Fi происходит на устройстве через его внеполосную консоль управления, а не через какие-либо инструменты беспроводной связи хоста.
Это позволяет ограниченному или встраиваемому хосту, или хосту без драйверов беспроводной связи, или ядру вендора, в котором они отсутствуют, подключаться к беспроводным сетям, используя только универсальные драйверы класса CDC.
== Как это работает
[#fig-topology] .Топологическая схема image::images/topology.svg[Топология прозрачного моста L2,820]
USB-интерфейсу хоста присваивается MAC-адрес Wi-Fi станции Pico W, так что существует единый MAC на всём пути, и Pico W может пересылать Ethernet-кадры без изменений между USB и Wi-Fi. Станция Wi-Fi не может прозрачно мостить несколько MAC-адресов, поэтому объединение хоста и станции в один MAC — это то, что делает возможным прозрачный мост. Полное обоснование, путь данных и обработка IPv6/многоадресной рассылки описаны в <<architecture,Архитектура>>.
== Требования к хосту
Хосту требуются встроенные драйверы cdc_ncm и cdc_acm.
Оба являются частью основного ядра Linux уже более десяти лет, поэтому любое поддерживаемое ядро их включает.
Не требуется никаких внешних модулей, бинарных прошивок или драйверов вендора.
Те же драйверы класса существуют на macOS, Windows 10 и новее, Android и iOS.
[NOTE] Другие операционные системы не тестировались.
== Сборка
Проект является стандартным проектом CMake для pico-sdk. Для него требуются инструментарий ARM Embedded, CMake, система сборки (Ninja или Make), Python 3 и копия pico-sdk с подмодулями. TinyUSB и lwIP, входящие в состав pico-sdk, используются без изменений.
=== Зависимости
В системах на основе 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 содержит учётные данные по умолчанию, устанавливаемые на этапе компиляции, и этот файл игнорируется git.
Если оставить его пустым, получится образ без встроенных учётных данных, которые можно настроить во время выполнения через консоль управления (<<management-console,Консоль управления>>); если заполнить его, то задаётся сеть по умолчанию.
== Запись прошивки
Действия ниже загружают прошивку на плату.
. Удерживайте кнопку BOOTSEL при подключении платы к компьютеру через USB.
Она смонтируется как USB-накопитель RPI-RP2, обычно по адресу /run/media/<user>/RPI-RP2 или /media/<user>/RPI-RP2.
. Скопируйте pico-usb-wifi.uf2 на том.
Плата автоматически перезагрузится в прошивку.
. Подключите плату к хосту, который должен получить Wi-Fi-подключение.
== Использование на хосте с Linux
Подключите устройство к хосту и один раз настройте его учётные данные Wi-Fi через консоль управления (<<management-console,Консоль управления>>). Затем интерфейс хоста будет вести себя как любое проводное подключение в сети точки доступа.
Хост, автоматически управляющий интерфейсами (NetworkManager, systemd-networkd, dhcpcd), не требует настройки: он запускает DHCP и SLAAC через мост и получает один IPv4-адрес, IPv6-адрес, шлюз точки доступа и DNS, точно как проводной клиент.
На стороне устройства не нужно настраивать адрес или шлюз, потому что Pico не имеет таковых.
MAC-адрес интерфейса — это MAC Wi-Fi станции, поэтому сеть видит единую идентичность.
Вывод команды 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 сохраняет все профили во flash; restore отменяет несохранённые изменения, перезагружая сохранённую запись.
В таблице ниже перечислен набор команд.
[#tbl-config-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
|Сохраняет все профили и настройки во flash.
|restore
|Отменяет несохранённые изменения, перезагружая сохранённые настройки.
|===
Назначенный хосту адрес отображается в дампе состояния как host IPv4 и host IPv6, пассивно отслеживаемые из мостового трафика, так как Pico не имеет собственного адреса для отображения.
Сектор конфигурации находится в конце flash, отдельно от образа программы в начале, поэтому обычная перепрошивка pico-usb-wifi.uf2 оставляет сохранённые профили нетронутыми (полное стирание чипа удаляет их).
Исключение составляет обновление до v1.1.0: формат записи изменился для хранения нескольких профилей, поэтому запись до версии 1.1.0 отбрасывается, и сети необходимо ввести заново один раз (см. список изменений).
== Консоль отладки
Консоль отладки — это поток диагностики только для записи на второй последовательной функции CDC-ACM (обычно /dev/ttyACM1).
Она молчит, пока set debug on не будет введено в консоли управления, поэтому не потребляет ресурсов в выключенном состоянии и никогда не мешает управлению.
При включении она сообщает об изменениях ассоциации и периодическую строку статистики моста, как в сеансе ниже.
Сборка с -DTRACE_FRAMES=1 добавляет однострочную сводку каждого мостового кадра, но при нагрузке заливает консоль, поэтому по умолчанию отключена.
Поля статистики описаны в таблице ниже.
[#tbl-debug-stats] .Поля статистики отладки [cols="1,3", options="header"] |=== |Поле |Значение
|->wifi
|Кадры, пересланные от хоста к Wi-Fi.
|->host
|Кадры, пересланные от Wi-Fi к хосту.
|txdrop
|Кадры от хоста к Wi-Fi, отброшенные из-за того, что станция ещё не ассоциирована (хост повторяет попытки).
|rxdrop
|Кадры от Wi-Fi к хосту, отброшенные из-за того, что сторона USB не успевала обрабатывать.
|refl
|Кадры от Wi-Fi к хосту, отброшенные, потому что они были собственной передачей хоста, отражённой точкой доступа.
|poolfail
|Кадры от хоста к Wi-Fi, отброшенные из-за временного исчерпания пула lwIP pbuf.
|ringpk
|Пиковая глубина очереди USB-TX от Wi-Fi к хосту (из 32) с момента предыдущей строки статистики, затем сбрасывается — текущий показатель; значение, близкое к 32, означает, что USB не успевает обрабатывать данные так же быстро, как Wi-Fi доставляет их. (В отличие от максимального значения за всё время, оно снова падает после прохождения всплеска.)
|link
|Статус ссылки Wi-Fi станции: up (ассоциирована), join/down (ассоциация) или причина ошибки — badauth (неверный пароль), nonet (SSID не найден), fail.
|hangs
|Количество раз, когда сторожевой таймер восстанавливал прошивку после зависания с момента последнего холодного включения; см. <<automatic-recovery,Автоматическое восстановление>>.
|faults
|Количество сбоев (hard faults), от которых прошивка восстановилась с момента последнего холодного включения.
|faultpc
|Адрес последнего сбоя (0x00000000, если не было), для сопоставления с addr2line.
|freeram
|Свободная оперативная память в байтах, для оценки запаса при настройке размеров буферов.
|===
Покадровая трассировка использует одну и ту же линию USB Full-Speed с мостовым трафиком, поэтому она как снижает пропускную способность, так и заливает консоль; это опция времени сборки (-DTRACE_FRAMES=1), предназначенная только для глубокой отладки.
== Автоматическое восстановление
Аппаратный сторожевой таймер перезагружает устройство, если прошивка когда-либо перестанет обслуживать свой главный цикл — блокировка или тупик драйвера — так что устройство само переобнаруживается в течение нескольких секунд вместо необходимости отключать его от USB. Отдельный обработчик сбоев немедленно перехватывает ошибку CPU и записывает адрес ошибки.
Счётчики моста, предшествовавшие сбою, переживают перезагрузку в неинициализированной RAM.
При восстановлении устройство выводит однострочный отчёт RECOVERED from ... на консоль отладки с этими счётчиками (и адресом ошибки для сбоя), а работающая строка stats: содержит счётчики hangs, faults и faultpc, поэтому сбой оставляет диагностический след, несмотря на то что он очищен.
== Состояния встроенного светодиода
В таблице ниже перечислены шаблоны встроенного светодиода и их значение.
[#tbl-led] .Шаблоны встроенного светодиода [cols="1,3", options="header"] |=== |Шаблон |Значение
|Постоянно горит |Ассоциирован с точкой доступа — нормальное рабочее состояние.
|Медленно мигает — 1 Гц |Wi-Fi настроен, ассоциация или ещё не ассоциирован.
|Быстро мигает — 5 Гц |Wi-Fi не настроен; настройте его через консоль управления.
|Двойная вспышка — два быстрых импульса, затем пауза |Выполняется непрерывное сканирование; устройство без ассоциации и передаёт ближайшие точки доступа на консоль управления до нажатия клавиши.
|Выключен |USB не готов. |===
== Будущая работа
Мост работает через собственный Full-Speed USB RP2040 (12 Мбит/с), поэтому пропускная способность ограничена примерно 4-5 Мбит/с TCP-нагрузки — достаточно для приборной панели или поверхности управления, но жёсткий предел. Узкое место — USB-соединение, а не радио Wi-Fi. Некоторые направления, которые могли бы повысить её, в порядке примерных трудозатрат:
Ни одно из этих направлений не требуется для предполагаемого использования прошивки; они являются отправными точками для тех, кто хочет большей пропускной способности.
== Внешние библиотеки и благодарности
Эта прошивка собрана из нескольких внешних проектов, перечисленных в таблице ниже.
[#tbl-upstream] .Внешние компоненты [cols="1,2,1,4", options="header"] |=== |Компонент (файлы в дереве) |Исходный проект |Лицензия |Роль
|USBNet |https://github.com/mattmyne/usbnet[mattmyne/usbnet] |MIT a|Базовый модуль USB-сети, дескрипторы USB и основной скелет, расширенные здесь для Wi-Fi моста.
usb_network.c, usb_network.h - rewritten as the L2 bridgeusb_descriptors.c - modified to include composite CDC-NCM + dual CDC-ACMtusb_config.h - modified|TinyUSB |https://github.com/hathach/tinyusb[hathach/tinyusb] |MIT a|Стек устройств USB CDC-NCM и CDC-ACM, используемый как встроенный в pico-sdk. +
|pico-sdk 2.2.0 |https://github.com/raspberrypi/pico-sdk[raspberrypi/pico-sdk] |BSD-3-Clause a|Поддержка платы, система сборки и встроенные TinyUSB, lwIP и cyw43-driver.
pico_sdk_import.cmake - exact copylwipopts.h - trimmed pico_w example|TinyUSB net_lwip_webserver example
|Peter Lawrence and Ha Thach, via https://github.com/hathach/tinyusb[hathach/tinyusb]
|MIT
a|Первоначальная основа для связки USB-сети; сокращено до пути CDC-NCM. +
|lrndis |https://github.com/fetisov/lrndis[fetisov/lrndis] |MIT a|Влияние на дизайн подхода USB-сети +
Остальные исходные файлы являются оригинальными для этого проекта:
main.cconfig.cconfig.hconfig_proto.cconfig_proto.hserial_console.cserial_console.hwifi_scan.cwifi_scan.hdebug_console.cdebug_console.h== Лицензия
Этот проект лицензирован по MIT; см. link:LICENSE[LICENSE]. Внешние компоненты сохраняют свои собственные лицензии, как указано в <<upstream-libraries-and-credits,Внешние библиотеки и благодарности>>.
== Архитектура
=== Обзор
Устройство представляет собой периферийное устройство USB CDC-NCM, которое соединяет хост с Wi-Fi.
Pico W выполняет роль Wi-Fi станции и переправляет Ethernet-кадры между USB-соединением и радио.
Хост использует собственный стек IP и обладает единственной сетевой идентичностью; Pico не имеет собственного IP.
Хосту не нужно ничего, кроме встроенных драйверов cdc_ncm и cdc_acm.
=== Почему мост уровня 2 через присвоение MAC-адреса
Цель состоит в том, чтобы хост появлялся в Wi-Fi сети как обычное устройство с одним адресом, в то время как Pico оставался невидимым. Одно жёсткое ограничение физического уровня определяет, как это достигается.
Станция Wi-Fi не может прозрачно мостить несколько MAC-адресов. Когда Infineon CYW43 ассоциируется с точкой доступа в режиме станции, ассоциация предоставляет ровно один MAC-адрес, и отправляемые кадры 802.11 привязаны к этому MAC-адресу станции. Без четырёхадресных (WDS) кадров, которые также должны поддерживаться точкой доступа и быть разрешены, радио не может передавать кадры от имени других MAC-адресов за ним. Это хорошо известное ограничение: клиент Wi-Fi не может быть подключён через мост.Эта прошивка не борется с этим ограничением; она его устраняет. USB-интерфейсу хоста предписывается принять MAC-адрес Wi-Fi-станции, так что от начала до конца используется ровно один MAC. Поскольку хост и станция используют одну идентичность, Pico работает как «тупой» мост второго уровня: он пересылает Ethernet-кадры без изменений между USB и Wi-Fi, не затрагивая ничего выше второго уровня. Точка доступа видит одну обычную станцию; хост сам запускает DHCP, SLAAC и Neighbor Discovery и владеет полученными адресами.
Последствия перечислены в таблице ниже.
[#tbl-bridge-effects] .Свойства моста с принятием MAC [cols="1,3", options="header"] |=== |Свойство |Почему это так
|Один IP, которым владеет хост |Pico не присваивает себе адрес, поэтому присутствует единственная идентичность в подсети самой точки доступа, а не в частной подсети модема.
|IPv4 и IPv6 одинаково |Пересылка происходит на втором уровне, поэтому SLAAC, DHCPv6, объявления маршрутизаторов и Neighbor Discovery проходят без изменений, без привязки к версии.
|Нет NAT и перенаправления портов |Ничего не переписывается, поэтому входящие соединения достигают хоста напрямую; нечего маскировать или отображать.
|Нет стека Wi-Fi на стороне хоста
|Pico управляет ассоциацией и запросчиком, поэтому хосту не нужны wpa_supplicant, база данных регуляторных требований или беспроводной драйвер — только драйверы класса CDC.
|===
=== Путь данных
Со стороны USB TinyUSB предоставляет устройство CDC-NCM, и MAC интерфейса хоста устанавливается равным MAC станции при запуске (usb_network_set_host_mac, до перечисления).
От хоста к Wi-Fi: кадр поступает через tud_network_recv_cb, помещается в очередь и в основном цикле передаётся на радио с помощью cyw43_send_ethernet.
От Wi-Fi к хосту: обработчик input сетевого интерфейса станции заменяется, поэтому каждый кадр, полученный драйвером cyw43, передаётся мосту вместо lwIP, помещается в очередь и отправляется хосту с помощью tud_network_xmit.
На стороне USB нет IP-интерфейса lwIP, и сетевой интерфейс станции не имеет IP; lwIP обслуживает только состояние канала cyw43 и пул pbuf.
=== Модель параллелизма
Прошивка использует pico_cyw43_arch_lwip_threadsafe_background.
Wi-Fi обслуживается в фоновом контексте IRQ и асинхронно, чтобы он никогда не "голодал" USB, чей tud_task() выполняется в основном цикле.
Такое устройство имеет одно строгое следствие для моста.
TinyUSB можно использовать только из основного цикла, но кадры Wi-Fi принимаются в фоновом контексте.
Поэтому обработчик приёма Wi-Fi только помещает каждый кадр в кольцевой буфер, а основной цикл передаёт данные из этого кольца в tud_network_xmit.
Нарушение этого правила проявлялось на стороне хоста как NETDEV WATCHDOG: transmit queue timed out, с отключением USB.
Отправка кадров хоста на Wi-Fi происходит в основном цикле и удерживает cyw43_arch_lwip_begin()/cyw43_arch_lwip_end() вокруг вызова cyw43.
=== Многоадресная рассылка и IPv6
Станция по умолчанию получает только те многоадресные группы, к которым она присоединилась. Мост не запускает собственный стек IP и ничего не присоединяет, поэтому без вмешательства радио отбросило бы многоадресный трафик, от которого зависит IPv6, и IPv6 хоста не работал бы через мост. Объявления маршрутизаторов, обнаружение дублирующихся адресов и разрешение адресов — всё это использует многоадресную рассылку.
При каждой ассоциации прошивка устанавливает iovar allmulti для CYW43, поэтому станция доставляет все многоадресные кадры независимо от фильтра.
Это делается с помощью публичного cyw43_ioctl для WLC_SET_VAR, а не входом в режим монитора, поэтому формат Ethernet-кадров не изменяется.
Сеть за коммутатором с IGMP- или MLD-снупингом может всё же отсечь некоторый многоадресный трафик, который хост никогда не запрашивал; плоская домашняя точка доступа его «заливает».
=== Фильтр самоотражения
Поскольку хост использует MAC станции, многоадресный или широковещательный кадр, отправленный хостом, «заливается» точкой доступа обратно на станцию, которая теперь получает весь многоадресный трафик, и этот кадр был бы передан обратно хосту как его собственный.
Обработчик приёма отбрасывает любой кадр от Wi-Fi к хосту, чей исходный MAC совпадает с MAC станции, поскольку такой кадр может быть только собственной передачей хоста, отражённой точкой доступа.
Мост не должен отражать кадры станции обратно ей; отбрасывание учитывается как refl в отладочной статистике.
=== Две последовательные консоли
Управление и диагностика выполняются внеполосно, на двух функциях CDC-ACM одного составного USB-устройства, а не по сети. Это намеренное следствие прозрачности: сетевая сторона несёт только трафик хоста и не имеет адреса, по которому Pico мог бы отвечать.
Консоль управления (/dev/ttyACM0) запускает протокол конфигурирования и доступна сразу после перечисления USB, до поднятия Wi-Fi, поэтому устройство всегда можно настроить без какого-либо IP.
Отладочная консоль (/dev/ttyACM1) — это поток диагностики только для записи: события ассоциации, периодические счётчики моста и необязательные покадровые сводки пакетов; передаётся только при включении, поэтому не расходует ресурсы, когда выключена, и никогда не засоряет консоль управления.
Ни одна из консолей не касается сети, поэтому ни одна не генерирует трафик, который мог бы быть зарегистрирован брандмауэром хоста.
=== Хранение конфигурации
Настройки времени выполнения — до восьми профилей учётных данных Wi-Fi, индекс активного профиля, страна регулирования и флаг отладки — хранятся в одной записи в последнем секторе флеш-памяти, отдельно от образа программы в начале флеш-памяти.
Запись занимает несколько страниц флеш-памяти, поэтому save программирует весь сектор сразу (стирание всё равно происходит на весь сектор).
При загрузке запись принимается только при точном совпадении её магического числа и CRC-32 по остальной части структуры; любое несовпадение загружает значения по умолчанию, заданные на этапе компиляции.
Нет миграции с версиями: изменение структуры записи просто приводит к неудаче проверки magic/CRC и возврату к значениям по умолчанию, что приемлемо, потому что встроенное значение по умолчанию по-прежнему задаёт первый профиль. (Вот почему обновление до v1.1.0, которое расширило запись до списка профилей, отбрасывает запись до v1.1.0.)
Операция save записывает запись обратно через flash_safe_execute, который координирует стирание и программирование с другим ядром и отключает прерывания на несколько миллисекунд, необходимых для этого.
Эта запись выполняется из обработчика консоли, пока удерживается блокировка lwIP; кратковременное отключение прерываний приостанавливает фоновое обслуживание Wi-Fi, что приемлемо для редкого сохранения, инициируемого хостом.
=== Особенности аппаратного и инструментального окружения
Эти особенности RP2040, Infineon CYW43 и pico-sdk стоят реального времени отладки и их легко воспроизвести снова.
==== Фоновое обслуживание требует TinyUSB только из основного цикла
Это правило параллелизма, описанное в <<concurrency-model,Модель параллелизма>>. При фоновом обслуживании приём Wi-Fi выполняется в контексте, который не может вызывать TinyUSB, поэтому существует кольцо отложенной передачи.
==== Ассоциация — это не IP
Вспомогательная функция cyw43 cyw43_tcpip_link_status сообщает CYW43_LINK_UP только после того, как станция получает IP-адрес, а эта станция намеренно никогда его не получает.
Поэтому состояние ассоциации считывается из флага канала netif (netif_is_link_up), который мост также использует для принятия решения о пересылке.
==== Изменённая идентичность требует нового идентификатора продукта
Составное устройство, которое изменяет свой набор интерфейсов, сохраняя тот же идентификатор поставщика и продукта, может получить кэшированный дескриптор от хоста.
Идентификатор продукта выводится из включённых классов, поэтому добавление каждой функции CDC-ACM сдвигает его (cafe:4020 до cafe:4022), и хост перечитывает новую компоновку.
==== Релизные сборки делают assert пустышкой
CMAKE_BUILD_TYPE=Release определяет NDEBUG, что компилирует assert() в ничто, поэтому выделение памяти, защищённое только утверждением, проходит мимо ошибки и разыменовывает NULL.
Сбой прошивки до выполнения tud_task проявляется на стороне хоста как ошибка перечисления USB -110, тайм-аут чтения дескриптора устройства.
На пути запуска используются реальные проверки на NULL, а не утверждения.
=== Проверенная среда
Подтверждённая рабочая конфигурация использует pico-sdk 2.2.0, инструментарий GCC arm-none-eabi и встроенные в pico-sdk TinyUSB и lwIP без изменений.
Эталонная плата — Pico W (RP2040).
Ожидается, что Pico 2 W (RP2350) будет работать.
Сборка создаёт файл build/pico-usb-wifi.uf2 размером примерно 670 КБ.