Оригинал этой статьи НЕ загружается с веб-сервера, работающего на одноразовом вейпе. Если вы хотите увидеть сам сервер, то это можно сделать здесь. В остальном контент идентичен.

Предыстория

Уже пару лет я собираю коллекцию одноразовых вейпов, полученных от друзей и членов семьи. Поначалу я только извлекал аккумуляторы для «будущих» проектов (это точно не синдром Плюшкина), но в последнее время одноразовые вейпы стали гораздо более продвинутыми. Не хотел бы я быть юристом, которому придётся доказывать, что устройство с разъёмом USB C и перезаряжаемым аккумулятором можно классифицировать, как «одноразовое». К счастью, в ближайшее время я не планирую подаваться в юриспруденцию.

В прошлом году я разбирал одну из этих технологичных сосок для взрослых и заметил нечто любопытное: вместо обычной чёрной капли, которой заливают ASIC (Application Specific Integrated Circuit), я увидел небольшую интегральную схему с маркировкой «PUYA». Не буду винить читателей, если это название не вызвало у вас того же восторга, что и у меня — большинство людей никогда его не слышало. Эта компания больше всего знаменита своими флэш-чипами, но впервые я узнал о них из поста Джея Карлсона о самом дешёвом флэш-микроконтроллере. Это довольно мощные крошечные микроконтроллеры ARM Cortex-M0+.

За последний год у меня скопилось довольно много таких одноразок с PY32; это были разные модели вейпов одного производителя. Я не буду бесплатно рекламировать табачный бренд, но выражу благодарность проектировщику за маркировку на отладочных контактах!

С чем мы работаем

Чип имеет не особо информативную маркировку PUYA C642F15. Я был практически уверен, что это PY32F002A, но, потыкавшись с помощью pyOCD, заметил, что флэш-память имеет размер 24 КиБ и в устройстве есть 3 КиБ ОЗУ. Лишняя флэш-память означала, что это, скорее всего, PY32F002B, то есть сильно отличающийся чип1.

Итак, вот спецификации микроконтроллера настолько плохого, что его, по сути, сделали одноразовым:

  • Cortex M0+, 24 МГц

  • 24 КиБ флэш-памяти

  • 3 КиБ статической ОЗУ

  • периферия, которую я не буду использовать.

Возможно, взглянув на спецификацию, вы решите, что здесь особо не с чем работать. Понимаю вас: мой телефон, которому уже десять лет, едва может загружать Google, а этот микроконтроллер примерно в сто раз медленнее. С другой стороны, я вижу в нём потрясающе быстрый веб-сервер.

Выходим онлайн

Мысль о хостинге веб-сервера на вейпе пришла мне не сразу. На самом деле, я уже какое-то время экспериментировал с ними, но после написания поста о semihosting меня озарило.

Если вы не хотите читать ту статью, объясню, что semihosting — это, по сути, системные вызовы для встроенных микроконтроллеров ARM. Мы записываем некие значения/указатели в регистры и вызываем команду контрольной точки. Прикреплённый отладчик интерпретирует значения в регистрах и выполняет некие действия. Большинство людей использует такую систему просто для вывода логов из микроконтроллера, но на самом деле она двунаправленная.

Если вы постарше меня, то можете помнить эпоху до появления Wi-Fi и Ethernet — тёмные века, когда для выхода в Интернет требовались модемы. Также вы можете знать, что призраки этих модемов до сих пор с нами. На самом деле, почти все последовательные устройства USB эмулируют эти модемы: модем на 56k — это просто последовательное устройство на 57600 бод. Данные между некоторыми из этих модемов передавались по протоколу SLIP (Serial Line Internet Protocol)2.

Возможно, вас не удивит, что Linux (а после дополнительной настройки — и macOS) поддерживает SLIP. Утилита slattach способна заставить любой /dev/tty* отправлять и получать IP-пакеты. Для этого достаточно лишь передавать данные в нужном формате и предоставить виртуальный tty. На самом деле, это проще, чем вы могли подумать: pyOCD может перенаправлять весь semihosting через порт telnet. Далее можно использовать socat для соединения этого порта с виртуальным tty:

pyocd gdb -S -O semihost_console_type=telnet -T $(PORT) $(PYOCDFLAGS) &
socat PTY,link=$(TTY),raw,echo=0 TCP:localhost:$(PORT),nodelay &
sudo slattach -L -p slip -s 115200 $(TTY) &
sudo ip addr add 192.168.190.1 peer 192.168.190.2/24 dev sl0
sudo ip link set mtu 1500 up dev sl0

Отлично, теперь у нас есть «модем», но на веб-сервер это непохоже. Чтобы общаться по TCP/IP, нам нужен IP-стек. Вариантов тут много; я выбрал uIP потому что он довольно мал, не требует RTOS и его легко портировать на другие платформы. Кроме того, у него уже есть минималистичный пример HTTP-сервера.

