[Из песочницы] На столбе висят три глаза, или сказ о том, что пяти ног ATtiny13 вполне достаточно

51tw-pewtw_nf5txookeohz5qga.jpeg
КДПВ «Ой, всё».

Мало шансов, что сей лонгрид станет живительным источником мудрости интеллектуалам, искушенным в тайнах гадания на картах Карно и познавшим потаенный смысл Третьей Нормальной Формы. Но если вы зачем-то трогали руками arduino, в кладовке пылится паяльник, понимаете, почему у батарейки один плюс, а у С++ два, то вас не смогут оставить равнодушными поистине волшебные и удивительные чудеса. Итак, имею удовольствие рекомендовать вам номера сегодняшнего представления бродячего цирка «Саман с Самшитом»:


  • Добавление RAM и ROM в ATtiny13!
  • Искусственный интеллект в микропроцессор — про и контра, или спящая красавица — ну она не дура ли?
  • Или все таки dura lex sed lex?
  • Как добавить ножек в ATtiny13?
  • Пару слов о пятом измерении: как впихнуть невпихуемое?
  • Распиливание напополам не-девствениц с перемешиванием содержимых половин (с гарантией восстановления).
  • Номер «Кормление страждущих» (см. более ранний случай насыщения пяти тысяч человек пятью ячменными хлебами и двумя рыбами).

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


Светофор первый или Каа принимает бой.

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

Имеющие родительский опыт, прекрасно знают, что именно в такой момент можно услышать. Тем, кто такого навыка пока не приобрел, объясню — с великой степенью вероятности сейчас родителей ошарашивают чем-то наподобие: «Папа! Нам завтра! В детский садик! Надо! Принести поделку на конкурс ёлочных игрушек! И я хочу первое место!».

Надо. Завтра. Нам. Поделку. Лучшую.

Вовлеченное интервью с Заказчиком проявляет контуры ТЗ: нам нужен светофор. И чтобы красивый и светился, как настоящий. Уже на этом этапе ряды кандидатов в ГИП проекта драматически редеют: у папы не получается обосновать идею, что прелестный, мягкий, сшитый из лоскутков или связанный спицами или крючком светофор и является воплощенным представлением мечт Заказчика: светофор должен светиться — что тут непонятного?

На страну накатывалась ночь, а у нас закипала работа.

Материал корпуса светофора — втулка от туалетной бумаги, обжатая так, чтобы получился параллелепипед. Десятирублевая монета обводилась карандашом, и затем вырезались канцелярским ножом отверстия под огни световой сигнализации (травмоопасная работа детям не доверяется).

op0fazb4pjg1z5mtam3hvudxve4.jpeg

Крышки верха и низа светофора раскроены из картона по месту. Козырьки над лампами по сделанной выкройке обводились на обыкновенной 80-ти граммовой бумаге и вырезались вручную.

rmga5uloolpi1di2umlqxn9lzmw.jpeg

Затем были приклеены на ПВА.

9xqlfftsq_fphcorsj7nimjlqo8.jpeg

Собранные конструкции вытащили во двор и покрасили аэрозольными акриловыми красками: щедро серебристой, после слегка сверху черной.

hhbmhaz7bod1ntgplrtyfeu3bgq.jpeg

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

Подсвечивать изнутри было решено парой сверхъярких белых светодиодов из щедрых закромов родины (ЩЗР). Каптерщицкая жаба наотрез отказалась выдавать папе из наличия ЩЗР еще и аж три таблеточных литиевых батарейки, а светиться от одной не соглашались светодиоды, заявляя, что они белые и яркие, и падение напряжения на них от 2.8 до 3.9 вольт. Максимум, на что удалось выторговаться с жабой — на одну батарейку АА, ферритовое колечко от дросселя сгоревшей материнки и транзистор КТ315. Поразмыслив и погуглив, папа пришлось принять предложение. Да и учитывая количество шаловливых ручек в каждой группе детского сада, идея с литиевыми элементами питания смотрелась не особо привлекательной.

Корпуса светофоров на батарее жестким запахом краски вытесняли ароматы хвои и мандаринов, папа задумчиво тыкал паяльником в канифоль, дети наматывали трансформаторы для блокинг-генераторов (дублирование экземпляров), все шло по плану…

И нет, «и вдруг» в тот вечер не случилось: всех хитростей при намотке трансформатора — сразу наматывать сложенным вдвое проводом максимально возможное количество витков. Не перепутать анод и катод светодиода, и выводы транзистора КТ315 и правильно подключить концы обмоток (или поменять их местами, если не засветилось) согласно принципиальной схеме. J1 — выключатель питания из компьютерного джампера, выведенный позже «на крышу» светофора.

