
В одной из своих статей я уже рассказывал, как из Raspberry Pi Pico сделать USB CD-ROM. Так как размер флеш-памяти Pico ограничен, поместить что-то серьёзное в образ, который будет находиться во флеш-памяти невозможно. Можно попробовать разместить образ на флеш-карте и использовать модуль для неё, но я нашёл более интересное с точки зрения используемых технологий решение. Эмулировать образ, который находится в интернете. Образа многих дистрибутивов Linux доступны для загрузки с сайта.
Raspberry Pi Pico не имеет сетевого интерфейса, а вот Pico W оснащена модулем Wi-Fi, благодаря которому можно получать данные напрямую с HTTP-сервера. Если реализовать чтение ISO-образа не из локальной памяти, а через сеть, то ограничение в 2 МБ флеш-памяти практически исчезает. Во внутренней памяти устройства потребуется хранить только прошивку и параметры подключения к сети.
Идея довольно проста. Компьютер обращается к виртуальному CD-ROM, отправляя команды чтения определённых секторов. Вместо того чтобы извлекать их из локального файла, микроконтроллер определяет смещение внутри ISO-образа и выполняет HTTP-запрос с заголовком Range, получая только необходимые байты.
Что потребуется
Для реализации проекта понадобится совсем немного оборудования:
Raspberry Pi Pico W,
USB-кабель,
точка доступа Wi-Fi,
любой HTTP-сервер, на котором размещён ISO-образ.
В качестве сервера можно использовать как собственный веб-сервер, так и официальный сайт дистрибутива Linux, если сервер поддерживает HTTP Range Requests.
Как всё работает
Укрупнённый алгоритм выглядит следующим образом:
Компьютер отправляет команду READ(10).
TinyUSB вызывает callback чтения.
Pico вычисляет смещение внутри ISO.
Отправляется HTTP GET с Range.
Полученные 2048 байт копируются в USB-буфер.
TinyUSB возвращает данные компьютеру.
HTTP
Хотя сейчас везде используется HTTPS, я, чтобы не усложнять статью и ухудшать производительность программы, использовал HTTP.
HTTP Range
Например, сектор CD-ROM имеет размер 2048 байт. Если операционная система запрашивает сектор с номером 12345, то необходимо получить диапазон:
12345 × 2048 = 25282560
Следовательно, HTTP-запрос будет выглядеть примерно так:
GET /debian.iso HTTP/1.1 Host: example.org Range: bytes=25282560-25284607
Если сервер поддерживает частичную выдачу файлов (HTTP Range Requests), он вернёт только один нужный сектор вместо передачи всего ISO-файла объёмом несколько гигабайт.
Практически все современные веб-серверы поддерживают диапазонные запросы. Именно благодаря этой возможности работают видеоплееры, позволяющие перематывать ролики без повторной загрузки файла. Проверить поддержку можно обычной утилитой curl:
curl -I https://example.org/image.iso
Если в ответе присутствует строка:
Accept-Ranges: bytes
то сервер подходит для использования.
Изначально HTTP подразумевал, что TCP-соединение открывалось и закрывалось для каждого HTTP-запроса. Это добавляет дополнительную нагрузку на сервер и неэффективно для получения секторов из ISO-образа. В HTTP есть так называемые keep-alive запросы, которые отсылаются в пределах открытого соединения.
Keep-alive запросы
Для того чтобы отсылать несколько запросов, не создавая соединение для каждого запроса, необходимо при отсылке запроса передавать заголовок:
Connection: keep-alive
Поддержка keep-alive запросов может различаться от сервера к серверу. Например, на сервере с дистрибутивами Ubuntu на момент написания статья было ограничение по количеству запросов, которые можно отослать в пределах одного соединения (100 или 10 запросов), после чего сервер закрывал TCP-соединение.
Отладка HTTP
Поэкспериментировать с HTTP-запросами можно даже без микроконтроллера и написания программ. Для отсылки HTTP-запросов, параметризированных различными заголовками, в Linux удобно использовать команду curl.
Проанализировать HTTP-трафик можно при помощи Wireshark. Выбираем сетевой интерфейс и ставим следующий фильтр:
http.request or http.response
Стек TCP/IP
Когда нужно, чтобы твоя прошивка работала на конкретном микроконтроллере, следует выбрать и разобраться со стеком TCP/IP, который будешь использовать на этом микроконтроллере. С точки зрения программиста, стек — это библиотека или драйвер, которые обеспечивают механизмы работы основных протоколов TCP/IP — TCP, IP, UDP, DHCP и другие. Чтобы глубже понять TCP/IP, можете почитать мою статью.
lwIP
В Raspberry Pico W канальный и физические уровни (L2 и L1) обеспечиваются чипом Infineon CYW43439 и библиотекой из Pico SDK pico_cyw43_arch_lwip_threadsafe_background. Lwip настраивается в конфигурационном файле lwipopts.h
#ifndef __LWIPOPTS_H__ #define __LWIPOPTS_H__ #ifndef NO_SYS #define NO_SYS 1 #endif // allow override in some examples #ifndef LWIP_SOCKET #define LWIP_SOCKET 0 #endif #if PICO_CYW43_ARCH_POLL #define MEM_LIBC_MALLOC 1 #else #define MEM_LIBC_MALLOC 0 #endif #define MEM_ALIGNMENT 4 #define MEM_SIZE 4000 #define MEMP_NUM_TCP_SEG 32 #define MEMP_NUM_ARP_QUEUE 10 #define PBUF_POOL_SIZE 24 #define LWIP_ARP 1 #define LWIP_ETHERNET 1 #define LWIP_ICMP 1 #define LWIP_RAW 1 #define TCP_WND (8 * TCP_MSS) #define TCP_MSS 1460 #define TCP_SND_BUF (8 * TCP_MSS) #define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) #define LWIP_NETIF_STATUS_CALLBACK 1 #define LWIP_NETIF_LINK_CALLBACK 1 #define LWIP_NETIF_HOSTNAME 1 #define LWIP_NETCONN 0 #define MEM_STATS 0 #define SYS_STATS 0 #define MEMP_STATS 0 #define LINK_STATS 0 #define LWIP_CHKSUM_ALGORITHM 3 #define LWIP_DHCP 1 #define LWIP_IPV4 1 #define LWIP_TCP 1 #define LWIP_UDP 1 #define LWIP_DNS 1 #define LWIP_TCP_KEEPALIVE 1 #define LWIP_NETIF_TX_SINGLE_PBUF 1 #define DHCP_DOES_ARP_CHECK 0 #define LWIP_DHCP_DOES_ACD_CHECK 0 #ifndef NDEBUG #define LWIP_DEBUG 1 #define LWIP_STATS 1 #define LWIP_STATS_DISPLAY 1 #endif #define ETHARP_DEBUG LWIP_DBG_OFF #define NETIF_DEBUG LWIP_DBG_OFF #define PBUF_DEBUG LWIP_DBG_OFF #define API_LIB_DEBUG LWIP_DBG_OFF #define API_MSG_DEBUG LWIP_DBG_OFF #define SOCKETS_DEBUG LWIP_DBG_OFF #define ICMP_DEBUG LWIP_DBG_OFF #define INET_DEBUG LWIP_DBG_OFF #define IP_DEBUG LWIP_DBG_OFF #define IP_REASS_DEBUG LWIP_DBG_OFF #define RAW_DEBUG LWIP_DBG_OFF #define MEM_DEBUG LWIP_DBG_OFF #define MEMP_DEBUG LWIP_DBG_OFF #define SYS_DEBUG LWIP_DBG_OFF #define TCP_DEBUG LWIP_DBG_OFF #define TCP_INPUT_DEBUG LWIP_DBG_OFF #define TCP_OUTPUT_DEBUG LWIP_DBG_OFF #define TCP_RTO_DEBUG LWIP_DBG_OFF #define TCP_CWND_DEBUG LWIP_DBG_OFF #define TCP_WND_DEBUG LWIP_DBG_OFF #define TCP_FR_DEBUG LWIP_DBG_OFF #define TCP_QLEN_DEBUG LWIP_DBG_OFF #define TCP_RST_DEBUG LWIP_DBG_OFF #define UDP_DEBUG LWIP_DBG_OFF #define TCPIP_DEBUG LWIP_DBG_OFF #define PPP_DEBUG LWIP_DBG_OFF #define SLIP_DEBUG LWIP_DBG_OFF #define DHCP_DEBUG LWIP_DBG_OFF #endif /* __LWIPOPTS_H__ */
Реализация HTTP поверх lwIP
В учебных целях я решил изобрести небольшой велосипед и реализовать минимальный функционал HTTP, необходимый для работы моего эмулятора: отправлять Range HTTP-запросы с использованием Keep-Alive, парсить ответы и возвращать сектора диска.
Стек USB
Стек USB в Raspberry Pi Pico реализуется при помощи библиотеки TinyUSB. Перед использованием её необходимо настроить в конфигурационном файле tusb_config.h и прописать в CMakeLists.txt
#ifndef _TUSB_CONFIG_H_ #define _TUSB_CONFIG_H_ // RP2040 #define CFG_TUSB_MCU OPT_MCU_RP2040 // USB Device, Full Speed #define CFG_TUSB_RHPORT0_MODE OPT_MODE_FULL_SPEED | OPT_MODE_DEVICE // EP0 #define CFG_TUD_ENDPOINT0_SIZE 64 // Классы USB #define CFG_TUD_CDC 0 #define CFG_TUD_HID 0 #define CFG_TUD_MSC 1 #define CFG_TUD_MIDI 0 #define CFG_TUD_VENDOR 0 // Размер буфера MSC #define CFG_TUD_MSC_EP_BUFSIZE 2048 #define CFG_TUD_MSC_LUN0_PDT 0x05 // Выравнивание памяти #define CFG_TUSB_MEM_SECTION #define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) #endif
Я уже описывал ранее основной алгоритм работы прошивки. А сейчас просто скажу, что отсылка запроса HTTP должна быть асинхронная, чтобы не блокировать callback для READ(10). В этом колбек tud_msc_read10_cb должен вернуть TUD_MSC_RET_ASYNC, а не количество байт, а когда уже будет получен полный HTTP-ответ, необходимо вызвать функцию tud_msc_async_io_done
Так как мы эмулируем CDROM, библиотеку TinyUSB необходимо пропатчить. Как это сделать, я писал в одной из прошлых статей. A так как текущая версия Pico SDK 2.2 использует версию TinyUSB, не поддерживающую асинхронные колбеки, необходимо подключить более свежую версию TinyUSB.
Как проверить, что эмулятор CDROM работает
Самый простой способ — подключить Pico к компьютеру с установленным современным дистрибутивом Linux с графической оболочкой. Linux должен распознать USB СDROM, и в файловом менеджере вы должны увидеть этот диск и файлы на нём.
Также в выводе команды sudo lsblk должно появиться блочное устройство srX, где X — номер.
Из устройства можно считать несколько секторов с помощью команды dd и сравнить с такими из образа ISO.
dd if=/dev/sr0 of=/dev/null bs=2048 count=3 status=progress
Или попробовать загрузиться с Pico, выбрав в BIOS Pico как устройство, с которого загружаться.
Скорость работы
Полная скорость работы зависит сразу от нескольких факторов:
скорости Wi-Fi-соединения,
производительности веб-сервера,
времени ответа сети,
размера кэша,
эффективности реализации USB Mass Storage.
Даже несмотря на то, что Pico W обладает весьма скромными ресурсами, пропускной способности Wi-Fi обычно достаточно для загрузки большинства дистрибутивов Linux. После первоначального чтения файловой системы дальнейшие обращения часто попадают в кэш как операционной системы компьютера, так и самого микроконтроллера.
Возможные проблемы
У такого решения есть и ограничения.
Во-первых, загрузка полностью зависит от доступности сети. Если соединение пропадёт, компьютер начнёт получать ошибки чтения.
Во-вторых, сервер обязательно должен поддерживать диапазонные запросы. Без них пришлось бы скачивать ISO целиком, что полностью лишает проект смысла.
Наконец, задержки в беспроводной сети значительно выше, чем при чтении с обычного USB-накопителя. Поэтому для комфортной работы желательно использовать стабильную сеть Wi-Fi с хорошим уровнем сигнала.
Выводы
Что у меня получилось, вы можете посмотреть в моём репозитории на GitHub.
Работа над прототипом в итоге оказалась гораздо ценнее самого устройства. Именно она позволила по-настоящему глубоко разобраться в том, как на низком уровне взаимодействуют протоколы USB, TCP/IP и HTTP. Когда ты сам реализуешь стек от железа до приложения — многое встаёт на свои места совсем по-другому, чем при чтении документации и туториалов.
Реалистично оценивая результат: практической ценности у получившегося девайса в 2026 году почти нет. Производительность по современным меркам откровенно слабая, и конкурировать с любым нормальным микроконтроллером или одноплатником он не сможет. Но это и не было главной целью.
Главная ценность — в наработках и понимании. Код, структура проекта, найденные решения и даже совершённые ошибки дают отличную базу для следующих, более серьёзных экспериментов. Есть куда расти, и это самое приятное. Если хочется продолжить копать в эту тему, интересных задач хватит не на один вечер:
Использовать оба ядра RP2040.
Использовать FreeRTOS вместо baremetal.
Заменить HTTP на HTTPS.
Сделать полноценную обработку ошибок и краевых случаев.
Сделать кеширование.
Можно было, конечно, сразу попытаться сделать «взрослую» версию со всеми этими фичами. Но тогда проект рисковал превратиться в ещё один бесконечный «pet project», который так и остался бы в папке unfinished.
А сделав сначала простую, но работающую версию, получаешь очень важное ощущение: цель достижима. Именно это ощущение мотивирует сильнее всего и заставляет возвращаться к проекту снова и снова.
В общем, если вы тоже любите ковыряться в железе и протоколах «просто чтобы понять, как оно работает» — такие прототипы определённо стоят того.
© 2026 ООО «МТ ФИНАНС»

