
В одной из своих статей я уже рассказывал, как из 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 ООО «МТ ФИНАНС»
Комментарии (49)

xoid555
07.07.2026 12:12жалко что наши телефоны нам больше не принадлежат и мы не можем написать такое приложение, экспортирующее ISO по USB-CDROM протоколу

artyomsoft Автор
07.07.2026 12:12Есть приложение DriveDroid. Но оно требует права root. Кажется, оно только локальные ISO позволяет эмулировать. Ну и образ должен быть до 2 GB ввиду странного ограничения прописанного в ядре Linux.

0x1b6e6
07.07.2026 12:12Раньше периодически пользовался этим приложением (оно очень полезное и удобное), но на текущем телефоне (pixel 8 pro) оно не работает - драйвер, который за это отвечал, вырезали из ядра, причем не просто выключили его сборку, а вырезали сам код из дерева. Как-то я пытался восстановить его, и ядро даже собралось, но оно не захотело загружаться, и я временно забил. Надо бы вернуться к попыткам, как раз скоро отпуск…

Stanislavvv
07.07.2026 12:12Если правильно помню, 2ГБ — это для флешки в FAT32. Не ядерное ограничение всё ж. Ядерное было 4ГБ и это было 20+ лет назад.

Dimmirslr
07.07.2026 12:12Проще таскать с собой копеечную флешку с Ventoy, чем воевать с секьюрити патчами андроида ради одной фичи

BoreaAlex
07.07.2026 12:12К сожалению, вентой хорошо работает с 99,5% ПК, но спотыкается на оставшемся 0,5% - либо менюшку не грузит, либо даёт выбрать образ, но не грузит уже его. А таскать с собой кейс zalman или iodd, чтобы раз в год достать, совсем не охота, телефоном было бы удобнее в разы.

WhiteSorcerer
07.07.2026 12:12Как же не хватает современному хабру именно таких материалов. Спасибо автору!

ZavDimka
07.07.2026 12:12А почему нельзя взять что либо на линухе и сделать это просто на уровне баша?

artyomsoft Автор
07.07.2026 12:12Я делал на баше для Raspberry Pi Zero 2 W.
Вы можете посмотреть здесь https://habr.com/ru/companies/ruvds/articles/722266/.
Правда там не было загрузки образа по сети.

xkspy
07.07.2026 12:12Интересный вариант реализации, спасибо за обмен опытом.
Уже несколько лет пользуюсь "IODD-2531", выручает и работает в нескольких режимах:
- Virtual USB Optical Drive (DVD-ROM or Blu-ray ROM)
- HDD
На нашем рынке больше нет аналогов, теперь только надежда на "DIY проекты".
artyomsoft Автор
07.07.2026 12:12Да, лучше IODD и Zalman никто еще не придумал.
Сам удивляюсь, почему другие производители не делают такие устройства. Сейчас правда, когда большинство дистрибутивов операционных систем можно ставить без CD-ROM, значимость IODD стала меньше.

lv333
07.07.2026 12:12Я может конечно ошибаюсь, но возможность ставить Линукс с флешки появилась как бы не до того как эти зелманы появились. Да и шындовс начиная с 7-ки уже можно было вроде. Полтора десятка лет точно как особой нужды в сд/двд-ром нет точно для установки любых осей.

artyomsoft Автор
07.07.2026 12:12Скорее всего все появилось +/- в одно время, после бума нетбуков, у которых отсутствовал оптический привод, и нужно было как-то решать эту проблему. Но решение от IODD эмулировать оптический привод мне нравится больше загрузочных флешек и даже больше, чем Ventoy.

lv333
07.07.2026 12:12Хоть и достаточно плотно работал все это время в сфере ИТ, ни разу не ощущал необходимость в таком девайсе) Но правильные одминчеги с хабра мне регулярно напоминают шо жизни без него нет! Мол им надо ставить 100500 разных сборок зверьцд, а ещё столько же серверных! Но ок... Допустим)

artyomsoft Автор
07.07.2026 12:12Он просто удобный, и с ним быстрее и меньше вероятности, что что-то пойдет не так.

T-helper
07.07.2026 12:12а я хорошо помню что нужно было пару тройку флешек таскать, посколько где один bios видел их чипы, другой не определял совсем
zalman был слишком дорог для школьника, и я мечтал о подобной штуке тогда )
lv333
07.07.2026 12:12Прям совсем в лохматые времена... у меня была флешка на 4гб кажить, но у нее была фирменная утилита, которая позволяла откусить кусок под эмуляцию cd из iso образа которий она же и записывала... для компа это в итоге был полноценный usb cd-rom, да разумеется с одним записаним диском внутри, но этого обычно хватало и запускалось где надо... Ну, а прям еще, еще раньше.... ну там без альтернативы, только болванки сд и были... еще раньньше только дискетки) Ну и десткие диски еще вынынимали и носили самые отчаянные! Если же вернутся к реальности, то хоть и iso как формат никуда не делся, давно нет крайней необходимости именно эмулировать полноценный сд-ром или тем более, упаси господи, на болванку его писать)

CorruptotronicPervulator
07.07.2026 12:12Серверы на ASUS
%опаZ10PA — отличный пример. Ventoy на них попросту виснет прямо в меню. С iODD же (был 2531, теперь ST300) всё прекрасно.

Andrusha
07.07.2026 12:12А какие нужны аналоги? У них до сих пор выпускаются современные модели, месяц назад заказал iODD Mini Pro с Aliexpress, доставили за несколько дней. Цена на него, конечно, некислая, но диски нормального объёма теперь ещё дороже (а мой первый Zalman VE-200, помню, стоил раза в 2 больше винчестера, который я в него установил).