Совершенно простая схема получения переменного напряжения с пиками в 3–7В от 1,5В батарейки G1 давно и широко известна.

fxonh57exbpayjclrpo3k05sazg.png

Физически принципиальная схема воплотилась вот так:

ttaq4unttq55rdeg-f0tr0ti3zq.png

В красной изоленте — алкалиновая батарейка АА.

А сам светофор в сборе — вот так.

2s5wddfdxjkcl34w1mgj_neduyg.jpeg

Утром гордая чада утащила изделие в садик, где игрушка произвела фурор и заняла центральное место на ёлке.

zzfyyckk-i-jdncvaangd1cee8u.jpeg

С первым местом на конкурсе, правда, не сложилось — крайний срок сдачи конкурсных работ, как оказалось, уже несколько дней как прошел.

Дома оставались два корпуса на подоконнике и папина зудящая неудовлетворенность решением задачи «хочу как настоящий светофор». И крутилась мысль «а можно ли полное решение уместить, скажем в 13ю тиньку, что у жабы в «ЩЗР» давно валяется? Хватит ли пяти ножек, килобайта на код и 64 байта оперативки и чтобы, как заказывалось, как настоящий»?

Так что всё это только присказка была, сказка об окончательном решении светофорного вопроса впереди.


Светофор второй, как настоящий


Используемый инструментарий, материалы и документация

Язык/фреймворк: C / Arduino 1.6/1.8.
IDE: MS Visual Studio 2012 + Visual Micro plugin + git.
CAD: DipTrace.
HW: МК ATtiny13, клон Arduino Nano на ATmega328, USB-UART на FT232R, программатор china-noname USBISP.
Технология ПП: ЛУТ.
Инструменты: Паяльник, клеевой пистолет, кухонная духовка, нож, ножницы, кусачки.
Материалы: Из «ЩЗР» — по 4 красных, желтых, зеленых выводных LED марки noname, SMD танталовый конденсатор и пара резисторов 0805 по 10к, полдюжины выводных 0,25Вт токоограничивающих резисторов MF-25, китайский повышающий DC-DC 5В преобразователь, аэрозольные баллончики с серебряной и черной краской, бумага А4, полимерная глина с распродажи.

Исходники и документы доступны на Github под лицензией MIT, коммиты исходного кода совпадают с нижеописанными итерациями прошивки, все упоминаемые пути к файлам — относительно корня репозитория.

Документация:
./docs/ATtiny13A datasheet.pdf [Спецификация на МК Atmel ATtiny13A]
./docs/ATmega328 datasheet.pdf [Спецификация на МК Atmel ATmega328]
./docs/AVR4027 — Tips and Tricks to Optimize Your C Code.pdf [Atmel AVR4027: Tips and Tricks to Optimize Your C Code for 8-bit AVR Microcontrollers]
./docs/AVR4013 — PicoPower basics.pdf [Заметки по режимам энергосбережения]

Из Arduino IDE, как из облака одеяло. Ихний езык Wiring — иезуитски тонкое издевательство над всем, что может быть свято у embed-программистов. Но Arduino, как экосистема, жил, жив и жить будет, и в силу простоты установки окружения на компьютеры под различные ОС, и потому, что они первыми дали возможность без особых затрат на хардварный программатор-отладчик, используя копеечный UART и фирменный бутлоадер, заниматься разработкой в достаточно серьёзных по начинке МК. А тут еще дядюшка Ляо, готовый продать пригоршню клонов по цене одного оригинала. То, что в старших атмегах можно отладить логику и работу программы, а потом с минимальными изменениями перенести на ту же тиньку — это тоже Довод.

Мне привычно и комфортно работать в MS VisualStudio, лишнего места для AtmelStudio или WinAVR нет и изыскивать не хочу, без аппаратного отладчика ультимативных удобств они не несут. О плагине VisualMicro, добавляющем в MS VisualStudio поддержку Arduino уже было достаточно подробно и толково написано на хабре.
Фокус » Arduino на ATtiny13» на хабре так же был рассмотрен давно и неоднократно.

Вкратце — в установленную инсталляцию среды Arduino добавить модуль MicroCore, после чего появляется возможность выбора в целевых платах МК ATtiny13.

9frmfek8wvpfsphvtyhedqiiuoy.png

Git — безальтернативен для любых, даже самых домашних проектов. Привычка несложная, но правильная: начиная работу над любой программой просто в командной строке набрать «git init» в каталоге. Встроенная в MS Visual Studio поддержка комитов в локальном репозитории не раз сэкономит нервы и время.

