Новогодняя светодиодная мигалка

Примерно 40 минут убитого времени и в итоге имеем новогоднюю цветомузыку на работе. Использовалась та базовая платка для ТВ-стиков, модуль с OpenWRT, и немного новогоднего быдлокода.

Музыка — кавер на Despacito от 2cellos.

Всех с праздниками 😉

Больше гламура в текстолите: Базовая платка для Android TV стиков

Что-то я как-то давно не постил сюда ничего гиканутого, надо срочно исправляться. Впрочем, у меня был важный повод — запиливал базовую платку для Android TV стиков, которая приведена на картинке ниже. А под катом можно вычитать целую кучу вкусных подробностей. IMG_2585   (далее…)

USB-машинка для ЛУТа с Web-интерфейсом

Заюзать ламинатор для ЛУТа у меня было в списке запланированных дел очень и очень давно. К сожалению, ламинатора с ручной регулировкой температуры в продаже я так и не нашел, потому взял что было. И, разумеется, это «то что было» тонер даже расплавить не осилило, не то что перевести на медь.

Супер, время сделать этой машинке небольшой апгрейд

(далее…)

uISP Tool

Давненько надо было описать эту тулу, да вот никак руки не доходили. В общем, история такая. Хотелось мне иметь в кармане компактный мультитул, который и отладочную плату юзать можно. Готового не оказалось. И я решил сделать свой. За основу взял самое доступное и дешевое, что было под рукой — atmega8, добавил на борт usb через vusb, после прошелся по всем основным проектам, созданным на основе vusb и адаптировал их под созданную железку. В этом-то и плюс avr — много готовых проектов, которые надо было просто собрать в кучу.


(далее…)

proof-of-concept: 13 сервомашинок по усб с attiny2313

Недавно кое кто жаловался, что на ардуйне на mega1280 можно шевелить малым количеством серв по USB (пан и тилт для нескольких камер).
Решив сварганить подобное на спор, я взял мк, у которого не было ни аппаратного USB для связи с ПК, ни 13ти хардварных каналов сравнения (attiny2313), далее сварганил платку, написал пару сотен строк кода и вуоля:
Attiny2313 рулит 13 сервомашинками 16-битным ШИМом по USB при практически отсутствующих внешних компонентах. Опция выставлять все каналы в Z-состояние из командной строки расслабляя сервомашинку в комплекте.
(далее…)

Прошиваем AVR удаленно от ARM’a

Ладно, признаюсь, делаем робота, достаточно умного и большого. Настолько, что для мозгов потребовался ARM (SmartQ7) И микроконтроллер для грязной работы (чтобы рулить движками, собирать данные с датчиков и все это скармливать ARM’у на обработку) Но бегать за роботом с программатором или сидеть на конце пяти метрового усб шнура как-то «не комильфо»
(далее…)

AVR: Учимся писать компактный код

AVR: Учимся писать компактный код.

В свое время, когда я только осваивал AVR, великое зло по имени ардуйно меня обошло стороной, за что я очень благодарен судьбе. Но время идет, и волею судьбы мне довелось познакомиться не столько с ардуйной, сколько с ее фанатами, которые после прочтения этой статьи меня, должно быть, возненавидят.
Не останусь в долгу и я, резонно заметив, что большинство проектов ардуинщиков где используются atmega1280 или 2560 (кто больше?) реализуемы если не на 555 таймере, то уж на аттайни2313.
Скажу более, это не единственная беда, частенько люди настолько загоняют себя в рамки той же ардуйны, что выходит весьма идиотский подход: не хватает ардуйны для решения задачи? Возьми две ардуйны. Не хватает шести? Возьми десять!
Если сказанное вызывает уже ненависть ко мне, я попал в точку, дальнейшее можно не читать.

Что не так с ардуйно?

