
- Цена: $2,29
И, да, я сейчас именно о теме электронных конструкторов, а не о хардкорной микроэлектронике и низкоуровневом программировании, до которых вряд ли когда-нибудь доберусь.
Небольшая политинформация. ATtiny85 — небольшой микроконтроллер с приличными возможностями и приятными особенностями, включая:
— 8 КБ памяти для программного кода, 512Б памяти для исполняемого кода
— 6 цифровых пинов
— 4 входа ADC
— 2 выхода PWM
— аппаратное прерывание
— частота от 1 МГц до 20 МГц
— питание от 1,8В до 5,5В (в зависимости от модификации)
— потребляемый ток — от 0,1 мкА при 1,8В в режиме максимальной экономии энергии
— выпускается (помимо прочих) в миниатюрном SOIC8 и более удобном для начинающих DIP-корпусах
Даташит на случай, если я что-то переврал.
Вообще это семейство также включает ATtiny25 и ATtiny45 — c 2КБ (128Б) и 4КБ (256Б) памяти для программного кода (исполняемого кода) соответственно. Легко видеть, почему я отдаю предпочтение ATtiny85: максимум памяти.
Казалось бы — этот контроллер идеальный кандидат в заместители Arduino, когда нужно миниатюрное, автономное и долгоживущее (от одного комплекта батарей) устройство. Например, так любимый нами всеми метеодатчик.
Но есть и минусы. Напрямую этот контроллер к компьютеру не подключить. Нужен программатор или же его аналог, в который, правда, исключительно легко превращается обычная плата Arduino. Но если вы думаете, что на этом трудности завершаются — рано радуетесь.
С программатором, предположим, да — завершаются (при условии, что есть нужная колодка). Но если его нет, а есть Arduino, то для каждой прошивки к ATtiny85 нужно подключать 6 (!) проводов. Конечно, со временем я научился делать это не за шесть операций, а всего лишь за четыре.
Но все равно очень утомительно.
И на этом фоне разработка парней из Digistump, которые придумали, как сделать так, чтобы ATtiny85 можно было без проблем программировать через USB, выглядит фантастически привлекательно. Решение, насколько я могу судить, комбинированное — немного согласующей обвязки для USB, плагин для среды Arduino и хитроумный бутлоадер (micronucleus tiny85) в микроконтроллере.
По их собственным словам, штука работает на грани возможного, поэтому не факт, что будет всегда и на все сто процентов совместима с любым USB-оборудованием. Кроме того, минималистический подход в схемотехнике выливается в полное отсутствие защит: фактически перед нами голый микроконтроллер.
Отсюда рекомендации: тщательно проверять полярность подключения питания и, по возможности, подключать контроллер к компьютеру через промежуточный USB-хаб. Чтобы в случае чего умер только хаб, а не порт компьютера.
Несмотря на эти ограничения, получившаяся у них плата Digispark оказалась настолько хороша, что стала эталонным дизайном вроде Arduino. Благо схемы, рисунок платы и софт эти товарищи раздают совершенно бесплатно.
По крайней мере только этим можно объяснить феноменальное количество всевозможных клонов Digispark в китайских магазинах. Клоны эти во многом идентичны, и различаются разве что конструктивом USB-разъема для подключения к компьютеру. Где-то он интегрирован на плату — как у плоских флешек. Где-то — реализован популярным microUSB.
Второй вариант (о котором речь) мне кажется более рациональным. Во-первых, не надо беспокоиться о наличии свободного места для подключения платы. Во-вторых, при активной отладке не нужно постоянно теребить далеко не вечный разъем у компьютера или хаба. В третьих, постоянно подключенный к компьютеру кабель USB-microUSB можно использовать и в других целях.
Итак, безымянная плата ATTINY85 Microcontroller Development Board на основе дизайна Digispark. Здесь мы видим самый обычный ATtiny85 20SU, то есть чип с рабочим диапазоном напряжений 2,7В — 5,5В и максимальной частотой 20 МГц в корпусе SOIC8.
Пинов отрезали ровно девять штук — под число контактов. Не больше, не меньше:
Размеры платы примерно 18х22 мм.
На плате, помимо прочего, размещается стабилизатор напряжения LM78L05. По этой причине питать контроллер можно двумя путями: напряжением до 5В при подключении к пину 5V и до 10В при подключении к пину Vin (через стабилизатор). В теории, если верить даташиту стабилизатора, он может выдержать и до 35В, но, вероятно, недолго.
Кроме удобного питания на плате удобно выведены и подробно подписаны пины контроллера. С лица — цифровые, со спины — по протоколу и аналоговым функциям. В общем, не заблудишься.
Для чего все это может пригодиться? Как и говорил — для прототипирования. То есть, для отладки железа и кода перед финальной сборкой. Например, с моими кривыми руками отладка может включать несколько десятков «заливок» кода в контроллер, что при упомянутых выше четырех операциях с проводами (что также подразумевает постоянный контроль правильности подключения) становится мягко говоря утомительным.
Здесь же — подключил один провод, залил код, отключил — проверяешь. Не понравилось — для изменений нужен тот же один провод. Красота.
Но для начала нужно подготовиться, что несложно. В первую очередь напаиваем гребенку для макетирования:
Теперь устанавливаем своеобразный плагин для среды Arduino (поддерживается начиная с версии 1.6.5). Процесс подробно описан в Wiki Digispark.
Открываем настройки:
Вставляем в поле Additional Boards Manager URLs строку
http://digistump.com/package_digistump_index.json
:
Переходим в меню Инструменты — Boards Manager:
В выпадающем списке Type выбираем Contributed, а затем щелкаем по Digistump AVR Boards, при этом появится кнопочка Install, которую и нажимаем:
Начнется скачивание и установка софта и драйверов. Говорим, что согласны на все:
Наконец все готово:
Выбираем рекомендованную для начинающих плату Digispark (Default — 16,5mhz):
Можно вбить классическую «мигалку» из примеров самих Digistump:
В отличие от классических плат Arduino, эту плату не нужно подключать к компьютеру перед загрузкой прошивки. Наоборот, сначала нужно запустить загрузку из среды Arduino и дождаться приглашения к подключению контроллера. Вот теперь — можно.
Таймаут на загрузку — 60 секунд:
Связано это с особым режимом работы загрузчика: при старте контроллера он ждет загрузки кода через USB в течение 5 секунд, а потом переключается в режим исполнения имеющегося в памяти контроллера кода.
Иными словами, если контроллер подключить к компьютеру до приглашения, то спустя пять секунд он начнет выполнять имеющийся код (если есть), а чтобы загрузить новый, нужно отключить и снова подключить плату к компьютеру.
Загрузка пошла:
Готово:
При прототипировании также следует иметь в виду и другие важные отличия данной платы от Arduino.
Пины 3 и 4 используются для USB, поэтому если они также необходимы в финальном проекте, то подключенную к ним для отладки периферию лучше отключать на время загрузки новой версии прошивки. Также надо иметь в виду, что к пину 3 подключен подтягивающий резистор 1,5К, необходимый для того же самого USB. Т.е. на этом пине по умолчанию будет далеко не ноль.
А помимо ограничений ATtiny85 (поддерживаются не все библиотеки и не все команды языка) есть и ограничение по памяти для программного кода, которой в версии Digispark 6КБ (а не 8КБ, как у «голого» ATtiny85).
Для тех, кто задумывается об использовании данных плат в готовых устройствах сообщаю, что на примере «Пищаля» потребление в активном режиме при напряжении 5В — около 30 мА. В режиме предположительно максимально глубокого сна, на который я способен уговорить контроллер по советам умных людей — 13,38 мА.
На примере «мигалки» получилось 30 мА при погашенном мигающем светодиоде и 35,5 мА — при горящем.
Куда контроллер тратит такую уйму энергии — пока не понимаю. Но подозреваю, что это связано с довольно высокой тактовой частотой (что, впрочем, не объясняет аппетит во время сна).
Где бы мне могла помочь эта плата? Да уже на самом деле в уйме, уйме мест, где работают крохи ATtiny85. Давайте считать.
Сигнализатор открытого дверного замка «Пищаль» (он же — «Трындец»)
Необходимость в этой штуке стала неотвратимой после того, как я стал систематически забывать закрывать входную дверь. Причин множество: сумки, мешающиеся коты, усталость в конце концов.
Пищалка, батарейка, контроллер, микрик — ничего лишнего:
Об этой проблеме я вспоминал уже когда ложился спать, а вставать лишний раз только чтобы узнать, закрыта дверь или открыта, очень не нравилось. Итог — ATtiny85 с пьезокерамической пищалкой, микропереключателем и батарейкой CR2032.
И до умопомрачения простым кодом. Который просто включает пищалку примерно через пять минут после включения контроллера. А контроллер, как несложно понять, включается микропереключателем. На микропереключатель, в свою очередь, давит ригель замка.
// Код сна: http://donalmorrissey.blogspot.ru/2010/04/sleeping-arduino-part-5-wake-up-via.html #include <avr/sleep.h> #include <avr/power.h> #include <avr/wdt.h> volatile int f_wdt=1; #define alPin 1 // пин пищалки #define alTime 38 // таймаут для сигнала в 8-секундных интервалах = alTime*8 boolean alarm = false; // флаг включения сигнала byte alTimeOut = 0; // счетчик таймаута // www.technoblogy.com/show?KX0 #define adc_disable() (ADCSRA &= ~(1<<ADEN)) // disable ADC (before power-off) #define adc_enable() (ADCSRA |= (1<<ADEN)) // re-enable ADC ISR(WDT_vect) { if(f_wdt == 0) { f_wdt=1; } } void enterSleep(void) { pinMode(alPin, INPUT); set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); sleep_mode(); sleep_disable(); power_all_enable(); pinMode(alPin, OUTPUT); digitalWrite(alPin, LOW); } void setup() { adc_disable(); /*wdt_enable(WDTO_8S); WDTCR |= _BV(WDIE); */ /*** Setup the WDT ***/ /* Clear the reset flag. */ MCUSR &= ~(1<<WDRF); /* In order to change WDE or the prescaler, we need to * set WDCE (This will allow updates for 4 clock cycles). */ WDTCR |= (1<<WDCE) | (1<<WDE); /* set new watchdog timeout prescaler value */ WDTCR = 1<<WDP0 | 1<<WDP3; /* 8.0 seconds */ /* Enable the WD interrupt (note no reset). */ WDTCR |= _BV(WDIE); pinMode(alPin, OUTPUT); tone(alPin, 300, 500); delay(500); } void loop() { if(f_wdt == 1) { if (alarm == false) { if (alTime > alTimeOut) { // если время таймаута не прошло alTimeOut++; // увеличиваем счетчик f_wdt = 0; // переключаем флаг признака просыпания } else { alarm = true; // иначе разрешаем сигнал } } } if (alarm == true) { makeSound(); } if (f_wdt == 0) { enterSleep(); } } void makeSound() { tone(alPin, 450, 250); delay(350); tone(alPin, 750, 250); delay(3000); }
То есть, пока ригель давит — контроллер выключен. Перестал давить — контроллер включился. Ну а таймаут в 5 минут сделан, чтобы пищалка не мешала при так сказать обычном использовании двери.
Иными словами, если забыть закрыть дверь, то через пять минут звуковой сигнал об этом напомнит. Ну почти как в холодильниках с подобной функцией. Отличие только в том, что «Пищаль» наблюдает за замком, а уж что там с дверью — это его не волнует.
Ну и еще нюанс: при включении (считай открытии двери) контроллер издает однократный писк. Это чтобы знать, что конструкция работает, как и батарейка.
Все идеально поместилось внутри дверной коробки. И уже неоднократно доказало свою пользу.
Автомат света «Артефакт»
Хотя я очень гордился простым решением для автосвета в гардеробе, когда свет включался контроллером по сигналу с беспроводного датчика открытия двери (из обычной беспроводной сигнализации), концепция себя изжила.
Во-первых, сама конструкция подразумевала, что нельзя просто так открывать и закрывать дверь, поскольку каждое открытие-закрытие переключало свет. То есть, необходимо было контролировать положение двери. Неудобно.
Во-вторых, со временем начались какие-то заскоки то ли у датчика двери, то ли у контроллера. В общем, срабатывать конструкция стала через раз, а причину я так и не нашел. Если что — батарейку в датчике менял, антенну крутил. Но что толку — до контроллера меньше полутора метров, а — не работает.
Стало очевидно, что дороги назад нет: надо менять сам принцип. Под это дело купил в Fix price несколько различных светильников (на корпуса), разломал дачный фонарик на солнечной батарейке под датчик света, в хозяйственном подвале приобрел батарейные отсеки под элементы ААА, в запасах откопал резервный передатчик на 433 МГц и популярный датчик движения HC-SR501.
Собрал все это воедино и вот именно здесь мне бы и понадобилась отладочная плата. Поскольку код я мучил довольно долго и сам замучился капитально, хотя задача не такая уж и сложная.
// Код сна: http://donalmorrissey.blogspot.ru/2010/04/sleeping-arduino-part-5-wake-up-via.html // Библиотека Livolo: http://forum.arduino.cc/index.php?topic=153525.0 #include <avr/sleep.h> #include <avr/power.h> #include <livolo.h> #define adc_disable() (ADCSRA &= ~(1<<ADEN)) // disable ADC (before power-off) #define adc_enable() (ADCSRA |= (1<<ADEN)) // re-enable ADC #define txPin 1 // пин передатчика #define txPowerPin 0 // питание передатчика #define intPin 2 // пин прерывания #define lightPin A2 // пин датчика света (цифровой пин 4, физический пин 3) #define sparePin 3 // пустой пин (аналоговый пин 3, физический пин 2) #define lightTimeOut 60000 // таймаут выключения света #define lightTreshold 55 // порог уровня света для включения (датчик свет/тьма) unsigned long lightTime; // счетчик таймаута до выключения света // УПРАВЛЕНИЕ Livolo Livolo livolo(txPin); void intHandler() { while(analogRead(lightPin) < lightTreshold) { lightsOn(); // выключение света, если он включен } lightTime = millis(); } void enterSleep() { pinMode(txPin, INPUT); pinMode(txPowerPin, INPUT); GIMSK |= _BV(PCIE); // Enable Pin Change Interrupts PCMSK |= _BV(PCINT2); // Use PB2 as interrupt pin adc_disable(); set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); sleep_mode(); /* The program will continue from here. */ /* First thing to do is disable sleep. */ sleep_disable(); power_all_enable(); adc_enable(); pinMode(txPin, OUTPUT); digitalWrite(txPin, LOW); pinMode(txPowerPin, OUTPUT); digitalWrite(txPowerPin, LOW); } void setup() { /* Setup the pin direction. */ pinMode(intPin, INPUT); pinMode(lightPin, INPUT); pinMode(txPin, OUTPUT); pinMode(sparePin, INPUT); digitalWrite(txPin, LOW); pinMode(txPowerPin, OUTPUT); digitalWrite(txPowerPin, LOW); attachInterrupt(0, intHandler, CHANGE); lightTime = millis(); } void loop() { if ((millis() - lightTime) > lightTimeOut) { while(analogRead(lightPin) > lightTreshold) { lightsOn(); // выключение света, если он включен delay(1500); } enterSleep(); } } void lightsOn() { digitalWrite(txPowerPin, HIGH); noInterrupts(); livolo.sendButton(6400, 80); interrupts(); digitalWrite(txPowerPin, LOW); }
Зато результат — симпатичная пирамидка, которая стоит на одной из полок и кажется, что ничего не делает. А на самом деле происходит следующее: контроллер спит до обнаружения движения, а когда его увидит, то оценивает уровень освещенности. Если света мало — включает, если много — ничего не трогает. Свет включается по радио, через выключатели Livolo.
Если нет никакого движения в течение минуты с включения света, контроллер свет выключает, потому что считает, что люди уже ушли. И после этого сразу же засыпает для экономии энергии.
Потребление в спящем режиме — около 60 мкА (с учетом датчика движения), в активном — около 8-9 мА.
Плюс конструкции по сравнению с «глупым» датчиком двери и в том, что если вдруг забудешь, что внутри автомат света и включишь свет вручную, то при открытии двери свет не выключится — такова логика «Артефакта».
Из той же логики растет недостаток: если свет включить вручную и не заходить, то свет не выключится, пока кто-то не зайдет.
Датчик протечек «Капель»
Это, если честно, не совсем удачная идея. То есть, железка удалась, но ее применение — нет. Дело в том, что в сантехническом шкафу однажды обнаружилась очень неприятная штука: ползучая протечка. Иными словами, соединение подтекало так, что это было особо незаметно, однако в итоге воды набралось столько, что стали возмущаться соседи снизу.
После этого я стал регулярно заглядывать в сантехшкаф, внимательно осматривая соединения и его «дно», и параллельно думал о том, как бы автоматизировать процесс. Теоретически меня могла бы спасти «нечеткая логика», то есть не совсем традиционное использование обычного метеодатчика.
Я рассуждал так: при протечке, которая не приводит к срабатыванию «затапливаемого» датчика, все же должна заметно повыситься влажность. И это повышение можно трактовать как сигнал к действию.
Придумано — сделано. Благодаря популярному датчику температуры и влажности ATtiny85 превратился в «Капель». Аппарат на макетной плате простоял в сантехническом шкафу две недели. Этого мне хватило для сбора статистики по влажности, которая, как оказалось, гуляет там в довольно широких пределах.
Но проблема оказалась не в пределах. Собственно, я для того и собирал статистику, чтобы понять порог срабатывания сигнализации. Проблема оказалась в том, что при натурном тесте (мокрая тряпка в сантехническом шкафу) влажность особенно не изменилась.
В общем, от «Капели» я отказался, но метеодатчик, который может передавать данные на Народный Монитор через домашний контроллер — остался. Точнее, остался код, а вот сам прибор я разобрал, поскольку стал заниматься «Артефактом».
// Код сна: http://donalmorrissey.blogspot.ru/2010/04/sleeping-arduino-part-5-wake-up-via.html // Основа для радиопротокола: http://thomasolson.com/PROJECTS/SC2262/ // Библиотека DHT: http://learn.adafruit.com/dht/downloads /* * Sketch for testing sleep mode with wake up on WDT. * Donal Morrissey - 2011. * */ #include <avr/sleep.h> #include <avr/power.h> #include <avr/wdt.h> #include "DHT.h" #define adc_disable() (ADCSRA &= ~(1<<ADEN)) // disable ADC (before power-off) #define adc_enable() (ADCSRA |= (1<<ADEN)) // re-enable ADC volatile int f_wdt=1; byte timeOut = 0; #define txPin 1 // пин передатчика #define sensorPower 4 // питание датчика #define sensorPin 3 // пин датчика #define DHTTYPE DHT22 #define txPower 0 // питание передатчика #define alPin 2 // пин пищалки #define interval 25 // число 8-секундных интервалов между отправками, около 200 секунд #define PULSESHORT 450 // confirmed #define PULSELONG 1350 #define PULSESYNC 13950 // Aproximately 10.67 times PULSELONG byte param = 0; // тип данных (температура/влажность) unsigned long myData; // данные к отправке byte i = 0; boolean leak = false; DHT dht(sensorPin, DHTTYPE); /*************************************************** * Name: ISR(WDT_vect) * * Returns: Nothing. * * Parameters: None. * * Description: Watchdog Interrupt Service. This * is executed when watchdog timed out. * ***************************************************/ ISR(WDT_vect) { if(f_wdt == 0) { f_wdt=1; } } /*************************************************** * Name: enterSleep * * Returns: Nothing. * * Parameters: None. * * Description: Enters the arduino into sleep mode. * ***************************************************/ void enterSleep(void) { digitalWrite(txPin, LOW); digitalWrite(sensorPower, LOW); // выключаем датчик digitalWrite(txPower, LOW); // выключаем передатчик pinMode(sensorPower, INPUT); pinMode(txPower, INPUT); pinMode(txPin, INPUT); pinMode(alPin, INPUT); pinMode(sensorPin, INPUT); adc_disable(); set_sleep_mode(SLEEP_MODE_PWR_DOWN); /* EDIT: could also use SLEEP_MODE_PWR_DOWN for lowest power consumption. */ sleep_enable(); /* Now enter sleep mode. */ sleep_mode(); /* The program will continue from here after the WDT timeout*/ sleep_disable(); /* First thing to do is disable sleep. */ /* Re-enable the peripherals. */ power_all_enable(); pinMode(sensorPower, OUTPUT); pinMode(txPower, OUTPUT); pinMode(txPin, OUTPUT); digitalWrite(txPin, LOW); digitalWrite(sensorPower, HIGH); // включаем датчик digitalWrite(txPower, HIGH); // включаем передатчик digitalWrite(sensorPin, INPUT_PULLUP); // включаем датчик } /*************************************************** * Name: setup * * Returns: Nothing. * * Parameters: None. * * Description: Setup for the serial comms and the * Watch dog timeout. * ***************************************************/ void setup() { /*** Setup the WDT ***/ /* Clear the reset flag. */ MCUSR &= ~(1<<WDRF); /* In order to change WDE or the prescaler, we need to * set WDCE (This will allow updates for 4 clock cycles). */ WDTCR |= (1<<WDCE) | (1<<WDE); /* set new watchdog timeout prescaler value */ WDTCR = 1<<WDP0 | 1<<WDP3; /* 8.0 seconds */ /* Enable the WD interrupt (note no reset). */ WDTCR |= _BV(WDIE); pinMode(sensorPower, OUTPUT); pinMode(txPower, OUTPUT); pinMode(txPin, OUTPUT); digitalWrite(txPin, LOW); digitalWrite(sensorPower, HIGH); // включаем датчик digitalWrite(txPower, HIGH); // включаем передатчик dht.begin(); getWeather(); } /*************************************************** * Name: enterSleep * * Returns: Nothing. * * Parameters: None. * * Description: Main application loop. * ***************************************************/ void loop() { if (leak == false) { if (f_wdt == 1) { if (timeOut > interval) { getWeather(); timeOut = 0; } else { timeOut++; } // Don't forget to clear the flag. f_wdt = 0; // Re-enter sleep mode. enterSleep(); } else { // Do nothing. } } else { digitalWrite(alPin, HIGH); delay(750); digitalWrite(alPin, LOW); } } // ПОЛУЧЕНИЕ И ОТПРАВКА ПОГОДЫ void getWeather() { delay(3000); int h = dht.readHumidity()*10; // Read temperature as Celsius (the default) int t = dht.readTemperature()*10; // Check if any reads failed and exit early (to try again). if (isnan(h) || isnan(t)) { rcSend(133331); return; } if (t < 0) { myData = 15101000 + abs(t);} // температура = код датчика + признак температуры + знак температуры + температура // myData = abs(myData);} // температура = код датчика + признак температуры + знак температуры + температура else { myData = 15100000 + t;} // температура = код датчика + признак температуры + знак температуры + температура // myData = DHT.temperature*10;} // температура = код датчика + признак температуры + знак температуры + температура rcSend(myData); delay(1000); // теперь отправляем влажность myData = 15110000 + h; // влажность = код датчика + признак влажности + влажность // myData = DHT.humidity*10; // влажность = код датчика + признак влажности + влажность rcSend(myData); // отправляем влажность if (h > 700) { // если влажность больше 70% - поднимаем тревогу rcSend(3502421); leak = true; } } static void rcSend(long remoteCode) { for(int repeat=0; repeat<4; repeat++){ for (byte i = 24; i>0; i--) { // transmit remoteID byte txPulse=bitRead(remoteCode, i-1); // read bits from remote ID // Serial.print(txPulse); switch (txPulse) { case 0: // 00 ookPulse(PULSESHORT,PULSELONG); //ookPulse(PULSESHORT,PULSELONG); break; case 1: // 11 ookPulse(PULSELONG,PULSESHORT); //ookPulse(PULSELONG,PULSESHORT); break; } // switch } // for loop ookPulse(PULSESHORT,PULSESYNC); // S(ync) // Serial.println(); } // repeat } static void ookPulse(int on, int off) { digitalWrite(txPin, HIGH); delayMicroseconds(on); digitalWrite(txPin, LOW); delayMicroseconds(off); }
Корректор не только осанки «Позиционер»
Об этой штуке я рассказывал в цветах и красках. В применении к прототипированию хочу сказать, что здесь упрощенная загрузка кода мне тоже очень бы помогла, поскольку «Позиционер» я отлаживал как-то очень долго.
Разумеется, что внутри у него все тот же самый ATtiny85.
На будущее
В планах, которые неизвестно когда сбудутся, переключатель освещения «Ротор». Очень хочется, знаете, такой портативный арт-объект с круглой, наподобие регулятора громкости в аудиотехнике, ручкой, вращение которой переключает свет во всей квартире.
То есть, крутишь в одну сторону — сначала включается фоновый свет, потом половина верхнего, потом — вторая половина верхнего. Крутишь в другую — все гаснет в обратной последовательности.
Даже заготовил концепт-версию кода под это дело. И успел вляпаться в борьбу с дребезгом контактов у энкодера, выяснив, что аппаратное подавление этой гадости очень эффективно.
Однако до финала еще далеко.
Возвращаясь к отладочной плате. Если спросите, купил бы я себе эту штуку сам, отвечу — однозначно бы купил. Собственно, уже и собирался.
Товар предоставлен для написания обзора магазином. Обзор опубликован в соответствии с п.18 Правил сайта.
(c) 2015 Источник материала.