DipTrace, как САПР для схемотехники и печатной платы — удобный, отечественный, схематика и разводка дружелюбна к пользователю, легкое добавление пользовательских компонентов (УГО, контакты), при желании печатную плату с компонентами можно покрутить в 3D, качественная справка и обучающие уроки в комплекте. Кроме этого, мне импонирует их политика лицензирования: ограничений бесплатной для России, Украины, Республики Беларусь лицензии Non profit standard (1000 pins, 4 signal layers), в 99% случаев хватает для домашних поделок.

Мне не удалось нормально заставить заработать Arduino Nano, как программатор для тиньки, но при наличии USB ISP программатора проблема решается несложно. По-быстрому набросал командных файлов для компиляции/прошивки — в директории ./gcc в проекте.

Файл .ino (а реально он С/C++) безошибочно компилирующийся в студии «под ардуино под ATtiny13», просто подается параметром этого батника в командной строке:

>"./gcc/0_MAKE & upload.cmd" MyArduinoFile.ino

Если в этот момент воткнут ISP программатор, а к нему присоединен МК, то пройдёт и компиляция и прошивка.

>"./gcc/0_MAKE & asm.cmd" MyArduinoFile.ino

Как понятно по названию, откомпилировав, создаст ассемблерный листинг прошивки — бывает очень полезно, даже если ассемблер для AVR не самая сильная сторона навыков. Важно: При желании использовать командные файлы придется необходимо изменить внутри пути, они абсолютные.


Душа светофора


Upgrade ROM&RAM под Arduino для ATtiny13

Скелет Arduino программ (последовательно setup (){…}; loop (){…};) незримо для ардуинщика при компилировании фактически «заворачивается» в классическую С-шную int main (){setup (); loop ();}, немножк прибавляя в весе за счет накладных расходов by Arduino.

uint8_t cnt;
void setup() {
    cnt=0;
}
void loop() {
  cnt++;
}
//Program size: 164 bytes (used 16% of a 1 024 byte maximum) (0,57 secs)
//Minimum Memory Usage: 5 bytes (8% of a 64 byte maximum)

Собственно абсолютно то же самое, но без вызова функций на простом С:

uint8_t cnt;
int main(){
    cnt=0;      // < setup()
    while(1){
        cnt++;  // < loop()
    }
}
//Program size: 60 bytes (used 6% of a 1 024 byte maximum) (1,23 secs)
//Minimum Memory Usage: 1 bytes (2% of a 64 byte maximum)

Кроме уменьшения с 16% до 6% требуемой программной памяти, обратите внимание, добавилось 6% бесплатной оперативной памяти на переменных. А еще вызов любой функции до самого её завершения незримо расходует память как минимум на адрес возврата в стеке, а стек — это тоже часть физически наличествующих 64 байт оперативки.


Интеллект спящей красавицы

Обязательным признаком интеллекта, безусловно, является лень: разумное существо не станет выполнять бесполезную работу. Неинтеллектуальный процессор будет раз за разом прокручивать бесконечный цикл, бесполезно переводя энергию в тепло, и лишь изредка, при изменении внешних условий, выполнять работу по изменению состояния. Более разумный процессор, напротив, работает только тогда, когда внешние условия уже поменялись. В целях повышения интеграционного показателя разума на планете Грязь, переводим тиньку на следующую ступень интеллектуального развития: наш CPU будет большую часть времени спать, просыпаясь и работая только когда это необходимо. Работа у него — инкрементировать глобальную переменную, хранящую значение времени каждые N единиц времени. Во время сна без участия «мозга»-CPU все равно будет работать таймер, увеличивающийся во сне на единицу каждые (9.6 МГц тактов в секунду / значение делителя таймера 1024 = 9370 Гц, столько раз в секунду таймер инкрементируется) 1/9370 = 0.0001067 секунды. Но проснется ЦПУ только когда 8-ми битный таймер переполнится. А проснувшись, первым делом метнётся, как ночью в туалет, в процедуру обработки события «переполнение таймера». А после побежит по программе дальше, начиная с места, где засыпал, пока не дойдет до места в бесконечном цикле с командой «спать». Таких пробуждений у него будет 37 раз за секунду (256×0.0001067 = 0.027315; 0.027315×37 = 1.01065 ~= 1s).

Изменение скелета программы: устанавливаем тактирование и делитель таймера-счетчика (HW блока, инкрементирующего своё значение каждые 1024 тактов), при переполнении 8 бит этого счетчика — запускается обработчик прерывания переполнения, затем управление передается на следующую строчку программы после той, в которой он уснул.

