µ-Wire: USB на ATtiny10 Печать
Добавил(а) microsin   

Микроконтроллеры AVR ATtiny10 компании Atmel - удивительно мощные устройства, которые поставляются в очень маленьком корпусе SOT23 с 6 выводами. На борту у них 1 кбайт памяти программ FLASH, 32 байта SRAM, и используется уменьшенное ядро AVR, которое поддерживает только 16 регистров вместо обычных 32. Наверное, идея Atmel для этих устройств состоит в том, чтобы заменять ими маленькие логические схемы. Однако некоторые умельцы показали, что можно найти этим микроконтроллерам более солидное применение, чем изготовление продвинутых проблесковых маячков. Например, это проигрыватель chiptune-мелодий noiseplug [2] (видео), и игра Simon Says [3]. Здесь переведена статья [1], рассказывающая об удивительном устройстве USB на основе микроконтроллера ATtiny10.

Ранее ATtiny10 использовались в проекте TinyTouchbutton [4]. Это кнопка, управляющая трехцветными RGB-светодиодами WS2812. На этот раз в голову пришла задача посложнее - можно ли превратить ATtiny10 в USB-совместимое устройство? Моя цель состояла в том, чтобы реализовать подмножество функциональности проекта Little Wire [5], чтобы можно было управлять светодиодами WS2812 через USB. Это займет 3 порта GPIO (2 порта на D+ D- USB и 1 порт на данные для WS2812), что полностью совпадает с количеством свободных выводов ATtiny10.

Проект Little Wire поддерживает несколько функций для управления несколькими WS2812 LED на произвольных GPIO. Я решил упростить это поддержкой только одного LED на указанном выводе, но с сохранением совместимости с Little Wire по протоколу обмена. Это будет означать, что все готовые утилиты ПО хоста, написанные для Little Wire, будут работать с моим новым устройством. Конечное устройство можно использовать, например, как индикатор RGB LED, наподобие Blink(1) [6].

Как все тестировалось, показано на фото ниже. Из всех используемых деталей ATtiny10 оказался почти самым маленьким. Некоторые дискретные компоненты расположены на обратной стороне печатной платы, так что не удивляйтесь, что отсутствуют конденсаторы развязки, диоды Зенера (стабилитроны) и резисторы.

u-wire-test

[Как это реализовано]

Интерфейс USB на основе библиотеки V-USB требует более 1.5 килобайт FLASH для самого маленького варианта реализации, и использует больше 50 байт ОЗУ, включая стек. Потребуется дополнительный код для реализации интерфейса Little Wire и записи в WS2812. Поскольку в ATtiny10 имеется всего лишь 1 кбайт FLASH и 32 байт SRAM и только 16 из 32 регистров, получается хорошее упражнение в сокращении библиотеки V-USB, чтобы оставить в ней абсолютный минимум необходимого.

Основной шаг в сторону уменьшения объема потребляемой памяти был в переключении на реализацию V-USB без прерываний, как это сделано для USB-бутлоадера micronucleus [7]. Это позволит уменьшить расход SRAM путем опускания сохранения регистров в стеке и устранении двойной буферизации. В свою очередь, это также позволяет уменьшить размер программы. На рисунке ниже показана диаграмма времени приходящего пакета USB, в котором задан цвет светодиода, и доступ к WS2812 LED. Микроконтроллер "крадет" время для записи в WS2812 путем игнорирования следующих пакетов данных USB и вылавливания этих пакетов после того, как хост посылает их заново. Такая обработка позволяет избежать перемещения данных в памяти.

u-wire-usb-timing

[Проблемы с AVR-GCC]

Самые большие неприятности в реализации проекта доставил AVR-GCC. Даже при том, что я использовал новейшую версию тулчейна Atmel (4.8.1), поддержка ATtiny10 все еще содержит ошибки. Микроконтроллеры ATtiny 4/5/9/10 поставляются с "уменьшенным" (reduced) ядром AVR CPU. Одно из изменений ядра касается введения различных инструкций доступа к памяти. Вместо инструкций из 2 слов LDS/STS поддерживается инструкция в 1 слово LDS/STS, что позволяет осуществить доступ к SRAM за 1 такт. К сожалению, AVR-GCC просто не генерирует эти инструкции. Вместо этого используется косвенный доступ к памяти (indirect memory access) через Z-регистр. Вот что я хотел получить:

