- PVSM.RU - https://www.pvsm.ru -

В одной из своих статей я уже рассказывал, как из 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 возвращает данные компьютеру.
Хотя сейчас везде используется HTTPS, я, чтобы не усложнять статью и ухудшать производительность программы, использовал HTTP.
Например, сектор 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 запросы, которые отсылаются в пределах открытого соединения.
Для того чтобы отсылать несколько запросов, не создавая соединение для каждого запроса, необходимо при отсылке запроса передавать заголовок:
Connection: keep-alive
Поддержка keep-alive запросов может различаться от сервера к серверу. Например, на сервере с дистрибутивами Ubuntu на момент написания статья было ограничение по количеству запросов, которые можно отослать в пределах одного соединения (100 или 10 запросов), после чего сервер закрывал TCP-соединение.
Поэкспериментировать с HTTP-запросами можно даже без микроконтроллера и написания программ. Для отсылки HTTP-запросов, параметризированных различными заголовками, в Linux удобно использовать команду curl.
Проанализировать HTTP-трафик можно при помощи Wireshark. Выбираем сетевой интерфейс и ставим следующий фильтр:
http.request or http.response
Когда нужно, чтобы твоя прошивка работала на конкретном микроконтроллере, следует выбрать и разобраться со стеком TCP/IP, который будешь использовать на этом микроконтроллере. С точки зрения программиста, стек — это библиотека или драйвер, которые обеспечивают механизмы работы основных протоколов TCP/IP — TCP, IP, UDP, DHCP и другие. Чтобы глубже понять TCP/IP, можете почитать мою статью [1].
В 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, необходимый для работы моего эмулятора: отправлять Range HTTP-запросы с использованием Keep-Alive, парсить ответы и возвращать сектора диска.
Стек 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.
Самый простой способ — подключить 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 [2].
Работа над прототипом в итоге оказалась гораздо ценнее самого устройства. Именно она позволила по-настоящему глубоко разобраться в том, как на низком уровне взаимодействуют протоколы USB, TCP/IP и HTTP. Когда ты сам реализуешь стек от железа до приложения — многое встаёт на свои места совсем по-другому, чем при чтении документации и туториалов.
Реалистично оценивая результат: практической ценности у получившегося девайса в 2026 году почти нет. Производительность по современным меркам откровенно слабая, и конкурировать с любым нормальным микроконтроллером или одноплатником он не сможет. Но это и не было главной целью.
Главная ценность — в наработках и понимании. Код, структура проекта, найденные решения и даже совершённые ошибки дают отличную базу для следующих, более серьёзных экспериментов. Есть куда расти, и это самое приятное. Если хочется продолжить копать в эту тему, интересных задач хватит не на один вечер:
Использовать оба ядра RP2040.
Использовать FreeRTOS вместо baremetal.
Заменить HTTP на HTTPS.
Сделать полноценную обработку ошибок и краевых случаев.
Сделать кеширование.
Можно было, конечно, сразу попытаться сделать «взрослую» версию со всеми этими фичами. Но тогда проект рисковал превратиться в ещё один бесконечный «pet project», который так и остался бы в папке unfinished.
А сделав сначала простую, но работающую версию, получаешь очень важное ощущение: цель достижима. Именно это ощущение мотивирует сильнее всего и заставляет возвращаться к проекту снова и снова.
В общем, если вы тоже любите ковыряться в железе и протоколах «просто чтобы понять, как оно работает» — такие прототипы определённо стоят того.
© 2026 ООО «МТ ФИНАНС»
Автор: artyomsoft
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/454629
Ссылки в тексте:
[1] мою статью: https://habr.com/ru/companies/ruvds/articles/759988/
[2] репозитории на GitHub: https://github.com/artyomsoft/pico-network-cdrom
[3] Источник: https://habr.com/ru/companies/ruvds/articles/1050406/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1050406
Нажмите здесь для печати.