Важно: переменная globalTimer определена как volatile, это означает, что изменяется она в совершенно непредсказуемое и неизвестное время, как следствие, перед любым её использованием CPU обязан сначала прочитать её актуальное значение из памяти, даже если действия именно с ней были в предыдущей строчке программы.

#include          // Для компиляции не из IDE - определения регистров
#include       // Да, будем спать
#include           // будет использоваться прерывание
volatile uint16_t  globalTimer;     //трачу два байта оперативки из 64 на глобальный таймер
// часики - переполнение таймера инкрементирует globalTimer каждые 1/37 секунды
ISR(TIM0_OVF_vect){
    globalTimer++;  // а больше ничего тут делать не надо. Проснется, сплюсует, пробежит while(1) цикл и уснет.
}
int main() {
    // Планировщик работает по схеме "а потом спи-отдыхай". 
    set_sleep_mode(SLEEP_MODE_IDLE);    //установить режим сна - см.даташит
    sleep_enable();                 // разрешаем уход в сон
    TCCR0B = _BV(CS02) | _BV(CS00); // Тактирование таймера0 - clock frequency / 1024
    TIMSK0 |= _BV(TOIE0);           // При переполнении будет вызвано прерывание overflow interrupt
    sei();                          // Глобально разрешаем обработку прерываний
    while(1){
        //......... действия основного цикла
        sleep_cpu();    //и в самом конце цикла - уходим в сон.
    }
}
//Program size: 128 bytes (used 13% of a 1 024 byte maximum) (0,86 secs)
//Minimum Memory Usage: 2 bytes (3% of a 64 byte maximum)


«У кошки 4 ноги: вход, выход, земля и питание»© жалостливая студенческая песня

Небольшое отступление, объясняющее, что за странные аббревиатуры появились в листинге.
В микропроцессорах управление блоками периферии, точно так же, как и управление CPU, осуществляется путем записи/чтения значений в/из определенных адресов пространства памяти. На примере периферийного модуля GPIO.

Уровень сигнала и тип ножки в Arduino задаются очень просто: сначала для ножки платы Arduino номер pin_number включаем режим работы на вход (или выход):

pinMode(pin_number, OUTPUT); // Был бы INPUT - был бы вход

А потом или выставляем высокое/низкое значение на ножке для выхода, или считываем — какое напряжение приходит на ногу для входа.

Value = digitalRead(pin_input_number);  //Value - присваивается HIGH или LOW (1 или 0)
digitalWrite(pin_outpit_number, Value);       // На ножке pin_outpit_number - HIGH или LOW

Для «упрощения» жизни пользователей, создатели языка Wiring пошли на некоторую подмену, используя номера, напечатанные на плате Arduino (белым на плате или в прямоугольниках цвета бледная фуксия) вместо номеров ножек микросхемы (белым по серому) или номеров принадлежности портов (черным по желтому) (что и логичнее и правильнее).

fpdiniruiiujuodrtyzrcfyljr8.png

Т.о. номер пина Arduino 7 равнозначен номеру ножки 11 или PD7 — седьмому биту порта D.
Все конструкции digitalRead, digitalWrite, pinMode и т.п., при компиляции всё равно приходят к установке режимов работы и значений в нумерации, близкой для процессора, увы, добавляя при каждом обращении к Wiring-функциям просто устрашающую кучу вызовов дополнительных действий, утяжеляя код и на порядки замедляя работу.

Управление состояниями ножки — оно еще проще, чем учит нас ардуина. Лучше, чем DIHALT объяснить режимы работы и установки портов у меня вряд ли получится.

rky8sjzln3u1k3lndv0inksdxjm.jpeg

Изнутри процессора ножки ввода-вывода порта общего назначения (GPIO) видятся сгруппированными по логическим портам, у 8-ми битных процессоров — максимум 8 ножек на один порт. У малоногой тиньки мало того, что только 6 ножек можно задействовать физически (рис. ATtiny13 pinout, белым по зеленому), с PB0 по PB6, так еще и если использовать PB6 — то исчезает возможность программирования MK без специального высоковольтного программатора. Управляется любой порт ввода-вывода общего назначения тремя регистрами: (на примере порта с именем «B») DDRB, PINB и PORTB. Регистр управления периферией — ячейка памяти, которой можно присваивать значения, или читать их. Обозначения PB0, PB1, PB2, PB3, PB4, PB5 — соответствие ножек битам байтов регистров порта B. При добавлении в начало программы #include (или даже сразу iotn13.h для тиньки), эти определения обретут номера битов (PB0 станет 0,…, PB5 — 5) в байтах значений регистров в IDE, и станет возможной компиляция без задействования фреймворка Arduino.