uint8_t   value0 = rq->wValue.bytes[0];
                        lds    r22,0x54

И что получил на самом деле:

uint8_t   value0 = rq->wValue.bytes[0];
344:    e4 e5           ldi    r30, 0x54
346:    f0 e0           ldi    r31, 0x00
348:    60 81           ld     r22, Z

Получается, что AVR-GCC впустую теряет 4 байта при доступе к каждой переменной. Я послал траблтикет в техподдержку Atmel, 3 месяца ждал ответа, но кажется, что для них такая проблема имеет низкий приоритет. Очень жаль, что у Atmel нет хорошей поддержки своих самых маленьких микроконтроллеров, но возможно люди на самом деле используют их только для самых простых задач.

Я нашел обходной путь решения проблемы, используя определения #define (см. также обсуждение).

#define STS(mem,in) \
 asm volatile ("sts %0,%1" : : "i" (&mem), "r" (in) : "memory" );
#define LDS(out,mem) \
 asm volatile ("lds %0,%1" : "=d" (out) : "i" (&mem));

Пример:

uint8_t   value0 = rq->wValue.bytes[0];              // 6 байт
uint8_t value0;     LDS(value0,rq->wValue.bytes[0]); // 2 байта

Что еще было сделано для уменьшения размера кода:

• Применено тактирование на частоте 12 МГц от внутреннего генератора RC, калиброванного от интервалов времени USB. Реализация V-USB на частоту 12 МГц по затратам на код меньше, чем рекомендованная версия на 12.8 МГц. Также убрана внутренняя цепь фазовой синхронизации, которая нужна для устранения неточностей синхронизации генератора RC. Удивительно, что ошибок синхронизации не наблюдалось.
• Поскольку уменьшенное ядро AVR поддерживает только 16 регистров, вручную были отображены многочисленные регистры в V-USB, чтобы избежать коллизий с GCC.
• Я удалил обработку сброса по шине USB. Это означает, что устройство не будет правильно осуществлять переэнумерацию, когда хост выдал сигнал bus reset. Это не составит проблему, если Вы подключили устройство в момент, когда компьютер уже включен и операционная система загружена.
• V-USB полностью урезана, и весь её код был вставлен в главный цикл main. Теперь поддерживается только обработка запросов SETUP, только к одной конечной точке. Все дополнительные функции были удалены. Это также уменьшило использование стека, поскольку требуется меньшее количество вызовов подпрограмм.
• Я удалил код, который поддерживает отправку ответов из SRAM. Теперь данные могут быть посланы только из FLASH. Это стало возможно поскольку нужны только однобайтные ответы для реализации протокола, не считая ответов конфигурации USB, которые фиксированы и находятся в памяти FLASH.

В результате получилось вот что:

• Flash: используется 988 из 1024 байт (96.4%)
• SRAM: используется 28 (под переменные) +2 (под стек) из 32 байт (93.8%)

Похоже, что это самое сложное firmware, которое когда либо было загружено в ATtiny10! Исходный код Вы можете найти в репозитории Github [8]. На фото ниже показано, как проект работает с новым 8-мм WS2812 LED.

u-wire-test-with-ws2812d

[Ссылки]

1. µ-Wire – USB on an ATtiny 10 site:cpldcpu.wordpress.com.
2. noiseplug - ATtiny9 microcontroller that plays a chiptune site:github.com.
3. ATtiny10 game – doing more with less site:hackaday.com.
4. TinyTouchbutton site:github.com.
5. Little Wire site:littlewire.cc.
6. Blink(1) site:blink1.thingm.com - миниатюрный цветовой индикатор событий, происходящих в окружении пользователя.
7. Micronucleus site:github.com - загрузчик кода программ через USB (USB bootloader) для микроконтроллеров ATtiny85.
8. U-wire site:github.com