lv333
07.07.2026 12:12А почему не на каком нить есп32? Они как то более распространены.
Да и ещё для данной задачи наверное даже больше подходит esp32 S3... По целому ряду причин, тот же https реализовать, буфер памяти, есть платы с 8 и даже если не ошибаюсь 16мб psram.

pvvv
07.07.2026 12:12а там вроде бы местами usb не совсем полноценный, CDC внутри аппаратно гвоздями прибито?

artyomsoft Автор
07.07.2026 12:12У ESP32-WROOM-32, который самый распространенный, не имеет аппаратного USB. Теоретически конечно можно и программный использовать.

lv333
07.07.2026 12:12Esp32 s3 согласно спекам вроде как таки имеет... Но скорость там в итоге конечно же где то такая же выйдет как и на RP.

artyomsoft Автор
07.07.2026 12:12Вроде как в вашей первой, неотредактированной версии сообщения я не видел S3, хотя, может я ошибаюсь.

Dimmirslr
07.07.2026 12:12У Pico SDK документация на порядок приятнее, чем китайская лапша у Espressif

Dimmirslr
07.07.2026 12:12Вместо того чтобы мучить Pico, проще взять Pi Zero с полноценным линуксом. Поднял usb gadget mode, написал скрипт на баше и забыл про боль с парсингом пакетов

lv333
07.07.2026 12:12Та такое уже делали много раз) Я не думаю что это устройство делалось для какой либо практической пользы, скорее больше для фана.

qwe101
07.07.2026 12:12Про инет: у тебя есть только то, что есть у тебя. РКН, линия, провайдер оборвут связь в любой момент, поставить не успеешь. Ещё и зависнет. Так что, сначала скачать полностью, проверить (сам не всегда делаю), потом ставить.
А как полёт мысли и тренировка ума - хорошо.

yokotoka
07.07.2026 12:12Поддерживаю подход автора "сделай хоть как-то работающее, а потом доделай нормально". Столько проектов похоронено перед стеной перфекционизма
А почему esp32 решили не брать? Штука тоже копеечная, но вроде как с меньшим числом ограничений

pda0
07.07.2026 12:12Старый android, но уже умеющий usb host, на него postmarketOS, и туда приложение. Так ещё и интерфейс выбора и соединения с сервером сделать можно будет.
15432
И упираемся в USB Full Speed (1.2 МБ/с)
artyomsoft Автор
Да, но это больше эксперимент для изучения технологий, чем полноценное устройство.
pvvv
при большом желании из PIO можно пожалуй ULPI изобразить для внешнего hispeed phy.
15432
Чем я сейчас и занимаюсь
pvvv
Там для ~40 МБ/с есть готовые мосты ft232h, правда чем попало прикинуться не получится.
А для ~400МБ/с есть ft601, но там ног даже у rp2350b не сильно много останется для чего-то ещё помимо usb.
А чтобы просто пропихнуть побольше данных в ПК возможно проще всего через HSTX -> DVI -> MS2130 USB capture. 640x480x60x3 = 55МБ/с. там вроде YUV компрессию научились отключать, ну либо 16бит на пиксель и 36МБ/с. теоретически даже есть обратный медленный канал передачи через EDID.
з.ы. а я аж на PIO-GMII замахнулся. Если 125МГц клоки с phy получится засинхронизировать так чтобы из PIO просто непрерывно данные читать/писать каждый такт не по внешнему клоку, а по внутреннему.
15432
ft232 не интересно, как вы упомянули, нет той гибкости, HSTX, увы, односторонний, а хотелось бы и USB 2.0 Host забацать. Насчёт вашей затеи - не нужно ограничиваться 125 МГц, вторая пика спокойно на 480 МГц у меня в проектах трудится, кое-где и на 600 МГц.
ULPI, к слову, будет на 360 МГц, чтобы и первой пике перепало
15432
Про ваш GMII - если клок "от нас" и нет backpressure, то спокойно завести можно. Спасибо за идею, чем заняться после ULPI)
pvvv
Клок не совсем "от нас", там у 1000Base-T вроде всегда один мастер другой слэйв и клок вообще может и с другой стороны езернетного провода приходить. Но если его на 4 поделить и обратно в PLL pico на 4 помножить, и полярность по ходу нигде не потерять, то может и получится. Ну а на 125х3=375МГц можно и просто "wait 1 pin" вставить и по нему дальше данные читать. Но для начала попробую номинальные 125МГц без "разгона".
15432
Пробовал ULPI в этом режиме, тактировал PHY на 24 МГц в расчете, что его PLL поднимет до 60 и вернёт почти синхронный с процом клок, не получилось ничего толкового, даже на 600 МГц не успевал реагировать на backpressure. Возможно с GMII будет полегче.
Пика, кажется, не умеет во входной клок выше 50 МГц, но реально не проверял. В любом случае рекомендую вырубать синхронизаторы на PIO для экономии наносекунд на входе GPIO
pvvv
там нет такого backpressure как у ULPI, только RX клоки 125МГц - выход под которые надо засинхронизироваться с забиранием данных. И даже несовпадение RX(выход) и TX(вход) клоков внутри phy вроде как просто дополнительно буферизируется (правда как именно если вдруг TX клоки медленнее - не очень понимаю). Наружу это никак не вылазит и никому не мешает.
pvvv
С целочисленными множителем вроде бы должно быть лучше чем с дробным
Dimmirslr
Ты просто не понимаешь дзен медленной загрузки. Зато флоппик эмулировать будет вообще с запасом)