Важно: если программа не обращается к регистрам периферии, это то же самое, что запись в эти регистры нулевых значений. Как правило, значения по-умолчанию оставляют блок периферии в выключенном состоянии, но, например, нули в DDRx GPIO определяют, что ножки — входы. А одновременно с этим ноль в PORTx — внутренняя подтяжка НЕ включена, потенциал «где-то меж нулём и единицей», ноги чутко ловят, в какую сторону им склонится — вверх или вниз, что записать в PINx. Им вполне хватит электромагнитного поля от электропроводки, чтобы 50 раз в секунду менять состояние между 0 и 1. И на каждое переключение процессор будет затрачивать электричество, бессмысленно и не очевидно для программиста.


Пусть, например

на ножку PB2 хочу вывести HIGH, +5В, на ножку PB3 сигнал LOW, 0В, а с ножки PB0 наоборот, прочитать — какой логический уровень напряжения на ней.
DDRB присваиваю значение числа, у которого бит PB0, номер ноль (самый левый) = 0 (PB0 определится как вход), второй и третий биты = 1 (PB2, PB3 — выходы), т.е. в бинарной записи число хххх11×0, где х — что угодно, хоть 1 хоть 0 — в условиях не оговорено.

DDRB = 12;  // двоичное 000001100 в десятичном виде - 12
//режимы работы сразу всех пинов порта устанавливаются одномоментно (!)
// не надо для каждой ножки отдельно вызывать pinMode().

Чтобы установить на выводах заданные значения — присвоить PORTB число, у которого второй бит=1 (PB2 в HIGH), а третий — нулю (PB3 в LOW), остальные — не важно, хххх01хх

PORTB = 8;  //(dec)8 === (bin)000001000
//Опять же - все значения всех ножек разом одной командой установлены.

Для чтения уровня с ножки у порта есть третий и последний регистр управления PINB, в нем число, значения бит которого — это логические уровни напряжения соответствующих ножек.
Все бы хорошо, но перевод из бинарных чисел в десятичные и обратно — некоторая морока.
Как установить бит номер 3 (PB3) в единицу: просто поставить 1 на нулевое место, и сдвинуть ее 3 раза влево.
3<<00000001 === PB3<<1 // результат — 00001000
Для совсем лёгкой жизни существует макрос, _BV (x), который при компиляции заменяется на (x<<1), т.о. _BV(PB3) — устанавливает третий бит в 1.
Запись _BV (PB3) | _BV (PB2) при компиляции превратится в число 00001100 (| — логическое побитовое ИЛИ).
Важно: макрос разворачивается в число уже при компиляции, в прошивке не будет никаких битовых действий, а будет использоваться получившаяся константа.

PORTB |= _BV(PB3) | _BV(PB2); // 2 и 3 биты порта == 1, уровни напряжения на ножках PB2 и PB3 станут HIGH, остальные биты PORTB не изменяются

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

PORTB &= ~(_BV(PB3) | _BV(PB2)); // В регистре PORTB 2й и 3й биты - стали нули, LOW, остальные не изменились.

В листинге выше инициализируется иной HW блок, таймер-счетчик 0, Timer0. Он сложнее GPIO, управляется уже не тремя, а семью регистрами, подробно их значения описаны в datasheet ATtiny13, если кому-то хочется прочитать еще и по-русски, можно посмотреть назначения битов в блоге Ilya Ananev в очень похожем по функционалу таймере-счетчике 0 в старшей ATmega328.

Изменяю только те регистры, функционал которых запускает то, что мне требуется.

TCCR0B = _BV(CS02) | _BV(CS00);    // Биты с названиями CS02 и CS00 в 1 - установить тактирование таймера0 = clock frequency / 1024
TIMSK0 |= _BV(TOIE0);           // Бит номер TOIE0 в 1 - при переполнении вызвать прерывание TIM0_OVF
// в этом контексте равнозначно и TIMSK0 = _BV(TOIE0);


ГОСТ или закон есть закон.

Сейчас уже не те старые жестокие времена, когда пренебрежение Государственным Стандартом являлось уголовным преступлением. И тем не менее, большинство людей не подозревает, насколько высоко влияние регулирования стандартами привычных повседневных явлений и вещей. Показателен неочевидный для не-технологов машиностроения пример, что попытка изготавливать автомат калашникова по более точным, с меньшими допусками при изготовлении деталей, нормам приведёт к (внезапно!) ухудшению его характеристик.