Портировав код SLIP для использования semihosting, я получил работающий веб-сервер, но… в половине случаев. Как часто бывает с сильно оптимизированными библиотеками, uIP спроектирован для 8- и 16-битных машин, редко имеющих требования выравнивания данных в памяти. Однако на ARM в случае разыменования u16 *, остаётся лишь надеяться, что адрес чётный, в противном случае возникнет исключение. В uip_chksum предполагается выравнивание u16, но оно не предполагается в скрипте, создающем файловую систему. Параллельно я решил немного изменить структуру файловой системы, чтобы сделать её чуть более портируемой. Это был мой первый опыт работы с perl, и должен сказать, что он вполне неплохо подходит для подобных задач.

Потрясающе быстрый

Насколько же быстрым может быть веб-сервер, работающий на одноразовом микроконтроллере? Ну, поначалу он был не очень быстрым. Пинги занимали около 1,5 с с потерей 50% пакетов, а простая страница загружалась более 20 с. Так плохо, что даже забавно; на самом деле, я подумывал на этом остановиться.

Однако оказалось, что всё это время проблема заключалась в прокладке между сиденьем и рулём. Первая реализация считывает за раз целый символ, с чем связан огромный оверхед. Ранее я выполнял бенчмаркинг semihosting на этом устройстве, и получал около 20 КиБ/с, но реализация SLIP в uIP проектировалась для устройств с очень малым размером памяти, поэтому она сериализировала данные побайтово. У нас же есть целых 3 КиБ ОЗУ, поэтому я добавил кольцевой буфер для кэширования чтения с хоста и передавал его в функцию опроса SLIP. Также я разделил операции записи на группы, чтобы обеспечить возможность экранирования.

И теперь сервер стал потрясающе быстрым! Пинги занимают 20 мс, пакеты не теряются, а вся страница целиком загружается примерно за 160 мс. Система занимает почти всю ОЗУ, но я вполне могу уменьшить размеры буфера, чтобы оставалось достаточно места для запуска других задач. В репозитории проекта всё настроено для удобного баланса между задержками и объёмом используемой ОЗУ:

Область памяти       Занятый размер Размер области   % занятого
           FLASH:        5116 Б        24 КиБ          20,82%
             RAM:        1380 Б         3 КиБ          44,92%

Однако для этого блога я использовал всю ОЗУ.

Как вы могли заметить, у нас осталось всего меньше 20 КиБ (80%) на накопителе. Может, для выпуска целого React этого и не хватит, но, как видите, этого более чем достаточно для хостинга всего поста. И это не просто сервер статических страниц, на нём можно выполнять любой серверный код, если вы знаете C.

Ради развлечения я добавил конечную точку JSON API, чтобы получать количество запросов главной страницы (с последнего вылета) и уникальный ID микроконтроллера.

Ресурсы


  1. Собирая материалы для поста, я наткнулся на проект, который правильно идентифицировал эти микроконтроллеры, как PY32C642 (они практически одинаковы с 002B).

  2. В более новых модемах использовался PPP (Point-to-Point Protocol).

Комментарии (0)


  1. Keks650
    16.09.2025 06:00

    Шикарно )
    Осталось запитать его от картофелины


  1. THEOILMAN
    16.09.2025 06:00

    С doom наигрались, теперь сервера. Ждем сервак на тесте для беременности. Спс.


    1. LeXaNe
      16.09.2025 06:00

      А криптофермы ещё не запускали?


  1. Indemsys
    16.09.2025 06:00

    Чел забыл упомянуть, что свой SLIP он прогоняет через RP2040, а потом через PC чтобы достичь интернета.
    Обкурился?


  1. woodiron
    16.09.2025 06:00

    del


  1. homesoft
    16.09.2025 06:00

    Интересно, зачем в вейпе Cortex M0? Или там фирмварь на питоне, питон в виртуальной машине, а машина под гипервизором? Это же вейп, Карл!


    1. radiolok
      16.09.2025 06:00

      Потому что ПИД регулятор на мелком камне сделать намного проще чем любым другим способом. К тому же кто-то должен управлять светодиодной подсветкой :)


    1. okhsunrog
      16.09.2025 06:00

      Не Cortex M4 же) Современные микроконтроллеры на Cortex M0 отлично подходят для этого, т.к. это дёшево в разработке, дёшево в производстве, энергоэффективно


  1. domage
    16.09.2025 06:00

    Обязательный комментарий
    Обязательный комментарий


    1. iamkisly
      16.09.2025 06:00

      В некоторых вещах не стоит искать смысл, они делаются по принципу "потому что я могу"