Не так крайне многое. Своя нумерация пинов, кривое апи, и многое другое. Будучи средой для чайников ардуйна легко загоняет новичка в свои рамки и вызыввает священный ужас при одной мысли о выходе куда-то за пределы, где нет привычных скетчей и есть страшный СИ.
При том, что реально код они уже пишут на С/С++ многие ардуинщики и не знают и честно говорят, что пишут на ардуйно. Это вызывает иногда улыбку, примерно как когда человек говорит, что пишет на ассеблере masm. Они здорово напоминают мне приверженцев одного такого язычка с решеткой в названии. ( С#) Оный тоже до железок добрался, так что ждем новой волны зеленых «погромистов».
Ну и, наконец, это С++. Его ниша никак не системное программирование. Здесь царство чистого СИ. Впрочем недостатки языка я не обсуждаю, скажу только, что при использовании С++ размер прошивки той же аврки пухнет как на дрожжах.

Но мы сейчас не ругаем ардуйно, а ищем конкретные выходы из положения, так ведь? Хочется и писать компактно, и чтобы удобно было.
Производители микроконтроллеров идут навстречу и в некоторых мк AT91RM9200, Stellaris Есть ROM с готовым набором костылей. В атмелевском варианте полезные штуки типа реализации xmodem’a, а у стеллариса вообще API, которое позволяет забыть что в регистры микроконтроллера надо что-то писать.
Что остается делать вымирающим фанатам красивого и компактного кода?
— Писать на ассемблере (круто, оптимально, позволяет познать дзен и научиться вселенскому терпению), но долго и не особо переносимо.
— Пользовать библиотеки типа avrlib, да таскать с собой по проектам вагон разных костылей, которые допиливать по мере необходимости.
— Или применять изыски для автогенерации кусков кода.

Про автогенерацию кода я скажу пару слов потом, когда закончу причесывать свою библиотеку для этих целей.
Сейчас же я рассмотрю парочку трюков, которые я использую для уменьшения размера кода. Начнем с простого.

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

Сколько раз в маленькой прошивке мк нам надо изменять функцию-обработчик того или иного прерывания? Можете не напрягаться, не часто. А теперь давайте оценим оверхед такой универсальности. Так как таблица векторов прерываний в AVR зашивается в Flash, то изменять ее из кода крайне нежелательно. (Можно, конечно, извратиться и перезаписывать флеш, но это скоро убьет его, так как количество циклов перезаписи ограничено).
А ардуине это реализовано так. Отдельный массив указателей в ОЗУ, куда attachInterrupt кидает указатель на функцию, которая будет вызываться. То есть как бы еще одна таблица векторов прерываний.

Иными словами, происходит нечто вроде этого:

поступило внешнее прерывание -> контроллер скакнул по адресу указанному в зашитой во флеше таблице векторов прерываний -> ардуиновская функция-обработчик загрузила в регистр адрес обработчика выставленного через attachInterrupt -> прыжок к обработчику -> мы обработали прерывание.

Давайте оценим потраченное на это место. Хотя бы для вызова функции по указателю. Для удобства сравнения перед вызовом функции ставим nop’ы.

Для теста набросаем вот такой файлик.

void (*fptr)(void)=0;
int main()
{
asm("nop");
fptr();
asm("nop");
}

Соберем его под архитектуру авр.

avr-gcc test.c -o out.elf

И отдизассемблируем полученную эльфятину.

avr-objdump -S out.elf

Это ассемблерный код, который нам сварганил gcc. Внимание на секцию мейн.

00000054

:
54: df 93 push r29
56: cf 93 push r28
58: cd b7 in r28, 0x3d ; 61
5a: de b7 in r29, 0x3e ; 62
5c: 00 00 nop
5e: 80 91 60 00 lds r24, 0x0060
62: 90 91 61 00 lds r25, 0x0061
66: e8 2f mov r30, r24
68: f9 2f mov r31, r25
6a: 09 95 icall
6c: 00 00 nop
6e: cf 91 pop r28
70: df 91 pop r29
72: 08 95 ret

Аккурат между двумя нопами инструкции, которые вызывают функцию по указателю. Запомним, и сварганим другой пример.

int callme()
{
}


int main()
{
asm("nop");
callme();
asm("nop");
}

Собираем elf, дизассемблируем, получаем:

00000072

:
72: df 93 push r29
74: cf 93 push r28
76: cd b7 in r28, 0x3d ; 61
78: de b7 in r29, 0x3e ; 62
7a: 00 00 nop
7c: eb df rcall .-42 ; 0x54
7e: 00 00 nop
80: cf 91 pop r28
82: df 91 pop r29
84: 08 95 ret

Как видно, разница одна инструкция против пяти. Четыре уходят на загрузку адреса. то есть 8 байт против двух.

Реально, конечно, оверхед не слишком большой, если функцию по указателю дергаем не так часто. Но не забываем, что каждая объявленная функция — это затраты на то, чтобы отправить в стек содержимое регистров и потом их оттуда достать. именно это и делает код в том же самом мейне, не относящийся к nop’ам.

Но не все же мы будем пихать в мейн, так? Тут опять есть выход.
Инлайны. Инлайн фунция, не заставляет компилятор загнать все регистры в стек, прыгнуть к коду, а потом вновь достать регистры из стека и вернуться при помощи ret. Она просто подставляет ее текст в место, где это надо.
В каких случаях это оптимально?
Смотрим дизасм функции callme состоящей из одного единственного nop и офигеваем.

00000054 :
54: df 93 push r29
56: cf 93 push r28
58: cd b7 in r28, 0x3d ; 61
5a: de b7 in r29, 0x3e ; 62
5c: 00 00 nop
5e: cf 91 pop r28
60: df 91 pop r29
62: 08 95 ret



На таком тесте видно, что это уже куча 14 байт оверхеда. Мелочь, если у нас сотни килобайт памяти, и в тоже время это ценное место, если у нас памяти кот наплакал — 1кб или 512 байт, на самых маленьких мк.

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

Инициализация переменных.
Допустим у нас есть массив чисел. Все числа надо инициализировать нулем, допустим в функции сброса, если они там что-то считают.
Любой программист, увидев, текст вроде:

char a[3];
a[0]=0;
a[1]=0;
a[2]=0;

Скажет китайский код, надо делать в цикле. А будет ли это реально меньше занимать памяти во флеше?
Давайте проверим.
Создаем массивчик:

char a[COUNT];

В мейн пихаем примерно такое:

int main()
{
reset();
}

А в виде reset’a делаем два разных варианта.

reset()
{
a[0]=0;
a[1]=0;
a[2]=0;
}

и

reset()
{
int i;
for (i=0; i<3; i++)
{
a[i]=0;
}
}

И что имеем на выходе?

Для начала дизассемблируем эльфятину с «китайским кодом» и видим вот такое:

00000054 :
54: df 93 push r29
56: cf 93 push r28
58: cd b7 in r28, 0x3d ; 61
5a: de b7 in r29, 0x3e ; 62
5c: 10 92 60 00 sts 0x0060, r1
60: 10 92 61 00 sts 0x0061, r1
64: 10 92 62 00 sts 0x0062, r1
68: cf 91 pop r28
6a: df 91 pop r29
6c: 08 95 ret

То есть 3 инициализации нулем превратились в 3 sts инструкции.

А теперь вариант с for

00000054 :
54: df 93 push r29
56: cf 93 push r28
58: 00 d0 rcall .+0 ; 0x5a
5a: cd b7 in r28, 0x3d ; 61
5c: de b7 in r29, 0x3e ; 62
5e: 1a 82 std Y+2, r1 ; 0x02
60: 19 82 std Y+1, r1 ; 0x01
62: 0c c0 rjmp .+24 ; 0x7c
64: 89 81 ldd r24, Y+1 ; 0x01
66: 9a 81 ldd r25, Y+2 ; 0x02
68: 80 5a subi r24, 0xA0 ; 160
6a: 9f 4f sbci r25, 0xFF ; 255
6c: e8 2f mov r30, r24
6e: f9 2f mov r31, r25
70: 10 82 st Z, r1
72: 89 81 ldd r24, Y+1 ; 0x01
74: 9a 81 ldd r25, Y+2 ; 0x02
76: 01 96 adiw r24, 0x01 ; 1
78: 9a 83 std Y+2, r25 ; 0x02
7a: 89 83 std Y+1, r24 ; 0x01
7c: 89 81 ldd r24, Y+1 ; 0x01
7e: 9a 81 ldd r25, Y+2 ; 0x02
80: 83 30 cpi r24, 0x03 ; 3
82: 91 05 cpc r25, r1
84: 7c f3 brlt .-34 ; 0x64
86: 0f 90 pop r0
88: 0f 90 pop r0
8a: cf 91 pop r28
8c: df 91 pop r29
8e: 08 95 ret

На этом месте тихонечко офигеваем: цикл нам обошелся в 40 байт или 20 инструкций. То есть до 20 элементов массива выгоднее инициализировать китайским кодом.
Смотрим сильно ли изменится если элементами будут 16ти битные числа:

00000054 :
54: df 93 push r29
56: cf 93 push r28
58: cd b7 in r28, 0x3d ; 61
5a: de b7 in r29, 0x3e ; 62
5c: 10 92 61 00 sts 0x0061, r1
60: 10 92 60 00 sts 0x0060, r1
64: 10 92 63 00 sts 0x0063, r1
68: 10 92 62 00 sts 0x0062, r1
6c: 10 92 65 00 sts 0x0065, r1
70: 10 92 64 00 sts 0x0064, r1
74: cf 91 pop r28
76: df 91 pop r29
78: 08 95 ret

В два раза больше sts при китайском коде. А теперь если воткнем цикл.

00000054 :
54: df 93 push r29
56: cf 93 push r28
58: 00 d0 rcall .+0 ; 0x5a
5a: cd b7 in r28, 0x3d ; 61
5c: de b7 in r29, 0x3e ; 62
5e: 1a 82 std Y+2, r1 ; 0x02
60: 19 82 std Y+1, r1 ; 0x01
62: 0f c0 rjmp .+30 ; 0x82
64: 89 81 ldd r24, Y+1 ; 0x01
66: 9a 81 ldd r25, Y+2 ; 0x02
68: 88 0f add r24, r24
6a: 99 1f adc r25, r25
6c: 80 5a subi r24, 0xA0 ; 160
6e: 9f 4f sbci r25, 0xFF ; 255
70: e8 2f mov r30, r24
72: f9 2f mov r31, r25
74: 11 82 std Z+1, r1 ; 0x01
76: 10 82 st Z, r1
78: 89 81 ldd r24, Y+1 ; 0x01
7a: 9a 81 ldd r25, Y+2 ; 0x02
7c: 01 96 adiw r24, 0x01 ; 1
7e: 9a 83 std Y+2, r25 ; 0x02
80: 89 83 std Y+1, r24 ; 0x01
82: 89 81 ldd r24, Y+1 ; 0x01
84: 9a 81 ldd r25, Y+2 ; 0x02
86: 83 30 cpi r24, 0x03 ; 3
88: 91 05 cpc r25, r1
8a: 64 f3 brlt .-40 ; 0x64
8c: 0f 90 pop r0
8e: 0f 90 pop r0
90: cf 91 pop r28
92: df 91 pop r29
94: 08 95 ret

Вариант с for тоже распух прилично. Таким образом можно сделать простой вывод: Если надо инициализировать что-то нулем, а памяти мало, то выгоднее набросать себе маленький костыль на ассемблере.

Пока это все примеры, которые я хотел раписать тут. Дальше мне заниматься этим было лениво.
Уменьшить размер прошивки и занимаемой оперативной памяти можно так же выгружая лишние данные в eeprom и progmem, но расписывать это мне, честно говоря, сейчас лениво.

Заключение.

Как видно из примеров, писать код занимающий мало места, требует знаний конкретной архитектуры, да и вообще штука это нетривиальная, но жутко интересная. Это вам не ПокажиСообщение(«Невосстановимая ошибка базы данных 1С»);

MegaDog, продолжаем доводить клиента до кондиции

Начнем с небольшой сказки. О текстолите.

Был у меня текстолит. Часть — который я сам купил, часть — который дед мне отдал из старых запасов, когда сам он этим безобразием занимался. Односторонний миллиметровый пошел хорошо и скоро трофейного однослойного не осталось — только тот, что я впоследствии прикупил. Но вот встала надобность травить двуслойку и достал я трофейный дедов двуслойный текстолит.

Создается впечатление, что он трофейный аж из германии в 45м, ну либо просто провалялся долго: мало того что поверхность от окисла час тер, так когда уже протравил при пайке он вонял, словно тухлая селедка приправленная не менее тухлыми носками. Ну и дороги после этого как-то подозрительно отрываются легко… В общем, трофей пришлось отложить в сторону, и достать однослойный полумиллиметровый текстолит, купленный на днях в «кварце». Хотя основную роль в необходимости перетравки платы здесь сыграло то, что я по дури навалял пару косяков в разводке, которые всплыли потом и пока паял оторвал энное количество дрожек.

Впрочем, оно и к лучшему.

На полумиллиметровом разводочка вышла просто заглядение — нигде ничего не отрывается, все ровненько, все красиво, прям как с завода, несмотря на то что все еще старый добрый ЛУТ.

(Слота под карту не видно, он снизу.)

Ну а дальше уже дело навыка — V-USB, CDC-ACM реализация, и бутлоадерHID

Девайсина содержит бутлоадер (растрепанированный BootLoderHID (Не влезало в 2кб, местами пришлось напильником допиливать)). Так что ISP может отдохнуть на полочке. Коли перед стартом накинуть джампер, то можно будет его обновить. Ну и если будет целевая аудитория — могут спокойно обновляться без ISP. Автор BootloaderHID’a приспособил его даже для работы под мерзопакостной виндой.

В штатном же режиме все плугнплей. Втыкай, находи в /dev файлик ttyACM0, ну а дальше кому и чем удобнее. Хоть миникомом, хоть чатом, хоть из кода — открывай и рули АТ командами.

—cut—

AT

OK

ATV

MegaDog Prototype I rev. 1.2a CoDeName: maniac

FW: 0.1-pre; ldr: BootLoadHID-md 0.9

S/N: lilith-0000

MEPHI Science Session 2010; Party Build. Kaf 27/Mephi

—cut—

Текст, кстати, я запихнул во внутренний еепром. Быренько наваял либку на сях, и вуоля — еепром превращается в хранилище строковых данных. Так что если хватит места и код правки еепрома останется — то любой сможет замутить себе гоблинский перевод интерфейса парой АТ команд.

Суть проста. Первые байты, не считая нулевого (его частенько затирает, потому выкидываем его), являются массивом из N указателей на NULL-trminated стринги в еепроме. Дефайним константы с номерами строк по мере надобности, и делаем хитрую функцию которая по номеру выдает нам стринг. желательно сразу в виртуальный уарт. Ну а далее я соорудил несколько AT команд и функций, которые потом можно исключить из проши дефайном.

ATSI — инициализирует нулями таблицу строк.

ATSA TEXT — добавляет текст в ближайшую свободную ячейку. фейлит если кончились ячейки или еепром.

ATSD — начинает поочередный дамп строк еепрома. выводит первую строку.

ATSN — дампит следующую строку из еепрома. Для сброса в начало используем ATSD

В принципе, то что надо, чтобы заполнять еепром скриптом на баше уже после прошивки. Конечно, реализация не самая красивая, зато зараза работает и сэкономит до 512 байт кода, если будем этот текст писать в PGMSPACE. ну и оперативы.

А вот с дисплеем вышел полный облом. mt6116, который я думал использовать по причине, что олед дисплейчик так и не дошел до меня, имеет минимальный уровень логической единицы — 3.6 вольт. а у меня питание 3.3. Первой мыслью было быренько соорудить на 74hc244 конвертер уровня (пока там придет заказ из элитана с готовым решением?) — читать из дисплея мне все равно не надо, но потом я эту затею оставил, ибо лень. Будем ждать OLED’a из раделя… ну и когда приедут из элитана запчасти можно будет собрать до кучи экстеншн с эзернетом… слотов под экстеншны через SPI я предусмотрел 2. Ну один еще можно добавить, перебросив проволочку на ISP коннекторе.