Светофор от цветомузыки отличается те, что режимы его работы диктуются государственным стандартом. Актуальные порядок и длительность чередования сигналов светофора находятся в документе «Распоряжение ФДА от 27.02.13» согласно 7 раздела ГОСТа Р 52289–2004 в части режимов работы светофоров, и полностью соответствует международной Конвенции о дорожных знаках и сигналах.
Сигналы чередуются в такой последовательности (одна из разрешенных): красный, красный с желтым, зеленый, зеленый мигающий, желтый, красный.

»*При этом длительность сигнала «красный с желтым» рекомендуется устраивать не более 2 с, длительность желтого сигнала — 3 с.

В режимах работы светофорной сигнализации с использованием светофоров рекомендуется предусматривать мигание зеленого сигнала в течение 3 с непосредственно перед его выключением с частотой 1 миг/с.*»

Опираясь на данный документ, добавлю определения длительности периодов последовательности (как и в большом светофоре, перпендикулярные направления имеют различные длительности разрешающего/запрещающего сигнала — PERIOD_0 и PERIOD_4, длительность остальных состояний — по ГОСТу).

#define ONE_SECOND  37      // количество переполнений счетчика в 1 секунду
#define QT_SECOND   9       // четверть секунды

#define PERIOD_FLASH_GREEN  QT_SECOND           //период мигания зеленым цветом (четверть сек) - перед переключением в желтый
#define PERIOD_FLASH_YELLOW ONE_SECOND * 1      //период мигания желтым цветом - регулировка светофором отключена - секунды
                                                        // север --- восток
#define PERIOD_0    ONE_SECOND * 10 //R G R G   0. красный --- зеленый  (10 сек)
#define PERIOD_1    ONE_SECOND * 3  //R g R g   1. красный --- зеленый мигающий (3 сек)     
#define PERIOD_2    ONE_SECOND * 1  //R Y R Y   2. красный --- желтый (1 сек)
#define PERIOD_3    ONE_SECOND * 2  //RY Y RY Y 3. красн+желтый --- желтый (2 сек) 
#define PERIOD_4    ONE_SECOND * 7  //G R G R   4. зеленый --- красный  (7 сек)
#define PERIOD_5    ONE_SECOND * 3  //g R g R   5. зеленый мигающий --- красный(3 сек)
#define PERIOD_6    ONE_SECOND * 1  //Y R Y R   6. желтый --- красный (1 сек)
#define PERIOD_7    ONE_SECOND * 2  //Y RY Y RY 7. желтый --- красный+желтый (2 сек)

И опишу структуру, которая будет хранить данные сигналов светофора, их длительности и мигании.

typedef struct{
    const uint8_t ddr_val_0;    // DDRB value - в горящем состоянии
    const uint8_t port_val_0;   // PORTB value   - в горящем состоянии
    const uint8_t ddr_val_1;    // DDRB value  - в состоянии "мигания", если такое есть
    const uint8_t port_val_1;   // PORTB value - аналогично
    const uint16_t flash_period;    // period of flashing - переключение между _val_1 и _val_0
    const uint16_t signal_period;   // period of this lighting state
}lightSignalization;            // состояние огней светофора, _0 и _1 - состояния при мигании, flash_long - время переключения мигания
//Если flash_period == 0, значит мигания нет, использовать только _val_0.
//Если signal_period == 0, значит переключения на следующее значение не лимитировано по времени.

Размер исходника вырос, но размер скомпилированной прошивки всё те же 128 байт — добавились лишь определения для компилятора, которые в программе пока не используются.


Как найти в ATtiny13 восемь (или девять?) пинов ввода-вывода

По данной матрице периодов, и учитывая, что направления север-юг и запад-восток у светофора по управлению равнозначны, естественный вывод: для управления огнями необходимо и достаточно 6 сигналов управления. Когда-то хабровчанин denvo в статье «Мало выводов? Используем RESET» при решении сходной задачи светофора ограничился 5-ю выводами для управления сигнализацией светофора, сделав управление желтым цветом общим, но в случае моей матрицы видно, что желтыми сигналами необходимо управлять раздельно, так что все-таки ножек нужно шесть.

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

Хорошо бы еще кнопку выключения — чтобы не тратилась, значится, зазря энергия, итого уже 8, нес па?

А еще, чисто по-человечески, не хотелось бы задействовать вывод «reset», PB6, чтобы не терять возможность внутрисхемного перепрограммирования.

Ведь что есть человек, философски говоря? Человек, товарищи, есть хомо сапиенс, который может и хочет. Может, эта, всё, что хочет, а хочет всё, что может.

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

Надо решить задачу — как добавить недостающие ноги.

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

