Внешними прерываниями называются такие прерывания основного цикла программы которые возникают в результате воздействия на специальные пины, электрических сигналов определенного вида. Для attiny2313 есть 4 таких вида:
1) передний фронт импульса (переход напряжения от низкого уровня до высокого),
2) задний фронт импульса (или спад (переход напряжения от высокого уровня к низкому)),
3) и фронт и спад,
4) низкий уровень напряжения.
То какой из них будет вызывать прерывание настраивается регистром MCUCR данного микроконтроллера. Реакция микроконтроллера на внешние прерывания гораздо быстрее чем его реакция на опрос пина порта командами SBIS и SBIC в основном цикле т.к. прежде чем пин опросится этими командами должны будут выполнится команды в основном цикле а это как минимум безусловный переход который делает этот основной цикл. С внешними прерываниями программа может работать настолько быстро что даже сможет посчитать дребезг контактов кнопки подключенной к пину внешнего прерывания. У attiny2313 есть 2 пина внешних прерываний, на схеме они помечаются как INT0 и INT1:
Счетчик дребезга контактов - это интересное устройство которое можно использовать для примерной оценки того насколько проверяемые контакты плохи своим дребезгом. Схема счетчика дребезга контактов на attiny2313:
Самым простым способом показать колличество дребезга является светодиод. Светодиод может мигнуть столько раз сколько кнопка сделала паразитных переключений своим дребезгом. От программатора, при этом, микроконтроллер м.б. отсоединен. Теперь давайте рассмотрим код программы на ассемблере:
В начале, как всегда, подключается файл с константами и макросами для attiny2313, далее директивой .CSEG указывается начало сегмента кода а после директивы .ORG 0x0000 происходит "перепрыгивание" через фрагмент когда который перенаправляет выполнение программы на подпрограмму обработки прерывания. Это нужно чтобы эта подпрограмма не сработала сразу после старта. Когда после того как произошло внешнее прерывание INT0 происходит переход по метке i0 в самый низ этого кода где число в регистре R17 увеличивается на единицу т.е. инкрементируется, после чего в регистр r20 записывается число 250 для того чтобы после возврата в основной цикл, задержка начиналась заново. Команда RETI делает выход из подпрограммы обработки прерывания обратно в основной цикл. Перед основным циклом происходит инициализация стека для того чтобы прерывание срабатывало как при фронте так и при спаде в нулевой бит регистра MCUCR записывается единица. Такой вид внешнего прерывания выбран по тому что светодиод, в основном цикле, также будет менять свое состояния на каждой итерации основного цикла т.е. переходить в светящееся состояние если он не светиться и переходить в не светящееся состояние если он светиться т.о. надо инкрементировать счетчик дребезга два раза при одном импульсе для того чтобы был подсчет полных импульсов. Записью единицы в 6й бит регистра GIMSK происходит разрешение внешнего прерывания INT0. Далее пин к которому подключен светодиод настраивается на выход и командой SEI разрешаются прерывания вообще. После чего начинается основной цикл в котором имеется задержка, для того чтобы глаз человека успевал замечать мигания светодиода, после которой происходит переход в начало основного цикла если в регистре с количеством дребезга пусто. Если же там не пусто то в атомарном блоке (блоке между запретом прерываний CLI и их разрешением SEI) происходит изменение состояния светодиода вместе с декркментированием (уменьшением на единицу колличества дребезга) после этого атомарного блока происходит возврат на начало основного цикла. Программа простая и не без недостатков но тем не менее её написание является хорошей тренировкой по ассемблеру в образовательных целях и собранное устройство даже имеет некоторую практическую ценность т.к. используя его можно убедиться в наличии дребезга контактов а также примерно оценить насколько контакты плохи в плане наличия этого дребезга. Посмотреть как работает счетчик дребезга и послушать описание работы программы можно на видео:
Многие современные микроконтроллеры, как правило, имеют в своем составе аналого цифровые преобразователи (АЦП) для измерения аналогового сигнала. Такая возможность часто бывает необходима. Однако не все микроконтроллеры имеют именно аппаратный встроенный АЦП. Например ATtiny2313 не имеет его. Зато он имеет аналоговый компаратор. Аналоговый компаратор, сам по себе, не может измерять аналоговое напряжение. Зато он может определить больше ли это напряжение некоторого опорного или нет. А также он работает гораздо быстрее чем полноценный АЦП. Бывают ситуации когда этого функционала достаточно, например когда надо определить превышение тока в нагрузке, включить защиту и сделать это быстро, в этом случае аналоговый компаратор будет гораздо лучше чем АЦП. Также этот компаратор может быть использован в каких нибудь преобразователях напряжения чтобы оперативно следить за напряжением. Или же небольшими внешними доработками можно сделать АЦП на основе имеющегося компаратора. Работа с аналоговым компаратором микроконтроллеров AVR достаточно проста. На основе компаратора микроконтроллера ATtiny2313 можно сделать например двухбитный АЦП. Такой АЦП не очень точный но зато очень быстрый и его можно использовать например для какой нибудь шкалы уровня заряда или ещё чего нибудь подобного. Рассмотрим схему:
Рисунок 1 - АЦП на ATtiny2313
Резистором R9 задается напряжение которое измеряет АЦП, вместо этого резистора можно поставить какой либо другой источник напряжения в пределах от 0 до напряжения питания микроконтроллера. На резисторах R5-R8 собрана R-2R цепь которая является двухбитным цифро аналоговым преобразователем (ЦАПом). На светодиодах VD1-VD2 сделана шкала уровня напряжения. Резисторы R1-R4 нужны для ограничения тока светодиодов VD1-VD2. Вместо R-2R цепи можно было бы использовать какие либо другие способы задания напряжения например ШИМ+RC цепь и т.о добиться гораздо большей точности но при этом скорость измерения напряжения значительно снизиться. Теперь давайте рассмотрим код программы на ассемблере:
Настройки компаратора в коде нет т.к. он уже правильно настроен по умолчанию. Есть настройка только пинов ввода/вывода и стека. В основном цикле реализован алгоритм поразрядного уравновешивания и управление светодиодами. Под основным циклом есть небольшая подпрограмма задержки. Про то как работает данный алгоритм можно посмотреть на видео:
Широтно импульсная модуляция (ШИМ) напряжения имеет множество применений. В электроприводах постоянного тока она используется для регулировки оборотов электродвигателя, в асинхронных электроприводах и инверторах такой модуляцией делаются синусоидальные напряжения, в источниках питания и DC-DC конвертерах для стабилизации и изменения напряжения, также этот тип модуляции может применяться для регулировки освещения электрическими лампами, температуры создаваемой электрическими нагревателями и использоваться во многих других случаях с электроприборами для которых подходит такой способ модуляции. В общем этот тип модуляции очень важен и поэтому во многих микроконтроллерах делают аппаратную поддержку ШИМа. ATtiny2313 - не исключение и в этом микроконтроллере есть 4 канала аппаратного ШИМа. Такой ШИМ можно получить с 4х специальных пинов:
Для создания ШИМа используются таймеры счетчики которых в ATtiny2313 две штуки. На каждом из них есть два канала. Настроить аппаратный ШИМ на этом микроконтроллере не сложно. Сперва нужно настроить соответствующие выводы (см. рисунок 1) на выход (о том как это делать см. предыдущие статьи по ассемблеру). После чего есть два регистра TCCR0A и TCCR0B для 8ми битного таймера и два регистра TCCR1A и TCCR1B для 16ти битного таймера. Для настройки 8 ми битного таймера в режим FAST PWM (быстрый ШИМ) в нулевой и первый биты регистра TCCR0A записываются единицы. Для того чтобы ШИМ работал так:
В начале периода идет высокий уровень а по прошествии некоторого количества тактов он заменяется низким:
И чтобы так было на обоих каналах. Нужно в 5й и 7ой биты регистра TCCR0A записать единицы.
в итоге должно получиться примерно так:
LDI R16, 0b10100011
OUT TCCR0A, R16
Также нужно запустить таймер чтобы ШИМ начался. (об этом см. статью про таймер https://electe.blogspot.com/2020/08/attiny2313.html). Запускать надо естественно без предделителя чтобы режим действительно был FAST. В итоге должно получиться что то вроде этого:
LDI R16, 0b00000001
OUT TCCR0B, R16
Чтобы устанавливать какое либо напряжение на нужном пине есть регистры OCR0A и OCR0В для 8 ми битного таймера и OCR1A и OCR1В для 16ти битного таймера. Записывать в них можно от 0 до 255 (т.к. регистр 8ми битный) а напряжения на соответствующих выводах (см. рисунок 1) будет меняться пропорционально записанному числу. Например установить половину напряжения питания на пине 14 ( он же PB2 или OC0A или PCINT2) можно командами:
LDI R16, 127
OUT OCR0A, R16
На основе аппаратных ШИМов можно сделать например не обычную светодиодную мигалку а например псевдоплавную. Код такой мигалки приведен в поле:
Светодиоды с резисторами необходимо подключит к выводам 9 и 14 микроконтроллера ATtiny2313. К данной статье прилагается видео:
В котором также рассказано о подпрограммах на ассемблере для микроконтроллеров AVR.
Иногда при работе с микроконтроллерами возникает необходимость сделать так чтобы микроконтроллер сам передавал какую нибудь информацию другим устройствам. Часто для этих целей используется интерфейс uart. В прошлом уроке https://electe.blogspot.com/2020/08/uart-attiny2313.html рассматривался способ сделать прием по интерфейсу uart символа и отреагировать на этот символ светодиодом подключенным к микроконтроллеру. Вторым закономерным этапом в изучении интерфейса uart для микроконтроллера должен быть этап изучения передачи информации из микроконтроллера во внешнее устройство. Как и ранее изучение можно начать поставив какую нибудь задачу. Т.к. работа с одним символом уже изучена то теперь можно усложнить задачу и попытаться сделать например передачу не одного символа а целой строки. Схему нужно немного изменить, надо добавить конденсатор по питанию (практика показала что без него uart плохо работает) а также можно убрать светодиод т.к. в нем нет необходимости в данном случае:
Рисунок 1 - Схема uart передатчика на ATtiny2313
Теперь давайте рассмотрим код программы на ассемблере для отправки строки по интерфейсу uart от микроконтроллера:
На этот раз можно обойтись без прерываний и не писать строки необходимые для них. В начале, как обычно, подключается .inc файл с константами и макросами для ATtiny2313 (специально урезанный для данного компилятора (см. урок про стек и .inc файл)) для удобства написания программы. Далее указывается начало сегмента кода, инициализация стека. Следующие 4 команды были рассмотрены в предыдущем уроке они нужны для настройки скорости UARTа и остаются без изменений. Далее регистр UCSRB нужно настроить по другому нежели ранее. Теперь нужно установить в единицу бит разрешающий передачу по UART. Биты разрешающие прием и прерывания можно не устанавливать. На этом можно закончить настройку UARTа т.к. всё минимально необходимое для передачи по UART уже настроено. Далее в регистр R17 записывается ноль. Этого конечно можно было и не делать но написав эту строку и добавив комментарий можно будет потом понять для чего этот регистр будет использоваться. Следующие две команды очень интересные. Они нужны для того чтобы использовать массив строки символов который мы будем отправлять по интерфейсу UART. Регистры ZL и ZH хранят адрес ячейки FLASH памяти микроконтроллера из которой можно извлечь байт информации командой LPM. Т.е. мы можем записать какую нибудь информацию на FLASH память микроконтроллера а затем извлекать её от туда и использовать. FLASH память обычно используется для хранения команд но для хранения данных её тоже можно использовать но при этом надо учитывать что в отличии от оперативной памяти у FLASH памяти ограниченное количество циклов записи/стирания. Хотя есть и преимущества напр. больший размер. А также эта возможность используется для записи в микроконтроллер загрузчика Ардуины и она же может использоваться для загрузки например своих самодельных загрузчиков или даже для программ которые могут менять сами себя! Эта возможность присутствует хотя микроконтроллеры строятся по гарвардской архитектуре. Т.о. в этом микроконтроллере сочетается быстродействие даваемое данной архитектурой и возможность создавать самопрограммирующиеся программы которые могут значительно расширить возможности микроконтроллера, если конечно хорошо уметь использовать эту возможность. Для того чтобы записать в регистры ZL и ZH адрес ячейки флеш памяти было удобно, можно использовать такой способ как в коде выше. Указать где нибудь в коде какую нибудь метку напр. str: потом через директивы LOW и HIGH и умножение на два получить в эти регистры адрес того места на который указывает данная метка. Его естественно просто так не видно программисту но в данном случае это и не обязательно, главное разместить метку где нибудь так чтобы при вытаскивании из FLASH памяти информации случайно не попасть в область где находятся команды или за границу памяти. Для избежания первой проблемы метку можно располагать под основным циклом и всем остальным кодом (как и было сделано в коде выше). После этой метки можно через директиву .db разместить массив строки так как в коде выше. Через запятую пишутся байты информации которые можно написать символами в одинарных кавычках либо числами в десятичном или каком либо другом допустимом виде. В начале основного цикла проверяется бит UDRE регистра UCSRA который указывает на то свободен ли регистр для отправки байта по UART или нет. Если он не свободен то надо подождать когда он освободиться чтобы записать в него бит который нужно отправить. Это и происходит в первых двух строках основного цикла. Когда регистр для отправки байта освобождается, происходит выход из этого, вложенного в основной, цикла и первый символ массива строки которую мы хотим передать записывается в регистр R18 а также адрес в регистрах ZL и ZH инкрементируется т.е. увеличивается на единицу чтобы в следующий раз можно было взять следующий символ. После того как символ получен в регистр. Он отправляется по UARTу путем записи этого символа в регистр UDR. Далее инкрементируется счетчик конца строки т.е. увеличивается число в регистре R17 который мы сделали счетчиком. Если счетчик не досчитал до конца строки то происходит переход на начало основного цикла и все повторяется заново. Если досчитал то в регистры ZL и ZH опять записывается адрес начала массива строки потом счетчик конца строки обнуляется. Потом происходит задержка, чтобы строка не присылалась слишком часто. И возврат на начало основного цикла.
Интерфейс uart (универсальный асинхронный приемник передатчик) часто используется для связи разных устройств друг с другом. Например для связи компьютера с микроконтроллером или микроконтроллера с например радиомодулем или другим микроконтроллером, модулем или микросхемой. Изучив uart микроконтроллера ATtiny2313 можно будет заменить Ардуино, в некоторых случаях, этим микроконтроллером. Чтобы изучить uart можно например поставить какую нибудь задачу например зажечь светодиод подключенный к микроконтроллеру передав ему какой либо код по uartу тогда в процессе решения этой задачи придет понимание того как использовать этот интерфейс. Светодиод, в таком случае, нельзя подключить к нулевому или первому пину порта D т.к. эти пины являются пинами uartа микроконтроллера ATtiny2313. Его можно подключить например к нулевому пину порта B
Рисунок 1 - Схема
В схему добавлены разъемы для того чтобы микроконтроллер можно было соединить с программатором чтобы зампрограммировать а потом отсоединить от программатора и соединить с usb-uart переходником для передачи информации на микроконтроллер от компьютера. Кварцевого резонатора в схеме нет. Практика показала что uart может работать и без него. uart - это асинхронный интерфейс т.е. передатчик передает биты через какие то определенные промежутки времени а приемник должен принимать биты через промежутки времени такие же. Иначе приемник будет принимать не то что нужно. К счастью некотарая доля синхронности в этом интерфейсе есть т.к. запускается прием только после возникновения запускающего импульса и асинхронность присутствует только в течении приема одного байта информации (если uart настроен на один байт т.е. 8 бит). Поэтому uart может работать при некотором небольшом несовпадении частот. Чем меньше скорость передачи тем больше может быть это несовпадение. Чтобы это несовпадение было почти нулевым и микроконтроллер мог работать по uartу на очень больших скоростях и очень стабильно и наверняка надежно, нужны специальные кварцевые резонаторы, например не на 1 или 2 МГц а на 1.8432 МГц для того чтобы время тактового импульса было кратным времени одного импульса по стандартам для uart. Естественно uart будет работать и при обычных кварцевых резонаторах (на Ардуине же он как то работает при кварцевых резонаторах с резонансной частотой 16 МГц). В данном случае для простоты мы не будем использовать такие редкие кварцевые резонаторы а используем внутренний rc генератор микроконтроллера с установленной по умолчанию на заводе частотой 1 МГц. Практика показала что uart при этом работает а также при такой частоте микроконтроллер может работать и при пониженном напряжении что позволяет его использовать например при питании от одной ячейки LiFePO4 аккумулятора что удобно для изготовления каких нибудь маленьких радиоуправляемых игрушек или роботов. Рассмотрим ассемблерный код программы:
Первые 4 команды а также инициализация стека, настройка пина на выход были рассмотрены в предыдущих уроках. Адрес ячейки памяти на которую переводиться выполнение программы после того как сработало прерывание по приему байта называется URXCaddr. После того как это прерывание возникло команда RJMP переводит выполнение программы на подпрограмму обработки прерывания. Но чтобы это смогло произойти сначала нужно настроить uart микроконтроллера для этого (а точнее для нашей текущей задачи) есть 5 регистров. Причем в данном случае 2 из них можно даже не трогать. Для начала можно настроить скорость. Слишком большую выбирать нельзя чтобы uart нормально работал. Можно выбрать например 9600 - эта скорость часто используется и подходит во многих случаях. Настраивается скорость через регистры UBRRL и UBRRH. В эти регистры нужно записать число соответствующее нужной нам скорости. Если это число больше 255 то надо младшие разряды этого числа записать в регистр UBRRL а старшие в UBRRH. Если меньше то можно это число записать в UBRRL а UBRRH не трогать вообще т.к. там нули по умолчанию. Чтобы определить это число не обязательно его рассчитывать по формулам. Можно просто найти его из таблицы в документации на микроконтроллер:
Также из этой таблицы видно что в нашем случае можно получить меньшую ошибку если установить бит удвоения скорости в единицу. Находиться этот бит в регистре UCSRA там он под номером 1 (самый первый это нулевой). После того как 3 регистра настроены осталось ещё 2. Разрешить прием байта и прерывание по завершению приема можно настройкой UCSRB. Чтобы разрешить прием, в единицу устанавливается бит 4 этого регистра, чтобы разрешить прерывание по приему, в единицу устанавливается бит 7 этого регистра. Последний регистр UCSRC надо настроить так чтобы принимать по 8 бит. Обычно так всегда и делается но в некоторых случая можно сделать по другому. Чтобы настроить прием 8ми бит нужно в записать единицы в бит 1 и бит 2 регистра UCSRC. Хотя этого можно было бы и не делать т.к. там по умолчанию и так имеются единицы:
Но зато теперь если вдруг понадобиться изменить количество принимаемых бит известно как это сделать. Далее в программе настраивается пин к которому подключен светодиод и разрешаются все прерывания. В основном цикле опять ничего интересного (хотя можно его заполнить например динамической индикацией или ещё чем нибудь). Когда бит приходит по uartу и возникает прерывание нужно вытащить из регистра UDR этот бит и поместить в какой либо регистр общего назначения например R18. Далее командой сравнения CPI и командой условного перехода если не равно BRNE сделать ветвление и организовать зажигание светодиода когда пришел ASCII код символа "a" и гашение светодиода когда пришел ASCII код символа "b". ASCII коды всех символов можно узнать из таблицы ASCII кодов всех символов:
Команда RETI делает выход из подпрограммы обработки прерывания. О том как откомпилировать программы и загрузить её в микроконтроллер можно узнать из предыдущих уроков. Если компиляция и загрузка прошли успешно то можно подключить микроконтроллер к компьютеру через usb-uart переходник и попробовать зажечь светодиод. Отправить какой либо символ с компьютера через переходник можно используя например Arduino IDE или же это можно сделать через командную строку. Если на компьютере обычная ОС Winows напр. 7 то например командой
mode COM3 BAUD=9600 PARITY=n DATA=8
Можно настроить этот переходник на нужную скорость и количество бит. Вместо COM3 нужно вписать название своего com порта который появляется после втыкания переходника в usb разъем. Отправить символ а можно командой
echo a > COM3
символ b соответственно командой
echo b > COM3
Вместо COM3 нужно вписать название своего com порта который появляется после втыкания переходника в usb разъем. После написания команды надо нажимать Enter чтобы символ отправился. В моем случае всё сработало, можно убедиться в этом посмотрев видео:
Таймеры/счетчики есть в большинстве современных микроконтроллеров. Они используются для множества разнообразных целей. Например для каких нибудь часов, широтно-импульсной модуляции (ШИМ), динамической индикации и ещё очень много для чего. В микроконтроллере ATtiny2313 их два. Один из них 8ми битный другой 16ти битный. Тот что 8ми битный может считать только до 255 т.к. для большего числа нужно большее количество бит. На основе 8ми битного счетчика можно делать что либо с малыми промежутками времени напр. ШИМ, динамическую индикацию, какие либо короткие импульсы напр. для каких нибудь протоколов связи по какому нибудь редкому интерфейсу или для чего нибудь ещё для чего нужны не долгие импульсы. На основе 16ти битного таймера счетчика можно делать длинные задержки (но короткие делать тоже можно). 16ти битный таймер/счетчик более универсальный поэтому его стоит изучить первым. На его основе можно сделать светодиодную мигалку
Рассмотрим ассемблерный код светодиодной мигалки на таймере:
Вначале подключаем специальный файл а также указываем директиву начала сегмента кода (об этом подробно написано в предыдущем уроке). Далее после директивы ORG с адресом с которого начинается выполнение программы ставим безусловный переход для того чтобы код изменения состояния светодиода (зажечь когда погашен или погасить когда зажжен) не выполнялся сразу после запуска микроконтроллера или после сброса. Далее инициализация стека (см. предыдущий урок). После инициализации стека идет инициализация таймера. Для настройки таймера есть некоторое количество регистров. В простейшем варианте (как сейчас) достаточно настроить два регистра TCCR1B - для запуска таймера и настройки его тактирования а также TIMSK - для разрешения прерывания. У разных микроконтроллеров регистры настраиваются по разному поэтому при переходе на другой микроконтроллер надо смотреть документацию и возможно устанавливать другие биты. В данном случае в регистре TCCR1B наиболее интересны первые 3 бита которыми устанавливается тактирование таймера. Если все три этих бита нули то раймер не тактируется и не работает. Если нулевой бит с единицей а остальные с нулем то таймер тактируется от того же генератора что и центрайльный процессор микроконтроллера и без предделителя. Если первый бит с единицей а остальные с нулями то тактирование также от генератора для центрального процессора но добавляется предделитель на 8. Более наглядно все опции представлены в таблице в документации:
Чтобы разрешить прерывание по переполнению 16ти битного таймера нужно в 7ой бит регистра TIMSK записать 1. Этот регистр в ATtiny2313 отличается от того же регистра в например ATmega8 т.е. TIMSK ATtiny2313 не TIMSK ATmega8 и настраивать их нужно по разному.
Далее в коде пин со светодиодом настраивается на выход (об этом в предыдущих уроках).
Командой SEI разрешаются все прерывания в том числе и то что было настроено для таймера.
Далее идет основной цикл в котором ничего интересного не происходит. Когда таймер досчитывает до максимального числа которое может поместиться во все его 16 бит, происходит переполнение этого таймера и прерывание по этому переполнению. Выполнение прогрвммы переноситься из основного цикла на ячейку флешь памяти с адресом обозначаемым как OVF1addr. А в этой ячейке организован безусловный переход на подпрограмму обработки этого прерывания. В этой подпрограмме меняется состояние светодиода (с погашенного на горящий или с горящего на погашенный). Команда RETI осуществляет выход из подпрограммы обработки прерывания. Выход происходит обратно в основной цикл где ничего интересного. Таймер опять считает до максимального значения и далее все повторяется.
При написании программ для микроконтроллеров на языке C от программистов обычно не требуется такое действие как настройка стека, однако если программа пишется на ассемблере то настраивать его нужно в большинстве случаев а также нужно понимать зачем он нужен и как используется. Стеком (применительно к микроконтроллерам AVR) можно считать некоторую область в оперативной памяти (ОЗУ) которая может заполняться и опустошаться (т.е. как бы менять свои размеры) в процессе работы программы. В 8ми битных микроконтроллерах AVR в стек может быть помещено 8 бит информации за один раз в конец стека, извлечено также 8 бит информации за один раз из его конца. Заполнять и извлекать информацию из стека можно только с его конца (т.е. брать можно только последний элемент) (хотя на самом деле это ограничение можно обойти но суть стека в том что можно трогать только последний его элемент). Данные микроконтроллеры работают так что стек заполняется не от начала ОЗУ до его конца а наоборот т.е. если первый элемент помещен по какому то адресу в ОЗУ то следующий элемент будет помещен по адресу который на единицу меньше чем предыдущий и т.д. Настройка стека заключается в том чтобы указать с какого адреса начинается стек, путем помещения этого адреса в соответствующие регистры (для микроконтроллеров у которых максимальный адрес ОЗУ не помещается в один байт) или регистр (как в данном случае для ATtiny2313 т.к. этот микроконтроллер имеет небольшой размер ОЗУ). Для ATtiny2313 этот регистр называется SPL и чаще всего в него помещается самый большой адрес из ОЗУ чтобы для стека было как можно больше места чтобы "разростись" и не перекрывать те области ОЗУ которые заполняются другими данными. Стек можно использовать по разному но в основном он нужен чтобы использовать подпрограммы в программе на ассемблере. Подпрограммы - это набор инструкций который может быть размещен где нибудь во флешь памяти и выполнятся по мере необходимости после вызовов из основной программы. Подпрограмма может вызываться много раз и использование их позволяет экономить место во флешь памяти не заполняя его повторными одинаковыми инструкциями. После вызова такой подпрограммы в стек помещается адрес того места на котором остановилось выполнение основной программы чтобы позже в это место вернуться. Также стек можно использовать чтобы просто хранить в нем какую либо информацию. В общем стек - вещь нужная и поэтому её нужно настраивать. Если взять программу из предыдущей статьи и дополнить её настройкой стека то получиться так:
Данная программа заработает если её откомпилировать и загрузить в ATtiny2313 методами описанными в предыдущих уроках. Однако можно заметить что обращения к регистрам микроконтроллера осуществляются по их адресам написанным в шеснадцатеричном виде хотя у регистров есть свои буквенные имена по которым обращение к ним было бы более удобным. Для того чтобы реализовать такое более удобное обращение существуют специальные .inc файлы которые можно подключить директивой .INCLUDE. К сожалению те файлы что скачиваются вместе с компилятором avra не подходят для этого компилятора т.к. содержат инструкции которые им не поддерживаются. Если эти инструкции удалить то можно получить урезанный но подходящий файл. Скачать такой можно по ссылке https://yadi.sk/d/QUFvZ5JgAwyYpw. Далее этот файл надо поместить в папку где располагается .asm файл с кодом:
и перед компиляцией в CMD перейти в папку с данными файлами. Сделать это можно командой CD. После этого компилятор не должен выдавать предупреждение о том что он не опознал микроконтроллер т.к. в подключенном файле находиться директива которая указывает ему для какого микроконтроллера написана эта программа.
После подключения данного файла и замены шестнадцатеричных адресов регистров их названиями код программы стал более понятным и удобным! Хотя всё же есть ньюансы которые стоит разяснить. Например Low(RAMEND) - означает что из двухбайтового числа которое теперь называется RAMEND мы возьмем только ту часть которая расположена в младших разрядах (как бы низкую половину возьмем). Low - это такой макрос который берет и выделяет эту половину. Т.к. ATtiny2313 это 8ми битный микроконтроллер и регистры в нем 8ми битные то поместить мы в них можем только 8 бит а адрес м.б. больше 8 ми бит, хотя в данном случае это не так и можно было бы сразу записать это число но для других микроконтроллеров этот макрос пригодиться.
Для того чтобы опросить пин микроконтроллера AVR настроенный на ввод существует несколько команд. Команда SBIS делает пропуск следующей после неё команды если на опрашиваемом пине высокий уровень напряжения (+5В (логическая единица)) и не делает этот пропуск если на этом пине низкий уровень напряжения. Существует команда SBIC которая делает такой пропуск если на опрашиваемом пине низкий уровень и не делает его если высокий. Т.о. система микроконтроллера AVR позволяет опросить отдельный пин, какого либо порта, одной командой и сделать это за один такт. Как было показано ранее вывод логической единицы или нуля на пин настроенный на выход также можно одной командой за один такт. Есть команды для работы с портом целиком - это команда OUT для вывода и команда IN для ввода (т.е. опроса порта целиком). Чтобы проверить на практике ввод с кнопкой, можно использовать схему:
Рисунок 1 - Мигалка с кнопкой с подтягивающим резистором на ATtiny2313
Можно взять код из предыдущей статьи, дополнить его одной строкой и получить мигалку работающую при нажатой кнопке и не работающую при отпущенной кнопке:
здесь добавлена строка:
SBIS 0x10, 1 ; пропустить следующий команду если кнопка не нажата
0x10 - это адрес регистра для опроса порта D,
1 - это номер пина порта D к которому подключена кнопка.
Т.о. получается что светодиод может загораться только в случае если кнопка нажата т.к. только тогда команда SBIS будет позволять выполниться команде установки пина, к которому подключен светодиод, в единицу.
Можно упростить схему на рисунке 1 убрав резистор для подтягивания пина к плюсу питания когда кнопка не нажата.
Рисунок 2 - Мигалка с кнопкой на ATtiny2313
Но чтобы схема работала также как раньше надо подключить внутренний подтягивающий резистор. Сделать это можно такой же командой какой мы устанавливали логическую единицу на пине настроенном на выход. Т.е. командой SBI но для пина к которому подключена кнопка.
Здесь добавлена строка:
SBI 0x12, 1 ; подача на пин 1 порта D высокого уровня
Прежде чем добавлять такую строку надо сначала убедиться что пин настроен на вход. Убедиться в этом можно посмотрев строки:
LDI R16, 0b00000001 ; поместим в регистр R16 число 1
OUT 0x11, R16 ; загрузим значение из регистра R16 в DDRD
в которых мы видим что первый пин порта D настроен на вход т.к. в соответствующий бит соответствующего регистра записан ноль. После того как программа написана её можно откомпилировать и загрузить в микроконтроллер (см. предыдущую статью).
Для программирования микроконтроллеров существуют два основных языка - это язык Си и Ассемблер. Бывают и другие языки но Си и Ассемблер используются чаще других т.к. их можно использовать для создания программ оптимально использующих ресурсы микроконтроллера. Язык Си более прост в изучении чем Ассемблер, более прост для понимания человеком вообще (на нем можно писать более читабельный код) а также более просто переносим между разными микроконтроллерами и вообще разными устройствами. Ассемблер же не так прост в понимании и хуже переносим т.к. он имеет множество разных команд которые отличаются друг от друга при программировании разных микроконтроллеров и устройств и даже при программировании одного и того же устройства но при использовании разных компиляторов. Однако язык ассемблер дает больше возможностей управлять ресурсами микроконтроллера. Программы написанные на Ассемблере могут быть более оптимальными чем программы на Си. Также изучение Ассемблера позволяет лучше понять работу микроконтроллера и писать более оптимальные программы на языке Си. Поэтому данный низкоуровневый язык стоит изучить даже если необходимость в его изучении и применении на практике никогда не возникала. Данный язык сильно отличается для разных микроконтроллеров но всё таки некоторые общие принципы наверняка должны присутствовать в той или иной степени для разных устройств. Начать скорее всего будет лучше с какого нибудь простого микроконтроллера. Например, для старта, можно взять ATtiny2313 и написать для него мигалку а если это получиться то делать далее уже более сложные устройства и переходить на более сложные микроконтроллеры например какие нибудь из STM32. Чтобы писать саму программу можно использовать какой либо текстовый редактор. Обычно в ОС Windows присутствует текстовый редактор "блокнот". После написания программы в "блокноте"её можно сохранить в формате с расширением .asm потом откомпилировать в файл с расширением .hex каким нибудь компилятором ассемблера для микроконтроллеров avr напр. компилятором avra (который можно скачать по ссылке https://sourceforge.net/projects/avra/files/1.2.3/ (для windows нужно скачать avra-1.2.3-win32.zip)) (для установки достаточно разархивировать) и записать полученный файл с расширением .hex в микроконтроллер утилитой avrdude (устанавливается вместе с arduino IDE, WinAVR или отдельно) через программатор. Рассмотрим схему устройства которое будем программировать:
Рисунок 1 - Светодиодная мигалка на микроконтроллере ATtiny2313 с программатором
На рисунке выше приведена схема светодиодной мигалки на микроконтроллере ATtiny2313 с программатором. Микроконтроллер ATtiny2313 имеет встроенный тактовый RC генератор и встроенный внутренний резистор для подтяжки вывода reset к нужному для работы микроконтроллера уровню также ему не нужно много внешних конденсаторов и прочей дополнительной обвязки (этим данный микроконтроллер прост и хорош). В качестве программатора можно использовать Arduino с записанным в неё скетчем ISP программатора:
Рисунок 2 - Светодиодная мигалка на микроконтроллере ATtiny2313 с программатором на Arduino Uno
Если все необходимые элементы имеются то можно начинать писать программу. Давайте рассмотрим простую программу мигания светодиодом на Ассемблере:
Ассемблер, в отличии от Си, регистронезависимый язык поэтому команды можно писать и большими буквами и маленькими и вперемешку. Команды которые начинаются с точки называются директивами. Директивы - это команды не для микроконтроллера а для компилятора. Директива .CSEG означает начало сегмента кода, помимо сегмента кода существует также сегмент данных и сегмент EEPROM. Директива .ORG указывает адрес с которого начинается сегмент. В ассемблере можно делать комментарии т.е. строки которые игнорируются компилятором и нужны для помощи программисту, комментарии начинаются с точки с запятой и завершаются концом строки. Команда LDI нужна для помещения в регистр какой либо константы. Константу можно записать в десятичном, шестнадцатеричном или двоичном виде. Если она записана в двоичном виде то перед ней ставиться нолик и английская буква b т.е. например 0b00000001. Двоичные числа удобны для конфигурирования порта микроконтроллера т.к. по ним наглядно видно в каком бите есть ноль а в каком единица. Если константа пишется в десятичном виде то перед ней ставить ничего не надо а надо просто написать число. Если константа пишется в шестнадцатеричном виде то перед ней ставиться 0x. Чтобы настроить второй пин микроконтроллера на выход нужно в записать единицу в нулевой разряд регистра DDRD. DDRD - это просто название регистра для удобства, на самом деле важно знать что этот регистр имеет какой то адрес в памяти и для того чтобы записать в этот регистр какое либо число и т.о. поместить либо ноль либо единицу в нужный его разряд, нужно записать это число по соответствующему адресу для регистра DDRD в микроконтроллере ATtiny2313 этот 0x11. Для того чтобы настроить второй пин микроконтроллера ATtiny2313 на выход мы сначала командой LDI записываем в регистр R16 число 0b00000001 а потом командой OUT из регистра R16 мы это число помещаем в регистр DDRD по тому что записать сразу константу в DDRD нельзя. Далее идет основной цикл программы т.е. "бесконечный" цикл в котором выполняются основные постоянные действия. Чтобы организовать этот цикл на ассемблере используется оператор безусловного перехода RJMP. В самом низу кода мы видим строку RJMP Start а в самом верху основного цикла мы видим метку Start. Команда RJMP просто переводит выполнение программы на то место где стоит метка т.о. если метку поставить перед этой командой после которой идет название этой метки то можно получить бесконечный цикл. Командой SBI мы устанавливаем высокий уровень т.е. пять вольт на нужном пине микроконтроллера. Если мы используем пин который связан с нулевым битом порта D то после команды SBI мы пишем адрес порта D (для микроконтроллера ATtiny2313 это 0x12) ставим запятую и после этого пишем номер бита который мы хотим установить в логическую единицу чтобы на нужном пине стало пять вольт. Аналогично командой CBI мы делаем ноль вольт на нужном нам пине. Для создания задержки сначала в регистр r18 записывается число 250 (максимальное которое можно записать = 255, минимальное = 0) потом 250 записывается в регистр r19. После чего командой dec (декремент) происходит уменьшение на единицу числа находящегося в регистре r19. Далее имеется команда brne - это оператор условного перехода т.е. он переносит выполнение программы на место с меткой при выполнении условия. Если результатом операции предшествовавшей данной команде был ноль то перехода туда где стоит метка не происходит и выполнение программы продолжается, если был получился не ноль то происходит переход на строку с меткой в данном случае это L1. Т.о. получается цикл выход из которого происходит тогда когда число в регистре r19 становиться равным нулю. Далее происходит декремент числа в регистре r18 и так как сразу там ноль не получиться то переход происходит на строку с меткой L1 а там стоит декремент числа в регистре r18 но так как там уже ноль то декремент делает там 255 и всё повторяется заново т.о. получается вложенный цикл. Эти циклы выполняются какое то время и делают задержку для того чтобы можно было наблюдать мигание светодиода.
После того как программа написана (например в имеющемся в windows текстовом редакторе "блокнот" или любом другом подходящем текстовом редакторе (неважно каком (это дело вкуса (на результат не повлияет (если текстовый редактор подходит для данных целей и позволяет сохранять текст в формате .asm))))) её можно сохранить в формате .asm и откомпилировать в cmd (командной строке windows) командой:
путь_до_компилятора_avra путь_до_файла.asm
например:
F:\avra-1.2.3\bin\avra F:\avr\blink.asm
Если компиляция пройдет успешно то компилятор создаст файл с расширением .hex который можно загрузить в микроконтроллер например командой:
avrdude -c avrisp -P COM3 -b 19200 -p t2313 -U flash:w:blink.hex
(avrdude перед этим надо установить на компьютер)
Эта команда сработает если используется микроконтроллер ATtiny2313 (-p t2313), программатор avrisp (-c avrisp (он же arduino, он же stk500, он же "пять проводков" (avrdude не поймет "пять проводков" (остальное должна понять)))) который подключен к порту который называется COM3 (-P COM3) (если называется по другому то в команду надо вписать соответствующее название). blink.hex - это название откомпилированного файла который надо загрузить в микроконтроллер. При этом в данной программе не прописана установка фьюзов и считается что микроконтроллер либо новый с завод и на нем установлены фьюзы по молчанию либо фьюзы ранее были установлены как надо и менять их не требуется. Если hex файл успешно запишется то светодиод должен замигать!
Arduino и радиомодули nrf24 можно использовать для создания на их основе системы радиоуправления самолетом. До появления подобных модулей, микроконтроллеров и даже транзисторов такие системы делали на вакуумных лампах и реле. Основное требование к таким системам - это малый вес т.к. чем больший вес имеет система управления тем меньше вероятность что самолет сможет лететь. Чтобы самолет мог лететь, подъемная сила его должна быть больше чем сила притягивающая его к земле. Подъемная сила зависит от площади крыла, его профиля, скорости самолета чем меньше будет вес самолета тем меньше может быть его крыло и его скорость но для того чтобы максимально увеличить шансы на успешный полет нужно и максимально снизить вес и максимально увеличить подъемную силу путем увеличения площади крыла. Для снижения веса желательно реализовать систему с как можно меньшим количеством разных плат, модулей, радиодеталей и т.д. Ардуино лучше использовать какое нибудь маленькое например Arduino pro mini или Arduino nano а также использовать маленькие и мощные моторы и маленький и мощный литий ионный аккумулятор для радиоуправляемых авиамоделей. Уменьшить вес аккумулятора можно ценой времени в течении которого он может работать. Для того чтобы не надо было использовать стабилизатор напряжения на 3.3 В для радиомодуля и небольшой аккумулятор с напряжением 3.7 В нужно использовать Arduino работающее от напряжения 3...3.7 В. Поэтому в схеме приемника ниже имеется Arduino nano
Рисунок 1 - Схема приемника
Для управления моторами используются полевые транзисторы управляемые логическими уровнями т.к. только такие транзисторы могут нормально открыться от такого низкого напряжения и передать моторам достаточную мощность из аккумулятора. Ардуины с низким напряжением питания имеют более низкую тактовую частоту но в данном случае это не так важно т.к. ардуино не делает ничего требующего высокого быстродействия и производительности. Для поворота влево или в право один из моторов вращает пропеллер быстрее другого так образуется ассиметричная тяга и самолет по идее должен повернуть. Для того чтобы управлять высотой будет изменяться скорость вращения обоих пропеллероы т.к. чем она больше тем быстрее летит самолет и тем больше подъемная сила и чем медленнее летит самолет тем она меньше. Это не самый лучший способ управления самолетом но зато для него не требуются дополнительные рули, сервомоторы и т.д. которые утяжеляют конструкцию и уменьшают вероятность полёта. Пульт управления самолетом не обязательно должен быть лёгким. Можно даже сделать универсальный пуль для управления разными электронными самоделками.
Рисунок 2 - Схема пульта управления
Самый просто реализуемый способ управления - это назначить один потенциометр на пропорциональное управление одним мотором а другой потенциометр на управление другим мотором но такой способ очень неудобен. Гораздо лучше было бы сделать так чтобы одним потенциометром можно было бы пропорционально регулировать скорости обоих моторов а другим влиять на ассиметричность тяги в ту или другую сторону. Пульт можно сделать таким чтобы он всегда передавал только положение потенциометров и состояния кнопок а всю остальную логику реализовывать в приемнике. Скетч передатчика приведен в текстовом поле:
Скетч приемника приведен в текстовом поле:
Данный скетч нормально откомпилируется если версия Arduino IDE будет 1.6.4
В более новых версиях IDE имеются другие версии библиотеки для радиомодуля nrf24 и скетч должны быть немного другими. Чтобы они подходили необходимо следить за обновлениями и соответствующим образом менять их или устанавливать ранние версии Arduino IDE. Посмотреть как летает самолет и послушать рассказ про него можно на видео:
Цифровая видеокамера может быть источником информации о внешнем мире для какого либо робота или электронного прибора подобно глазу для человека. Помимо камеры существуют и другие приборы для сбора информации о внешнем мире (например ЛИДАР) но именно камера является одним из самых дешёвых и при том информативных. Возможно это связано с тем что для человека наиболее важна информация получаемая от изображений и поэтому технологии изготовления видеокамер получили большое развитие. Если у нас например имеется ондноплатный компьютер Raspberry pi (или вообще какой либо другой компьютер) а также usb камера которую можно подключить к этому компьютеру то мы можем, на основе этих приборов, пытаться создавать какие либо устройства способные распознавать визуальные образы из внешнего мира. Задача распознавания образов компьютером является сложной и это препятствует автоматизации некоторых видов работ которые выполняют люди. Однако возможно что кто то сможет решить эту проблему в какой либо сложноавтоматизируемой области и возможно что это будет один из тех кто прочитает данную статью. Для начала мы попробуем сделать простейший датчик цвета на основе usb камеры и Raspberry pi. Сделав данный прибор мы освоим один из наиболее простых способов извлечения информации из цифровой цветной usb web камеры. Далее с извлеченной информацией можно будет эксперементировать по разному. Для удобной визуализации результата можно использовать например RGB светодиод с общими анодами и соединить его так как показано на рисунке:
Рисунок 1 - Схема подключения RGB светодиода к Raspberry pi 3
Если камера обнаружит преобладание красной составляющей то загорится красный свет, если зеленой то - зеленый, если синей то - синий. Теперь осталось разобраться с USB камерой и распознаванием цвета. Для Raspberry pi нужно будет написать какую то программу на каком то языке программирования. Т.к. Raspberry pi это просто компьютер с операционной системой то вариантов того какой язык программирования выбрать очень много. Если кто либо из читающих данную статью занимается web программированием то ему наверняка знакомы такие языки как PHP, javascript, python и т.д. Это хорошие языки, по ним есть много учебников они популярны и распространены, для них есть много библиотек и их можно использовать для программирования Raspberry pi. Однако эти языки интерпретируемые т.е. когда программы написанные на них выполняются то вместе с их выполнением происходит их трансляция на машинный код и это занимает некоторое время. Для обработки большого количества информации (а у нас именно такой случай т.к. современные камеры имеют высокое разрешение и могут снимать видео) лучше подходят компилируемые языки. Существуют также варианты работающие через виртуальную машину - это например java и C# когда сначала код программы преобразуется в байт код а потом этот байт код преобразуется в машинный. Но и в этом случае расходуется время на преобразование байт кода в машинный код. Языки C и C++ являются компилируемыми и они очень похожи на описанные выше языки своим синтаксисом (за исключением питона) поэтому эти языки очень хорошо подходят для наших целей т.к. многие их уже знают либо смогут освоить и программы написанные на них могут быть достаточно быстры для обработки изображений с камеры. К тому же вместе с операционной системой для Raspberri pi обычно прилагаются компиляторы для данных языков и их скорее всего не придется дополнительно устанавливать. Помимо компилятора C++ нам понадобятся некоторые другие утилиты поэтому включим Raspberry pi зайдем в терминал и впишем там команды (нажимая enter после каждой):
Теперь проверим есть ли на нашем Raspberry pi утилита fswebcam для того чтобы делать снимки с usb камеры. Впишем команду whereis webcam и нажмем enter
Если в консоли появятся какие то пути то данная утилита уже установлена, если нет то она не установлена и установить её можно командой sudo apt-get install fswebcam
Также нам понадобятся ещё 3 утилиты, это конвертер изображений imagemagic, компилятор языка C++ а также утилита wiringpi для управления пинами Raspberry pi (которая обычно также как и компилятор устанавливается также вместе с операционной системой). С этими тремя утилитами поступим ткже как и с первой. Сначала проверяем наличие утилиты потом устанавливаем её если её нет:
Теперь можно начинать писать программу. Для этого нужно открыть какой нибудь текстовый редактор. Можно например это сделать через графический интерфейс или в терминале открыть например vim или nano:
Подключим библиотеку ввода/вывода:
Далее надо будет подключить ещё пару библиотек:
Библиотека stdlib.h нужна для того чтобы можно было использовать функцию system а эта функция нужна для того чтобы можно было запускать какие либо внешние команды которые мы можем вписать в терминале или в bash скрипте. И ещё одна библиотека:
Эта библиотека нужна для чтения файла (в нашем случае фотографии с камеры). Также эту библиотеку можно использовать для вывода вместо iostream. Далее напишем функцию main:
Функция должна что то вернуть, вернем ноль:
Далее нужно объявить все необходимые структуры, массивы и переменные т.к. в языке С++ переменные можно использовать только после их объявления.
Первым объявляется указатель на переменную типа FILE. В языке C++ и C если при объявлении переменной ставиться звездочка то это указатель. Данный указатель нужен для того чтобы прочитать файл (в данном случае фотографию). Далее имеется массив символов для хранения строки (в языке C++ нет отдельного типа "строка" как в других языках напр. java, python, javascript и т.д. вместо строки тут используется массив символов). Размер его 12 по тому что для хранения одного пикселя (в формате который мы рассмотрим ниже) нужно 12 символов (не всегда их будет столько но здесь мы ставим максимальное количество чтобы они всегда помещались). Далее объявляется переменная x типа int для того чтобы считать количество строк. После чего переменная типа long для того чтобы хранить символ. Эта переменная не типа char по тому что в переменную типа char нельзя поместить символ конца файла. Далее есть три переменный для хранения трех составляющих цвета пикселя. И наконец ещё одна переменная - это ещё один вспомогательный счётчик. Теперь мы можем настроить пины Raspberry pi к которым подключен светодиод на выход:
После того как мы настроили пины мы делаем снимок с камеры, убираем баннер (который утилита fswebcam делает на фотографии по умолчанию) чтобы он не влиял на результат (он добавит красной составляющей если его не убрать) и сожмем всю фотографию до размеров одного пикселя:
Теперь мы имеем картинку в формате jpg размером 1х1 пиксель. Чтобы достать этот пиксель из картинки формата jpg придется изрядно потрудиться т.к. формат jpg очень сложный. Это не просто набор пикселей а сложный формат с длинной преамбулой и сжатием. Помимо jpg есть и другие форматы напр. gif или png но они тоже сложные и со сжатием. Формат bmp без сжатия и хранит изображение как набор из 3х (RGB) или 4х (RGBA (A-прозрачность)) составляющих для каждого пикселя. Однако этот формат тоже не прост и имеет большую и сложную преамбулу. Он занимает много места и он сложен и утилита fswebcam не выдает изображения в этом формате поэтому этот формат тоже не годиться. Но к счастью для нас существует ещё один замечательный простой формат изображений который идеально подойдет для наших целей. Это формат PPM !!! Утилита fswebcam не может выдать изображение в таком формате и поэтому ранее мы установили ещё одну утилиту ( imagemagic ) для конвертации изображений. Сконвертируем наше изображение в формат PPM
Видно что мы конвертируем в формат ppm и без сжатия. В данном случае это означает что мы получим картинку в виде набора символов а не байтов. И после данной команды мы даже сможем открыть эту картинку текстовым редактором и понять какого цвета наш пиксель! Также мы сможем понять с какой строки нужно начинать вынимать пиксели из данного файла. Мы получили сконвертированную картинку и теперь можем её открыть функцией fopen
После открытия данной картинки имеется цикл на каждой итерации которого из картинки вынимается один символ и если это символ конца строки то переменная x инкрементируется. Так происходит подсчет количества строк. Если мы уже глянули в файл который получается после конвертации изображения из формата JPG в формат PPM то мы увидели что на строке под номером 5 находиться наш пиксель и именно с этой строки мы можем записать в наш массив символов. Это не очень хороший способ получить нужную нам информацию т.к. по разным причинам (обновление утилиты и т.д.) номер строки может поменяться и тогда система сработает неправильно. К тому же в данном коде присутствует "магическое число", такие числа очень не любят программисты по тому что когда программист читает чужой код то он не понимает что означает это число и поэтому злиться. Мы могли бы придумать другой способ получения нужной нам информации или как минимум сделать удобочитаемую константу для номера нашей строки но в данном случае мы разбираем простую программу просто в целях образования. Чтобы было проще понять основную суть, код делается небольшим, без дополнительных удобных наворотов. В коде мы видим что перед помещением символа в массив символов происходит преобразование из типа long в тип char. Это сделать нужно обязательно т.к. в языке C++ нет автоматического преобразования. Если из файла прочитался символ конца файла то цикл завершается. После чего надо обязательно закрыть файл функцией fclose(). После того как мы получили строку с нашим пикселем мы можем вытащить из неё три составляющие и записать их в предназначенные для них переменные. Сделать это мы можем функцией sscanf
Их мы можем вывести на экран
Это на самом деле было не обязательно т.к. далее мы выведем результат на светодиод но это может быть полезно для наладки если что то будет работать не так как надо. И наконец мы можем выяснить какая составляющая преобладает и в соответствии с эти зажечь нужный свет светодиода. Для этого мы использует обычное всем известное ветвление if
Ну а дальше идет конец функции main и программы
Теперь наш код надо сохранить
и откомпилировать компилятором
Если компилятор не выдал ошибок то скорее всего всё получилось и программу можно запустить
Программа выполнится один раз. Её можно зациклить сделав простой bash скрипт с циклом
Данные скрипты мы уже делали в предыдущих статьях данного блога. Это отдельная длинная тема но не очень сложная, не сложнее чем C++. На языке bash тоже можно писать сложные программы однако это интерпретируемый язык и по этой причине он не используется в нашем случае целиком. Конечно вставки в код на C++ кода на языке bash замедляют всю программу в целом но здесь мы рассмотрели самый простой вариант с которого можно начать, достаточно быстро получить результат и мотивацию (т.к. полученный без больших усилий результат обычно дает удовлетворение и мотивирует продолжать) чтобы не забросить данное занятие на начальном этапе. Посмотреть как работает данное устройство и увидеть данную статью в видеоформате можно на видео:
скопировать полный текст программы можно из данного текстового поля (оказывается текстовые поля отлично подходят для вставок кода в html страницу т.к. тексты в треугольных скобочках в них не считаются тегами)