Схемотехнически можно управлять двумя зелеными ветками одним пином МК:

hw6_6aeekqqdlt7a0cqnk9kf2pa.png

Подача 1 на управляющий выход PORTB |= _BV (GREEN_PIN) — включает левую ветку (север и юг) светодиодов, а подача 0 т.е. PORTB &= ~(_BV (GREEN_PIN)) — правую (восток и запад).
А что делать, когда необходимо выключить обе ветки, когда ни один зеленый не горит?
А просто ранее определенный выход (DDRB |= _BV (GREEN_PIN)) переопределить как вход, DDRB &= ~(_BV (GREEN_PIN)), GREEN_PIN в состоянии Hi-Z не привносит никакого потенциала в цепочку. Последовательное падение на четырех светодиодах (2.2В * 4) будет превышать имеющиеся 5В, и напряжения для свечения не хватит.

Управление красным ярусом сигналов вполне укладывается в ту же самую логику.
И только желтый ярус должен иметь отдельные управляющие сигналы для нормальных сторон — т.к. есть еще и режимы, когда выключены все желтые сигналы, и когда все выключены.


Конечно, вру, не так уж обязательно и должен

Можно, вполне можно реализовать управление и желтым ярусом также одним проводом по той же схеме подключения. Если чуть смухлевать, организовав импульсное управление — включая и выключая сигналы поочередно, для глаза 37/2=18 Гц не будут выглядеть постоянным свечением, но можно просыпаться в цикле не с такими длинными паузами, и переключать гораздо чаще. Но:
а) идея не моя, самому в голову не пришло, не вспомнилось, подсказал человек, гораздо более опытный в схемотехнике;
б) усложнение кода — не просто из справочной таблицы назначать значения регистров, но еще и реализовать режим «быстрого переключения» или запуска аппаратного ШИМ для ситуации 4х желтых лампочек;
в) изменение яркости, когда после пары желтых начинают светить 4 (переходы PERIOD_2→PERIOD_3 и PERIOD_6→PERIOD_7);
г) использование 4 ног на управление всеми световыми сигналами и так освобождает достаточно ресурсов для реализации остальных хотелок человека желающего.

Окончательная принципиальная схема.

35inymyguij4kl6qnsuckgz_ukq.png

// PINB === 0 0 0 g r y0 btt y1
#define RED_PIN     PB3     // OUT: 1 - "север-юг" красный, 0 - "запад-восток" , IN - ни один, выключен, подтяжку не(!) включать
#define YELLOW0_PIN PB2     // OUT: 1 - желтый "север-юг" 
#define YELLOW1_PIN PB0     // OUT: 1 - желтый "запад-восток" 
#define GREEN_PIN   PB4     // OUT: 1 - "север-юг" зеленый, 0 - "запад-восток" , IN - ни один, выключен, подтяжку не(!) включать

#define RED     _BV(RED_PIN)        // _BV - сдвиг влево единицы на количество(), 1<

Кнопка — на последнем оставшемся пине. Если распознавать длительность нажатия, короткое как сигнал для переключения режимов работы светофора с регулируемого на нерегулируемый, а длинное — как сигнал на включение и выключение, закроем оставшиеся задачи ТЗ.

#define BUTTON_PIN      PB1
#define BUTTON_ON  !(PINB & _BV(BUTTON_PIN))    //( (PINB & _BV(BUTTON_PIN)) == 0)  // условие "кнопка нажата" - на кнопке LOW
#define BUTTON_OFF (PINB & _BV(BUTTON_PIN))     // условие "не нажата" - на пине кнопки HIGH, (пин на схеме притянут к питанию)

Читатели Гарри Поттера на этом месте могут возразить, что заклинание Чарлиплексинг, позволяет управляя n ножками зажигать (и тушить) n (n−1) = n²−n светодиодов. Для 4 пинов получим 12 управляемых светодиодов, то есть любым светодиодом светофора возможно будет управлять независимо. Индикация сигналов «да нет, наверное» или «ой всё, делай что хочешь» на произвольной стороне светофора превращается в тривиальную задачу.


Схемотехника варианта чарлиплексинга для 12 LED

oqsnlertvho7jlbhqt5ehp_fcks.png

***Расплата — усложнение программного кода, исчезает красота переключения режимов горения просто присвоением значений DDRx|PORTx, работа в импульсном режиме на высокой частоте, т.к. постоянство свечения в чарлиплексинге это физиологическая иллюзия. Регулировка падения напряжения на разных цепочках принесет немало сюрпризов — три цвета диодов, это три варианта рабочих токов и падений напряжения, обеспечивать сравнимую яркость придется подбором скважности для различных цветов.

И все эти излишние сложности не дают никаких ключевых преимуществ именно для светофора по сравнению с вариантом управления ярусами на средней точке.***


Пару слов о пятом измерении: как впихнуть невпихуемое?

Любое рабочее состояние светофора полностью определяется вышеописанной структурой типа lightSignalization. Массив вышеописанных структур lightSignalization содержит данные по всем возможным состояниям режимов работы. Элементы массива с 0 го по 7й регулирующие сигналы, 8й — мигающий желтый, и 9й — режим, где все «лампочки» у светофора выключены. В дефайнах определены номера элементов, но обязательным должны быть только значения номеров с 0 по 7, их номероместо используется в логике программы. Да, я знаю, что тащить данные в код непристойно. Но альтернативой работы с 3х битным циклическим счетчиком (от 0 до 7) для 8 ми состояний стандартного к-ж-з режима являются дополнительные проверки на достижения максимальных и минимальных значений режима при увеличении. Любые ветки if-ов и снижают быстродействие, и раздувают объём кода. Поэтому назову это красивой непристойностью, практически светофорной эротикой.

lightSignalization traffic_signals[] = {// Порядок чередования сигналов
 // {DDRB0, PORTB0,   DDRB_when_flashingif, PORTB_when_flasingif (if flashing),   continous of half-period flashing,   continous curr mode runing}
    {RED|GREEN, RED,   0, 0,   0, PERIOD_0},                    // R G R G
    {RED, RED,  RED|GREEN, RED,   QT_SECOND, PERIOD_1},     // R g R g - flash east green
    {RED|YELL1, RED|YELL1,   0, 0,   0, PERIOD_2 },         // R Y1 R Y1
    {RED|YELL0|YELL1, RED|YELL0|YELL1,  0, 0,   0, PERIOD_3 },  // RY0 Y1 RY0 Y1
    {RED|GREEN, GREEN,   0, 0,   0, PERIOD_4},              // G R G R
    {RED|GREEN, GREEN,   RED, 0,    QT_SECOND, PERIOD_5 },  // g R g R - flash nord green
    {RED|YELL0, YELL0,   0, 0,   0, PERIOD_6},                  // Y0 R Y0 R
    {RED|YELL0|YELL1,  YELL0|YELL1,   0, 0,   0, PERIOD_7  },       // Y0 RY1 Y0 RY1

    {YELL0|YELL1, YELL0|YELL1,   YELL0|YELL1, 0,  ONE_SECOND, 0},   // y0 y1 y0 y1 - flash yellows lights 
    {0, 0,   0, 0,   0, 0}          // traffic lights off, DDR in, Hi-Z
};

// номера режимов работы в массиве  lightSignalization traffic_signals[]
#define LIGHT_NUM_YELLOW_FLASH  8       // номер состояния порта при мигании желтым - включено - flash yellows lights
#define LIGHT_NUM_STD_START     0       // С какого номера начинается работа стандартного режима
#define LIGHT_NUM_LIGHTS_OFF    9       // номер состояния порта всё выключено - в спячке - traffic lights off

Каждый экземпляр lightSignalization требует 8 байт оперативной памяти. Т.о. для глобальной переменной массива traffic_signals[] из 10 таких значений памяти необходимо 80 байт (ага, из 64 физически присутствующих, причем 2 у нас уже под таймер). Нельзя забывать, что используется оперативка и для организации стека программы.

К счастью, массив предопределенный, константный, для этого варианта переменных существует академический выход из положения — помещение констант в ROM, программную память, и извлечение в RAM только тех значений, что требуются на данный момент.

Всего-то необходимо изменить определение массива как

const lightSignalization traffic_signals[] PROGMEM= {...

И помнить, что чтения значений структуры теперь нужно будет организовывать через функции

 pgm_read_byte_near() и pgm_read_word_near()

для char и shortint соответственно.

Светофор работает в одном из 3 состояний режимов сигнализации.


  1. Сигнализация выключена, все огни погашены.
    Номер в массиве traffic_signals[] — LIGHT_NUM_LIGHTS_OFF
    {0, 0, 0, 0, 0, 0} // traffic lights off
    Самый простой режим — в lightSignalization все нули, все пины управления светодиодами — входы, продолжительность сигнала lightSignalization.signal_period==0 (т.е. бесконечность), продолжительность мигания lightSignalization.flash_period==0, т.е. та же бесконечность.


  2. Нерегулирующая сигнализация, мигающий желтый.
    Номер в массиве t

    © Habrahabr.ru