Программирование микросхем для начинающих. Вы еще не программируете микроконтроллеры? Тогда мы идем к вам

05.08.2019

Минимальный материальный набор для изучения программирования

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

Стартовый набор начинающего микроконтроллерщика

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

Для первой группы все очень просто:
— приобрести программатор и научиться с ним работать

Для второй группы остановлюсь немного подробнее.
Arduino ориентированна на начинающих, непрофессиональных пользователей, и состоит из двух частей — программной и аппаратной.
Программная часть состоит из бесплатной программной оболочки для написания программ, их компиляции и программирования устройства.
Язык программирования — стандартный С++ с некоторыми изменениями облегчающими работу с этим языком (хотя есть возможность создавать программы или подключать готовые файлы проектов используя стандартный язык С++). Научиться программировать в Arduino очень просто (поэтому программы на Arduino называются «наброски») — весь процесс программирования сводится в основном к выбору необходимых готовых библиотек для получения конкретного результата.
Аппаратная часть состоит из готовой платы с микроконтроллером с необходимой обвязкой для нормальной работы микроконтроллера и плат расширения (шилды). Кроме того выпускается множество готовых датчиков и исполнительных устройств. Весь процесс сборки конструкции на Arduino напоминает конструктор «Лего» — выбираете необходимые платы расширения и устройства и стыкуете их с основной платой. Для загрузки программы отдельный программатор не требуется.
Arduino вещь конечно хорошая, но предназначена в основном только для тех, кто хочет собирать конструкции на микроконтроллерах, но не хочет загружать свои мозги лишними (по их мнению) знаниями (это сугубо мое мнение).

Ну а мы причислим себя к третьей группе и пойдем хотя и тернистым, но очень интересным путем.

Для того, чтобы начать практическое изучение как устройства, так и программирование микроконтроллера, нужно иметь минимальную материальную базу — стартовый набор. Стартовый набор, необходимый по моему разумению для освоения микроконтроллера можно приобрести в интернет-магазине сайта (так-что эту статью можно считать и коммерческой рекламой:)):

Хочу отметить комментарий одного читателя сайта. К сожалению комментарий куда-то улетучился, и не сохранилось даже имя читателя, но человек подметил очень точно — это не первый вариант набора, а уже третий, более дорогой — изменилась комплектация набора, она стала более расширенной, добавлены новые (нужные) комплектующие (прошу читателя сайта, оставившего комментарий, меня извинить за ошибку работы сайта). Я не пытаюсь навязать читателям сайта что-то купить в интернет-магазине сайта. Это совсем необязательно, можете заказать у Китайских товарищей.

А теперь к главному:
1. Для практических опытов нам потребуется микроконтроллер (а лучше три):
— наиболее популярные и востребованные микроконтроллеры — ATmega8A-PU и ATtiny2313A-PU, ATtiny13A- PU. Кстати, ATtiny13 очень популярный МК, и не зря его называют «малюткой» — малые возможности — но серьезные разработки.
2. Для записи программы в микроконтроллер необходим программатор:
— идеальное решение, на мой взгляд, — программатор USBASP, от которого мы к тому-же будем получать напряжение 5 Вольт для будущих конструкций.
3. Для визуальной оценки и выводов результатов работы программы необходимы средства отображения информации:
— светодиоды
— семисегментный светодиодный индикатор
— знакосинтезирующий (буквенно-цифровой) LCD дисплей
4. Для изучения процессов общения микроконтроллера с другими устройствами:
— цифровой датчик температуры DS18B20 и часы реального времени DS1307 (очень практичные устройства)
5. Кроме того нам потребуются транзисторы, резисторы, кварцевые резонаторы, конденсаторы, кнопки:
— биполярные транзисторы структуры NPN и PNP
— набор резисторов различного номинала
— кварцы (вот тут я выкинул лишнее) на 32,768 кГц, 8 МГц.
— керамические конденсаторы на 22 pF
— тактовые кнопки
6. Для сборки конструкций на микроконтроллере понадобится макетная плата для монтажа без пайки и набор перемычек к ней:
— макетная плата МВ102 (идеально иметь две такие платы — они стыкуются между собой, что очень пригодится в дальнейшем)
— соединительные перемычки к макетной плате трех типов — гибкие (мама-мама, папа-папа) и жесткие П-образной формы

Получается вот такой набор:

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

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

Выбор языка программирования и среды разработки для программирования

Честно говоря, выбор языка программирования и среды разработки вопрос очень ответственный, навязывать кому-то свои предпочтения и что-то советовать дело довольно-таки трудное.
Давайте попробуем подойти к этому выбору не предвзято, чисто с практической стороны.
1. Существует два основных языка программирования микроконтроллеров — Ассемблер (язык низкого уровня) и Си (язык высокого уровня).
Если мы хотим программировать микроконтроллеры используя полностью все их возможности (а мы это хотим), то необходимо изучать эти два языка.
2. Среда разработки для программирования микроконтроллеров.
Тут выбор большой и очень много мнений. Поэтому можно сказать: «Каждая лягушка хвалит свое болото». Мне, к примеру, очень нравится малораспространенная графическая среда разработки «Algorithm Builder», и «квакать» о ее преимуществах перед другими программами я могу очень долго. Но будем делать выбор, как было сказано выше, не предвзято и практично.
Микроконтроллеры AVR выпускает фирма Atmel, она же предоставляет в наше распоряжение бесплатную среду программирования «Atmel Studio» (бывшая AVR Studio). На ней мы и остановимся.
Интегральная среда разработки (IDE — Integrated development environment) Atmel Studio позволит нам:
— писать программы как на Ассемблере, так и на Си (Почему на Си. Программа «Atmel Studio» позволяет писать программы на трех языках (О чем мы и погорим в первой статье), но есть одно но: программы на Си++ мы рассматривать не будем, по одной причине, и в следующей статье я расскажу об этом
— отладить программу
— перевести программу в машинный код (откомпилировать)
— записать программу в микроконтроллер

Все, выбор мы сделали:


Теперь осталось выполнить два пункта:
1. Обзавестись каким-нибудь стартовым набором (для начала хватит и микроконтроллера ATmega8, нескольких светодиодов, пары кнопок и сопротивлений к ним).
2. Установить (именно установить, а не скачать, и с регистрацией) с официального сайта Atmel (http://www.atmel.com/ru/) программу Atmel Studio.
Программировать микроконтроллеры мы будем с использованием программатора USBASP.
Отдельной статьи по Atmel Studio я писать не буду, будем изучать ее постепенно, по мере надобности и в связке со статьями по устройству и программированию микроконтроллеров.



Запаивая еще одну микросхему на очередную плату или перерезая дорожку на плате в десятый раз для внесения очередных (но не последних) изменений в новое устройство, вы начинаете подумывать: «А не бросить мне это нудное занятие?!» Ваше новое устройство получается не таким, как вам бы хотелось, но и изменять схему и переделывать всё на плате вам уже надоело.
Перелистывая журналы по электронике, вы всё чаще встречаете слова: процессор, микроконтроллер, прошивка, программирование. Но эти слова для вас не имеют конкретного значения. Вы где-то слышали, а может даже и держали в руках то, о чем вы даже думаете с благоговейным трепетом… микроконтроллеры! То, что уменьшает размеры устройств, наделяя их недостижимыми для вас возможностями…Нет, вам, как электронщику, понятны общие идеи работы этих устройств, но об их практическом применении в своих изделиях не может быть и речи! Вы уже несколько раз пытались освоить микроконтроллеры, даже приобрели пару книг из серии «… для чайников» и скачали из интернета несколько популярных самоучителей. Проходило какое-то время, и всё останавливалось на самом интересном месте: схемы, показанные в книгах, вам были понятны, но вот методы создания программы для вас так и остались загадкой. Набрать несколько символов на английском (или каком-то там) языке в указанной книгой программе для вас не проблема. Но не ясна СУТЬ и МЕТОДЫ использования этих загадочных символов, последовательность которых в книге называли программой. Вы винили себя в тупости и откладывали идею освоения микроконтроллеров в дальний ящик. Да и зачем? Вы и так прекрасно справляетесь: у вас есть много-много микросхем, на которых вы разрабатываете свои устройства… Устройства на больших платах, которые вы долго-долго отлаживаете и переделываете…
Но знакомитесь с парнишкой из соседнего дома: он пишет программы, загружает их в микроконтроллер и то, что вам приходится отлаживать месяцами, он делает за несколько дней. Вы в панике, начинаете искать заброшенные книжки, и вспоминать всё ранее прочитанное… Он может, а вы – нет. Вы поближе знакомитесь с этим парнишкой, и под видом стороннего разговора начинаете расспрашивать его о микроконтроллерах и их возможностях. И он спокойно говорит о том, что контроллеры для него хобби. Вы просите его рассказать вам об их устройстве. Его ответы просты и незатейливы.

Микроконтроллеры. Что такое микроконтроллер?
Микроконтроллер - это маленький специализированный компьютер, по-русски микро-ЭВМ. Причем, эта микро-ЭВМ выполнена в одной микросхеме, на одном кристалле. Отсюда и полное название: «однокристальная микро-ЭВМ». Как и компьютер, микроконтроллер - это электронное устройство, работой которого управляет программа - последовательность команд, заранее загруженная в память. Эти команды выполняет процессор: этакий «мега-мозг», имеющий в своем составе АЛУ - арифметически-логическое устройство. Т.е, процессор «умеет» выполнять математические действия и производить логические операции над данными.

Разрядность процессора. Методы представления информации.
И процессор, и память - цифровые устройства, которые «понимают» сигналы только двух уровней: есть напряжение/ток, и нет напряжения/тока на линии. Эти два состояния принято записывать так: логическая единица - «1», и логический ноль - «0». Команды и данные - это набор единичек и нулей. Одна линия (называют разрядом) при двух её состояниях может передать только два значения. Но при увеличении количества разрядов увеличивается и количество значений: два разряда - уже четыре, а восемь разрядов - уже 256 значений. Разряд принято называть битом: один разряд это один бит. А набор из восьми разрядов - байтом: восемь бит это один байт. Но один байт имеет только 256 значений. Для передачи большего количества информации используется несколько, последовательно расположенных в памяти, байт. Два байта передают уже 65536 значений. Три байта - 16777216 значений! И так далее. Самыми распространенными являются процессоры, которые за одно действие могут обработать восемь разрядов, поэтому такие процессоры и называют восьмиразрядными.

Система команд процессора.
При разработке процессора в него закладывают возможность выполнения определенных команд. Команды, которые данный процессор в состоянии выполнять, называют набором команд. Что это за команды? Самые распространенные арифметические и логические команды, а также команды работы с портами - линиями связи процессора с внешним миром. Процессор, считав значение из ячейки памяти или состояние порта в собственную память - регистр, может произвести над ним математические или логические действия. Математически действия нам понятны: сложение, вычитание и другие. Под логическими действиями понимаются такие действия: сравнение - больше, меньше, равно; работа над разрядами ячейки памяти или регистра: обнуление или его установка, а также операции сдвига разрядов влево или вправо.

Память и её типы.
Данные могут быть считаны из памяти. Память - место, где какое-то время могут храниться программа и/или данные. Они могут храниться кратковременно - до отключения питания, или долговременно - независимо от наличия напряжения питания. Память первого типа используется для хранения промежуточных данных, используемых при выполнении различных операций. Поэтому её так и называют - «оперативная память». Память второго типа чаще используется для хранения программы. Типов долговременной памяти несколько: однократно программируемая память, память с электрическим стиранием и память, стираемая ультрафиолетовым или рентгеновским излучением. Физическое устройство и принцип работы памяти может быть разным, но суть одна: хранить данные. Для описания хранилища данных используют понятие «ячейка». Следовательно, чем больше ячеек, тем больше данных может быть сохранено. У каждой из ячеек имеется индивидуальный адрес. Процессор обращается к значению ячейки памяти именно по её адресу.

Порты. Режимы работы портов.
Данные так же могут быть получены из внешних устройств через линии связи - выводы микроконтроллера. Эти линии связи называют портами, а по-научному: устройства ввода и вывода данных. Выводы порта могут быть входами, с использованием которых процессор получает информацию извне от разных датчиков, или быть выходами, подавая сигналы на которые можно управлять внешними устройствами. В современных микроконтроллерах выводы практически всех портов двунаправленные, т.е., могут быть и входами, и выходами. Универсальные порты необходимо настраивать - установить режим работы на ввод или вывод. Для этого имеется специальная ячейка в памяти - регистр управления режимами работы порта. Например, для того, чтобы сделать входом необходимый вывод (разряд) порта, в разряд регистра управления записывают 1 или 0, в зависимости от модели микроконтроллера.

Периферийные устройства.
Но микроконтроллер содержит в своем составе не только процессор и память. Основную роль играют так называемые периферийные устройства: таймеры, счетчики, аналоговые компараторы, цифро-аналоговые и аналогово-цифровые преобразователи, устройства последовательного обмена информацией (часто их называют последовательным портом). Часто микроконтроллер имеет и некоторое количество ячеек энергонезависимой памяти (чаще всего Flash), в которой могут быть сохранены различные данные.

Семейства микроконтроллеров.
Наличие всех перечисленных устройств в составе микроконтроллера необязательно. Чаще всего производитель выпускает несколько моделей изделий, имеющих в своем составе различные периферийные устройства. Микроконтроллеры с одним типом процессора (и набором исполняемых машинных кодов), но различными периферийными устройствами, относят к одному семейству. Так и говорят: микроконтроллеры семейства ATtiny.

Многофункциональность выводов микроконтроллера.
Может возникнуть вопрос: как все эти устройства «общаются» с внешним миром, если у большинства микросхем в корпусе DIP не более 40 выводов? Для решения проблемы нехватки выводов используют метод объединения функций нескольких устройств с использованием одного вывода. Например, выводы одного из портов (8 разрядов – 8 выводов) также используется и для работы аналогово-цифрового преобразователя, а выводы другого порта - как входы аналоговых компараторов, последовательного порта или для подключения других встроенных узлов. Для управления режимами работы выводов используется специальный регистр управления режимами работы порта (о нем рассказывалось ранее, при объяснении принципов работы портов). В большинстве микроконтроллеров выводы имеют несколько функций. Если обратиться к технической документации на контроллер, то при описании функции вывода будет сделано замечание об основной и об альтернативной функции данного вывода. Например: PD0/RX - нулевой разряд порта D одновременно является и входом последовательного порта, PB1/Ain0 - первый разряд порта В одновременно является входом аналогового компаратора.

Алгоритмы. Программы.
Команды для процессора даются в определенном порядке, в соответствии с ранее разработанным алгоритмом. Алгоритм - это последовательность выполнения процессором. Причем команды должна быть понятна процессору, и при этом иметь однозначное толкование, без какой-либо самостоятельности при её выполнении. Алгоритм можно записывать словесно. Например: начало программы; сделать нулевой разряд порта входом; сделать седьмой разряд порта выходом; считать значение нулевого разряда порта; если он равен логической единице, то выполнить следующие действия: загрузить в седьмой разряд порта логическую единицу; вернуться к началу программы. Так мы описали алгоритм работы схемы, состоящей из выключателя, лампочки (или другой нагрузки) и источника питания. Результат выполнения будет таков: при нажатии кнопки на вход порта поступает напряжение, процессор выполняет программу - подает на выход порта напряжение. И пока контакты буду замкнуты, на выходе порта будет напряжение.
Но такое написание весьма сложно воспринимать. Поэтому были разработаны методы графического описания алгоритма. Вот пример графической записи вышеописанного алгоритма.
Рис. Алгоритм-1. Графический метод описания алгоритма

Команды ветвления: условный и безусловный переходы.
К особым командам процессора относятся команды условного и безусловного перехода. Для того чтобы понять это, необходимо объяснить такое понятие как «указатель адреса выполняемой команды». У процессора имеется специальный регистр, в котором хранится адрес выполняемой в текущий момент команды. При подаче питания этот регистр обнуляется - в него записывается ноль. Далее процессор начинает выполнять команды, хранящиеся в памяти, начиная с нулевого адреса - ведь в регистре указателя адреса выполняемой команды указан ноль. Выполнив команду, этот указатель инкриминируется, т.е., его значение увеличивается. Процессор считывает следующую команду из памяти по адресу, указанную в указателе. Т.е, команды выполняются последовательно. Нарушить последовательность выполнения программы можно, используя команды условного и безусловного переходов. Для этого в одной из ячеек памяти хранится команда, указывающая процессору изменить значение регистра указателя адреса выполняемой команды. Команда безусловного перехода указывает процессору изменить порядок последовательно выполнения программы и начать выполнять команды, хранящиеся в памяти, начиная с ранее указанного адреса.
Команда условного перехода сложнее: при ее выполнении проверяется выполнения какого-либо условия. Например, необходимо сравнить значение двух ячеек памяти. Если значение первой ячейки больше, то продолжить выполнение программы по адресу А, иначе (т.е., значение первой ячейки меньше) - перейти по адресу С.

Прерывания и их типы. Приоритеты прерываний.
Имеется еще один способ «заставить» процессор прекратить последовательное исполнение программы и перейти к выполнению программы по определенному адресу - вызвать «прерывание». Понятие прерывание появилось вместе с первыми процессорами. Всё дело в том, что процессор управляет менее скоростными, чем он, устройствами. Например, процессор должен обрабатывать данные до появления определенного сигнала. Приведем простой пример: процессор выполняет программу подсчета количества импульсов, поступивших на один из его портов. При нажатии кнопки процессор должен прервать выполнение этой программы и выполнить другую программу: включить какое-либо устройство (т.е., подать на один из разрядов порта логическую единицу - «1»). Как решить эту задачу? Можно в самой программе постоянно опрашивать необходимый разряд порта, к которому подключена кнопка. Но при этом часть ресурсов (скорости) процессора будет практически впустую тратиться на опрос порта. Второй способ - это использование прерываний. У процессора (следовательно, и у микроконтроллера) имеется специальный вывод. Обычно его обозначают как «Int» (англ. «Interrupt» - прерывание). При подаче сигнала на вывод «Int» происходят следующие действия:
- остановка выполнения основной программы,
- в оперативной памяти сохраняется значение регистра указателя адреса выполняемой команды (место прерывания выполнения программы),
- после чего в этот же регистр загружается новый адрес (зависит от желания производителя процессора),
- в ячейке памяти с указанным адресом расположена команда безусловного перехода: «перейти по адресу хх»,
- в памяти, начиная с ячейки с адресом хх, расположена еще одна программа, назовем ее служебной программой.

В нашем случае служебная программа должна выдать в порт логическую единицу, тем самым включив необходимое устройство. А вот тут начинается самое интересное: последней командой служебной программы является команда «выход из прерывания». Получив эту команду, процессор считывает из памяти ранее сохраненное значение регистра указателя адреса выполняемой команды и загружает его в этот регистр. Следовательно, процессор продолжает выполнение основной программы с прерванного места.
Но прерывание может быть вызвано не только внешними сигналами, но и внутренними устройствами самого микроконтроллера: таймерами, счетчиками, последовательными портами и даже энергонезависимой памятью. Опять-таки, делается это в основном для того, чтобы уменьшить количество выполняемых команд по анализу состояния этих периферийных устройств. Приведем пример: процесс записи данных в энергонезависимую память весьма длителен, за это время процессор может выполнить весьма большое количество команд. Поэтому процессор выполняет основную программу, в ней выдает команду на стирание энергонезависимой памяти, после чего продолжает выполнение основной программы. Как только очистка энергонезависимой памяти завершена схемы управления формируют сигнал прерывания от этой памяти. Процессор прерывает выполнение основной программы и начинается процесс записи данных в память. Данный способ выполнения какого-либо действия вне основной программы называют фоновым режимом. Так же часто говорят: «эта часть программы выполняется в фоновом режиме».
При работе с прерываниями необходимо быть осторожным: возможна ситуация, при которой выполнение программы и работа всего устройства может быть нарушена. Дело в том, что микроконтроллер имеет несколько прерываний. Для управления режимами работы прерываний имеется регистр управления прерываниями. Вы при настройке режимов работы прерываний вы разрешили работу нескольких прерываний - это нормальная ситуация. Но, получив внешний или внутренний сигнал прерывания основной программы и перейдя к выполнению программы обработки прерывания, вы не отключили прерывания. Процессор выполняет служебную программу и в этот момент он получает еще один сигнал прерывания. Процессор прерывает выполнение служебной программы и переходит к выполнению программы обработки нового прерывания. Несложно догадаться, к чему это может привести.
Для решения этой проблемы был разработан метод присвоения каждому из прерываний степени важности, или «приоритета прерывания». В зависимости от модели микроконтроллера приоритет прерываний может быть задан жестко (а программист лишь разрешает или запрещает обработку того или оного прерывания), или быть реализован программистов программно (т.е., приоритет прерывания зависит от предпочтений программиста и алгоритма реализации конкретной задачи).

Управляем процессором. Языки программирования. Трансляторы.
Машинные коды. Ассемблер.
Команды для процессора - последовательности единиц и нулей. Часто команды процессора называют машинными кодами, подчеркивая, что данные команды изначально рассчитаны на конкретного исполнителя - машину, но не человека. Запоминать команды из цифр (машинные коды) весьма сложно. Поэтому для упрощения работы был придуман способ замены цифровых последовательностей на более понятные человеку символьные сокращения. Например, для команды «загрузить данные» придумали понятное сокращение «ld» (англ. «load» - загрузить), для команды «сравнить» - «cp» (англ. «compare» - сравнение) и так далее. Данный метод символьной записи команд процессора называют «ассемблер». Если при работе с машинными кодами программист непосредственно вводит команды управления процессором в память устройства, то при работе с ассемблером между программой и процессором имеется своеобразный посредник, который преобразует символьные обозначения в машинные коды. Программу, являющуюся посредником, называют транслятором, т.е., переводчиком. Но тут есть маленький нюанс: ассемблером называют не только метод символьного обозначения цифровых команд (машинных кодов), но и программу-транслятор, которая помогает программисту перевести символьные обозначения команд непосредственно в машинные команды. Поэтому часто используется следующий прием: когда говорят о языке - пишут Ассемблер, когда о программе - просто ассемблер.
У Ассемблера есть огромное достоинство: программы, написанные на Ассемблере, очень быстро выполняются процессором. Дело в том, что Ассемблер - это практически машинная команда. Но у ассемблера есть и минусы: основной минус – сложность написания программ, второй - даже относительно несложные программы имеют большой обьем исходного текста, что затрудняет анализ программы.

Модульность программ. Часто повторяющиеся задачи.
Каждый программист за время своей работы накапливал определенное количество программ. Но многие программы содержат одинаковые действия. Например, во многих программах производится опрос клавиатуры и анализ нажатой кнопки. Значит, эту часть кода программы можно переносить из одной программы в другую. Из таких кусочков (модулей) постепенно сформировались библиотеки программ. Программисты стали «лепить» программу из модулей: т.е., вставляли необходимый модуль в необходимое место программы. Такой подход ускорял процесс написания программы и увеличивал надежность работы программы в целом за счет использования уже отлаженных модулей. Но с первых дней возникла проблема совместного использования модулей: ведь каждый программист писал модули по собственному «стандарту» - как ему было удобнее в тот или иной момент. Поэтому постепенно выработался стандарт (точнее, несколько стартов) написания этих модулей. В них была описана структура модулей для их более удобного «склеивания» в одну программу.

Языки программирования и их функциональное разделение.
Постепенно эти разрозненные стандарты использования модулей сформировали то, что позднее будет названо «языками программирования». Как и человеческие языки, язык программирования имеет несколько подуровней, которые определяют как написание отдельных слов (модулей) и методы их записи, так и правила их использования. Со временем языки программирования преобразовывались и видоизменялись. Постепенно все языки программирования разделились на несколько групп, в зависимости от «профессиональной ориентации»:
- прикладные языки программирования (FORTRAN для математиков, FoxPro для финансовых работников);
- универсальные (Pascal и Basic);
- системные (Ассемблер и Си).

Системные слова языки стали называть низкоуровневыми, т.е., программист работает на нижнем, наиболее приближенном к процессору, уровне. А языки, при работе с которыми программист не сталкивается с непосредственным управлением работой процессора, стали называть Языками Высокого Уровня (часто обозначают как ЯВУ). Не путайте это сокращение с названием языка «Ява» - «Java».

Трансляция программы. Методы трансляции программы.
Как и при использовании Ассемблера, программу, написанную на любом языке высокого уровня, необходимо преобразовать в понятные процессору команды. Вначале это делалось в ручную: в таблице находили команду на ассемблере и записывали ее в машинном коде. Для ускорения процесса преобразования (трансляции) программы были написаны специальные программы - трансляторы. Существует два метода трансляции программы: интерпретация и компиляция. Следовательно, и транслятор называют либо интерпретатор, либо компилятор. При использовании интерпретатора исходный текст программы анализируется и последовательно, команда за командой, выполняется интерпретатором. В интерпретаторе содержатся модули всех используемых действий. Такое покомандное преобразование очень медленное. Но данный метод имеет большой плюс: программу можно остановить, изменить ее код и продолжить её выполнение. Это удобно при отладке программы. Так же в данном случае мы имеем исходный текст программы и можем его многократно редактировать.
При использовании компилятора текст программы анализируется, и создается файл с машинными командами, так называемый исполняемый файл. Это обеспечивает очень высокую скорость выполнения откомпилированной программы - ведь преобразование текста программы в машинные коды происходит только один раз при её компиляции. Но изменить программу «на лету» не получится: необходимо изменить текст программы и заново её откомпилировать. Если исходный текст отсутствуют по какой-либо причине, то перекомпилировать программу невозможно, а изменить исполняемый файл крайне сложно.

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

Интегрированная среда разработки программ.
В последнее время появился новый подход: «Интегрированная Среда Разработки» (англ. «IDE»). Под интеграцией понимается выполнение в одной программе всего процесса создания программы: написав текст программы, программист щелчком мыши запускает трансляцию текста программы в машинные коды, после чего полученный исполняемый файл автоматически загружается в память процессорного устройства. Т.е., все делается в одной программе. Такой подход ускоряет работу программиста.

Первые сложности.
Все предыдущие главы были вводным курсом, готовящим вас к восприятию новой информации. На пути у нас имеется несколько проблем.
1. Большой объем разносторонней информации : электроника, устройство микроконтроллеров, алгоритмы, синтаксис языков программирования, описания работы с программным инструментарием. И как писать? Один читатель - хороший электронщик, но ни разу не писал программы, другой - программист, но электроника – на уровне радио кружка, третий - что-то среднее...

2. Выбор МК : если все микроконтроллеры хороши, то на основе какого изделия и какого производителя строить процесс дальнейшего обучения и практического применения микроконтроллеров?
Для того чтобы выбрать микроконтроллер для ОБУЧЕНИЯ нам необходимо выполнить следующие условия:
А) выбранный для обучения микроконтроллер должен быть доступным и недорогим.
Б) он должен быть современным изделием, но не самым новым.

Теперь подробнее о каждом пункте.
С пунктом А всё понятно: какой смысл изучать изделие, которое трудно приобрести или его цена заоблачная для новичка.
Пункт Б требует пояснения. Дело в том, что новые изделия всегда имеют какие-либо недоработки. Они обнаруживаются только через какие-то время, пока кто-то случайно не наткнётся на данную проблему в ходе работы с данным изделием. Но новинки не сразу попадают в новые конструкции: требуется время на написание программ для новых моделей. Тут присутствует человеческий фактор: у разработчиков уже имеются готовые решения на предыдущих моделях микроконтроллеров, и переходить на новые - сложно.
Также все новые микроконтроллеры имеют только фирменное описание. А оно написано на английском языке и с использованием многочисленных профессиональных терминов: ведь на профессионалов и рассчитано! А мы - ученики… Через какие-то время появляются примеры конструкций, более подробные описания с многочисленными комментариями и советами. Потом кто-то начнет переводить документацию на русский язык (не всё, но хотя бы самое сложное или наиболее часто используемое).
К новому микроконтроллеру может не быть и инструментария: компиляторы, отладчики и программаторы «не понимают» это изделие. Опять ожидание, пока авторы этих программ не обновят свои творения...

3. Необходимо выбрать язык программирования , на котором мы планируем писать программы для МК.
Выбор языка программирования - весьма щепетильное занятие. Для обучения программированию микроконтроллеров хотелось бы использовать язык программирования с простым синтаксисом: программист должен заниматься программой, но не ее оформлением!
Тут надо заранее сделать пояснение: в настоящее время среди разработчиков программ и устройств на микроконтроллерах популярны три «семейства» языков: Си (Пишут как «С»), Паскаль (Pascal) и Бейсик (BASIC). Паскаль изначально разрабатывался как инструмент изучения программированию. Бейсик самой структурой похож на Паскаль, но запись команд упрощена и требований к оформлению программы значительно меньше. Си – принято считать языком для профессионалов. Си – это как китайская философия: важен не только символ (команда), но и его начертание и цвет. Шутки шутками, но моё мнение таково: Си – это кошмар. Его использование оправдывается лишь в некоторых, весьма узко профильных, задачах. Но наша задача – попробовать свои силы, и минимально их тратить на задачи, не имеющих прямой связи с основной целью.

4. Нам необходима среда разработки программ для микроконтроллеров . Её выбор напрямую зависит от типа используемого МК и языка программирования.
Среда разработки программ очень важна для успешного освоения программирования микроконтроллеров. Писать программы в текстовом редакторе вроде «Блокнота» можно, но неудобно (проверено!). Да и в командной строке вызывать компилятор - дело неблагодарное в наш, графическо-оконный, век.
Выбор среды разработки напрямую зависит от микроконтроллера, на котором мы будет строить практическую часть обучения. Ко всему прочему нам надо иметь бесплатный инструментарий. Но, как показало тестирование таких программ, бесплатное ПО чаще всего имеет посредственное качество как с точки зрения и пользования, так и с точки зрения изучения программирования МК: наличие ошибок или недоработок в самих трансляторах создают дополнительные сложности и лишают уверенности в собственных силах.
Сойдет и демонстрационная версия, которая имела бы минимум ограничений и работала хотя бы пол года - именно такой срок необходим для получения навыков работы с микроконтроллерами в домашних условиях.

5. Программатор, с использованием которого будем загружать написанные программы в память МК . Выбор программатора также зависит от типа используемого МК. Есть, конечно, «универсальные» программаторы, позволяющие работать с разными микроконтроллерами и микросхемами памяти, но они дорогие. Да и не нужны в большинстве случаев. Поэтому проще изготовить что-то узкоспециализированное для данного семейства МК.
Но дело не столько в сложности схем программатора, а в методе подключения этого программатора к ПК. Тут необходимо пояснить: программатор - это электронный адаптер, преобразующий сигналы компьютерных интерфейсов (порты СОМ, LPT и USB) в сигналы, подаваемые на выводы МК для загрузки программы в его память. Электронным адаптером управляет программа ПК, которая и «заставляет» адаптер выдавать необходимые последовательности сигналов на выводы МК.
Если адаптер программатора, подключаемый к ПК через порты COM и LPT, возможно изготовить в домашних условиях - «на коленке», то изготовления такого адаптера, но подключаемого к USB порту, уже несколько проблематично: сердцем такого адаптера часто является… микроконтроллер. Тут возникает парадокс: для того чтобы запрограммировать МК нам необходимо запрограммировать МК.
Напрашивается логичный вопрос: а для чего изготавливать сложный адаптер, подключаемый к USB, когда можно сделать простой и подключить его к LPT или COM порту. Все дело в том, что многие (практически все) современные ПК не имеют в своем составе этих портов. Поэтому придется изготавливать более сложный адаптер для программирования МК.

Март 2010

Эти вопросы я задавал себе в марте, а сейчас уже конец ноября. Но это время не прошло даром: я нашел выход из ситуаций, описанных выше, и нашел ответы на все мучавшие меня вопросы. А теперь обо всём по порядку.

Ответ на вопрос номер 1
Если материалы предыдущих глав еще как-то можно было логически систематизировать и преподносить поэтапно, то материалы в последующих главах даются параллельно: одно подразумевает другое. Возможно, вам мой метод подачи новых материалов покажется несколько сумбурным, но придумать что-то более красивое по оформлению я не смог.

Ответ на вопрос номер 2
Микроконтроллер производства компании ATMEL ATMEGA48. Хорошо описан, выпускается уже несколько лет, не планируется к снятию с производства еще как минимум 3 года, имеет оптимальные технические параметры.

Ответы на вопросы 3 и 4
Среда программирования - BASCOM (производитель MCS Electronics, автор Марк Альбертс). Язык программирования по стилю и требованиям к оформлению текста программы схож с Паскалем, но синтаксис команд взят из BASIC.
Причины выбора:
- полнофункциональная демонстрационная версия компилятора (единственное ограничение: генерируемый компилятором код ограничен объемом 4 КБайт)
- желание автора программы сотрудничать (я сделал перевод сообщений интерфейса и справочной системы на русский язык, он добавил русский язык в эту программу)
- наличие русскоязычного форума пользователей данного компилятора

Ответ на вопрос номер 5
Совместить простоту схемы и USB не получилось. Было решено описать две модели программаторов: одна подключается к LPT порту компьютера, вторая к COM порту. При отсутствии этих портов вторая версия программатора может быть подключена к компьютеру с использованием преобразователя USB-COM. Так получаем связку USB-COM-программатор-микроконтроллер.
Первая модель программатора известна как STK-200/300, содержит микросхему буфера с третьим состоянием и несколько резисторов. Вторая модель - известный программатор USBasp.

Всё популярнее становится тема электронного конструирования. Предлагаем вашему вниманию статью, которая расскажет, микроконтроллеров для начинающих.

Какие микроконтроллеры существуют?

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

Как микроконтроллеры программируются?

Программирование микроконтроллеров осуществляется, как правило, с помощью специальных приспособлений, которые называются программаторами. Программаторы могут быть или покупными или самодельными. Но при прошивке микроконтроллера с помощью самодельного программатора шанс того, что он превратится в «кирпич», довольно высокий. Есть ещё один вариант, который можно рассмотреть на примере платы «Ардуино». Плата работает на МК фирмы Atmel, и в ней осуществляется программирование микроконтроллеров AVR. В плате уже есть заранее прошитый бутлоадер и порт USB, которые позволяют безопасно прошить используемый микроконтроллер, не давая пользователю доступа к данным, что могут этот самый МК вывести из строя. Программирование микроконтроллеров для начинающих не так сложно, как может показаться, и при определённой сноровке и сообразительности избавит вас от необходимости ехать за новым механизмом.

Аппаратные различия разных микроконтроллеров

При выборе микроконтроллеров следует обратить внимание на некоторые аппаратные различия даже не разных компаний, а и в одном модельном ряду. Для начала следует обратить внимание на возможность перезаписи информации на микроконтроллер. Эта функция позволит вам долго экспериментировать с одним МК. Также обратите внимание на количество выводов с их предназначением. Не обделяйте вниманием и частоту работы кристалла, на котором работает схема: от неё зависит количество операций в секунду, которые может выполнить микроконтроллер. При осмотре этих характеристик, а также памяти МК сначала может показаться, что на микроконтроллерах ничего толкового не сделаешь, но это ошибочное мнение. Помните, что программирование микроконтроллеров для начинающих не требует самой лучшей техники вначале, но про запас вы можете взять и что-то более мощное.

Языки программирования микроконтроллеров

В качестве языков программирования микроконтроллеров используется два: С/С++ и ассемблер. Каждый из них имеет свои преимущества и недостатки. Так, если говорить про ассемблер, то он даёт возможность сделать всё очень тонко и качественно, особенно важно это, когда не хватает оперативной памяти или оперативных мощностей (что, впрочем, довольно редко происходит). Но его изучение и написание программ на нём требует довольно много усилий, пунктуальности и времени. Поэтому для разработок на основе микроконтроллеров часто используют языки программирования С и С++. Они являются более понятными, по своему виду и структуре они близки человеческой речи, хотя и не представляют из себя её в полноценном понимании. Также они имеют очень хорошо проработанный функционал, который может запросто взаимодействовать с аппаратной частью, представляя, что это всего лишь элемент программы. При всех своих явных преимуществах на С и С++ создают более объемные программы, нежели на ассемблере.

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

Напутствие

Если появилось желание поэкспериментировать с прекрасно. Можно только посоветовать запастись терпением и настойчивостью, и тогда любые цели, поставленные перед изобретателем, окажутся осуществимыми. Программирование микроконтроллеров для начинающих и для опытных людей выглядит по-разному: что для начинающих сложно, то для опытных - рутина. Главное - помнить, что всё, что не противоречит законам физики, является осуществимым и решаемым.

Декабрь 2015 г.

1. Преимущества предлагаемого способа

Схемы устройств на микроконтроллерах (МК) обычно отличаются сочетанием двух трудносовместимых качеств: максимальной простотой и высокой функциональностью. К тому же функциональность может в дальнейшем меняться и расширяться без внесения каких-либо изменений в схему - путём лишь замены программы (перепрошивкой). Эти особенности объясняются тем, что создатели современных МК постарались разместить на одном кристалле всё, что только может потребоваться разработчику электронного устройства - по крайней мере настолько, насколько это возможно. В результате произошло смещение акцента со схемотехнического и монтажного на программный. С использованием МК теперь меньше приходится "нагружать" схему деталями, между компонентами становится меньше соединений. Это, конечно, делает схему более привлекательной для её повторения как опытными, так и начинающими электронщиками. Но, как обычно, за всё приходится платить. Здесь тоже не обошлось без своих сложностей. Если купить новый МК, установить его в правильно собранную из исправных деталей схему и подать питание, то ничего не получится - устройство не будет работать. Микроконтроллеру нужна программа.

Казалось бы с этим тоже всё просто - в интернете можно встретить множество схем с бесплатными прошивками. Но тут возникает одна загвоздка: прошивку необходимо как-то "залить" в микроконтроллер. Для того, кто никогда этим раньше не занимался, такая задача зачастую становится проблемой и главным отталкивающим фактором, нередко заставляющим отказаться от прелестей использования МК и поискать схемы на "рассыпухе" и жесткой логике. А ведь всё не так уж сложно, как может показаться на первый взгляд.

Проанализировав публикации в интернете, можно заметить, что данная проблема решается чаще всего одним из двух путей: покупкой готового программатора или изготовлением самодельного. При этом публикуемые схемы самодельных программаторов очень часто неоправданно сложны - гораздо сложнее, чем это действительно необходимо. Конечно, если предполагается каждый день прошивать МК, лучше иметь "крутой" программатор. Но если надобность в такой процедуре возникает нечасто, от случая к случаю, то можно вообще обойтись без программатора. Нет, конечно, речь идет не о том, чтобы научиться делать это силой мысли. Имеется в виду, что понимая, как происходит взаимодействие программатора с микроконтроллером при записи и считывании информации в режиме его программирования, мы можем обойтись подручными средствами более широкого назначения. Эти средства должны будут заменить как программную, так и аппаратную части программатора. Аппаратная часть должна обеспечить физическое соединение с микросхемой МК, возможность подавать логические уровни на его входы и считывать данные с его выходов. Программная часть должна обеспечить работу алгоритма, управляющего всеми необходимыми процессами. Отметим также, что качество записи информации в МК не зависит от того, насколько "крутой" у вас программатор. Такого понятия, как "лучше записалось" или "хуже" не существует. Есть только два варианта: "записалось" и "не записалось". Это объясняется тем, что непосредственно процессом записи внутри кристалла руководит сам МК. Нужно лишь обеспечить ему качественное питание (отсутствие помех и пульсаций) и правильно организовать интерфейс. Если по результатам контрольного считывания ошибок не выявлено, то все в порядке - можно использовать контроллер по назначению.

Для того, чтобы, не имея программатора, записать в МК программу, нам потребуется преобразователь порта USB-RS232TTL и , а также . Конвертер USB-RS232TTL позволяет при помощи порта USB создать COM-порт, отличающийся от "настоящего" лишь тем, что на его входах и выходах используются логические уровни TTL, то есть напряжение в интервале от 0 до 5 вольт (подробнее можно почитать в статье " "). Такой конвертер в любом случае полезно иметь в "хозяйстве", так что если у вас его еще нет, непременно стоит приобрести. Что касается логических уровней, то в нашем случае TTL - это даже преимущество перед обычным COM-портом, потому что входы и выходы такого порта можно напрямую подключать к любому микроконтроллеру, питающемуся от напряжения 5 В, в том числе ATtiny и ATmega. Но не пытайтесь использовать обычный COM-порт - там используются напряжения в интервале от -12 до +12 В (либо -15...+15В). Непосредственное соединение с микроконтроллером в этом случае недопустимо!!!

Идея создания скрипта для программы "Перпетуум М", реализующего функции программатора, возникла после ознакомления с рядом публикаций в интернете, предлагающих те или иные решения по прошивке МК. В каждом случае обнаруживались серьезные недостатки или чрезмерные сложности. Часто попадались схемы программаторов, содержащие в себе микроконтроллер и при этом вполне серьезно давались советы типа: "... а чтобы запрограммировать микроконтроллер для этого программатора нам потребуется... правильно - другой программатор!". Далее предлагалось сходить к другу, поискать платную услугу и т.п. Качество программного обеспечения, распространяемого в сети для этих целей, также не впечатлило - замечено множество проблем как с функциональностью, так и с "мутностью" пользовательского интерфейса. Зачастую много времени нужно потратить, чтобы понять, как использовать программу - ее необходимо изучать даже ради осуществления простейших действий. Иная программа может долго и усердно что-то делать, но о том, что ничего в МК не записывается, пользователь узнает только после полного завершения всей прошивки и последующего контрольного считывания. Встречается и такая проблема: пользователь пытается выбрать из списка поддерживаемых кристаллов свой МК, а его в списке нет. В этом случае воспользоваться программой не удастся - внесение в список недостающих МК, как правило, не предусмотрено. Кроме того ручной выбор контроллера из списка выглядит странно, если учесть, что программатор во многих случаях может сам определить тип МК. Все это сказано не для того, чтобы облить грязью существующие продукты, а для того, чтобы объяснить причину появления скрипта к программе "Перпетуум М", описываемого в данной статье. Проблема действительно существует, и она касается в первую очередь новичков, которым не всегда удается преодолеть данную "стену", чтобы сделать свой первый шаг в мир микроконтроллеров. В предлагаемом скрипте учтены недостатки, обнаруженные в других программах. Реализована максимальная "прозрачность" работы алгоритма, предельно простой интерфейс пользователя, не требующий изучения и не оставляющий шанса запутаться и "не туда нажать". При отсутствии нужного МК среди поддерживаемых есть возможность самостоятельно добавить его описание, взяв нужные данные из документации, скачанной с сайта разработчика МК. И, самое главное - скрипт открыт для изучения и модификации. Каждый желающий может, открыв в текстовом редакторе, изучать и править его на свое усмотрение, изменяя на свой вкус существующие функции и добавляя недостающие.

Первая версия скрипта была создана в июне 2015 года. В этой версии реализована только поддержка Atmel"овских МК серий ATtiny и ATmega с функциями записи/чтения флэш-памяти, с настройкой конфигурационных бит, с автоматическим определением типа контроллера. Запись и чтение EEPROM не реализованы. Были планы дополнить функциональность скрипта: добавить запись и чтение EEPROM, реализовать поддержку PIC-контроллеров и т.д. По этой причине скрипт до сих пор не был опубликован. Но из-за нехватки времени осуществление задуманного затянулось, и, чтобы лучшее не становилось врагом хорошего, решено опубликовать имеющуюся версию. Если уже реализованных функций окажется недостаточно, прошу не огорчаться. В этом случае вы можете попробовать самостоятельно добавить нужную функцию. Не стану скрывать: идея создания данного скрипта изначально несет в себе еще и образовательный смысл. Разобравшись в алгоритме и добавив к нему что-то свое, вы сможете глубже понять работу МК в режиме программирования, чтобы в будущем не оказаться в положении девушки перед сломавшимся автомоблем, задумчиво разглядывающей его внутренности и не понимающей, почему "не едет".

2. Интерфейс МК в режиме программирования

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

Но, прежде чем приступить к рассмотрению сигналов, необходимых для формирования SPI, сделаем ряд оговорок. Микроконтроллер имеет конфигурационные биты. Это что-то вроде тумблеров, переключение которых позволяет менять некоторые свойства микросхемы в соответствии с нуждами проекта. Физически это ячейки энергонезависимой памяти, вроде тех, в которые записывается программа. Разница в том, что их очень мало (до трех байт для ATmega), и они не входят в адресное пространство какой-либо памяти. Запись и чтение конфигурационных данных выполняется отдельными командами режима программирования МК. Сейчас важно отметить, что некоторые конфигурационные биты влияют на саму возможность использования SPI. При некоторых их значениях может оказаться, что SPI нельзя будет использовать. Если вам попадется такой микроконтроллер, то метод, предлагаемый в данной статье, не поможет. В этом случае придется либо изменить настройки конфигурационных бит в программаторе, который поддерживает иной режим программирования, либо использовать другой микроконтроллер. Но данная проблема касается только бывших в употреблении МК, либо тех, с которыми уже кто-то неудачно "поигрался". Дело в том, что новые МК поставляются с настройками конфигурационных бит, не препятствующими использованию SPI. Это подтверждается и результатами испытаний скрипта-программатора для программы "Перпетуум М", во время которых были успешно прошиты четыре разных МК (ATmega8, ATmega128, ATtiny13, ATtiny44). Все они были новые. Начальная настройка конфигурационных бит соответствовала документации и не мешала использованию SPI.

Учитывая сказанное выше, следует обращать внимание на следующие биты. Бит SPIEN в явном виде разрешает или запрещает использование SPI, следовательно в нашем случае его значение должно быть разрешающим. Бит RSTDISBL способен превратить один из выводов микросхемы (заранее предопределенный) во вход сигнала "сброс", либо не превратить (в зависимости от записанного в этот бит значения). В нашем случае вход "сброс" необходим (при его отсутствии не получится перевести МК в режим программирования через SPI). Есть еще биты группы CKSEL, задающие источник тактового сигнала. Они не препятствуют использованию SPI, но их тоже необходимо иметь в виду, потому что при полном отсутствии тактовых импульсов, либо при их частоте ниже допустимой для заданной скорости SPI, также ничего хорошего не получится. Обычно у новых МК, имеющих внутренний RC-генератор, биты группы CKSEL настроены на его использование. Нас это вполне устраивает - тактирование обеспечено без каких-либо дополнительных усилий с нашей стороны. Ни кварцевый резонатор припаивать, ни внешний генератор подключать не нужно. Если же указанные биты содержат иную настройку, придется позаботится о тактировании в соответствии с настройкой. В этом случае может потребоваться подключение к МК кварцевого резонатора или внешнего тактового генератора. Но в рамках данной статьи мы не будем рассматривать, как это делается. Примеры подключения МК для программирования, содержащиеся в данной статье, рассчитаны на самый простой случай.

Рис. 1. Обмен данными по SPI в режиме программирования

Теперь обратимся к рисунку 1, взятому из документации на МК ATmega128A. На нем показан процесс передачи одного байта в МК и одновременного приема одного байта из МК. Оба эти процесса, как видим, используют одни и те же тактовые импульсы, поступающие от программатора в микроконтроллер на его вход SCK - один из выводов микросхемы, для которого в режиме программирования по SPI отведена такая роль. Еще две сигнальные линии обеспечивают прием и передачу данных по одному биту за такт. Через вход MOSI данные поступают в микроконтроллер, а с выхода MISO снимаются считываемые данные. Обратите внимание на две пунктирные линии, проведенные от SCK к MISO и MOSI. Они показывают, в какой момент микроконтроллер "проглатывает" выставленный на входе MOSI бит данных, и в какой момент сам выставляет на выход MISO свой бит данных. Все достаточно просто. Но чтобы ввести МК в режим программирования нам еще потребуется сигнал RESET. Не забудем также про общий провод GND и питание VCC. В общей сложности выходит, что к микроконтроллеру для его прошивки по SPI нужно подключить всего 6 проводков. Ниже разберем это подробнее, а пока добавим, что обмен данными с МК в режиме программирования по SPI выполняется пакетами по 4 байта. Первый байт каждого пакета в основном полностью отводится под кодирование команды. Второй байт в зависимости от первого может быть продолжением кода команды, либо частью адреса, а может иметь произвольное значение. Третий байт используется в основном для передачи адресов, но во многих командах может иметь произвольное значение. Четвертый байт обычно передает данные, либо имеет произвольное значение. Одновременно с передачей четвертого байта в некоторых командах принимаются данные, поступающие из МК. Подробности по каждой команде можно найти в документации на контроллер в таблице под названием "SPI Serial Programming Instruction Set". Пока отметим лишь, что весь обмен с контроллером построен из последовательности 32-битных пакетов, в каждом из которых передается не более одного байта полезной информации. Это не очень оптимально, но в целом работает неплохо.

3. Подключение МК для программирования

Чтобы обеспечить подачу на входы микроконтроллера всех необходимых сигналов для организации интерфейса SPI и чтение данных с его выхода MISO, не обязательно создавать программатор. Это легко осуществить при помощи самого обыкновенного конвертера USB-RS232TTL.

В интернете часто можно встретить информацию о том, что такие конвертеры неполноценны, что с ними ничего серьезного сделать нельзя. Но в отношении большинства моделей конвертеров такое мнение ошибочно. Да, существуют в продаже конвертеры, у которых доступны не все входы и выходы по сравнению со стандартным COM-портом (например, только TXD и RXD), имеющие при этом неразборную конструкцию (микросхема залита пластмассой - невозможно добраться до ее выводов). Но такие и покупать не стоит. В некоторых случаях получить недостающие входы и выходы порта можно, подпаяв проводки непосредственно к микросхеме. Пример такого "усовершенствованного" конвертера показан на рисунке 2 (микросхема PL-2303 - подробнее о назначении ее выводов в статье " "). Это одна из самых дешевых моделей, но обладающая своими преимуществами при использовании в самодельных конструкциях. Широко распространены и полнофункциональные шнуры-переходники со стандартным девятиконтактным разъемом на конце, как у COM-порта. От обычного COM-порта они отличаются только уровнями TTL и несовместимостью с устаревшим программным обеспечением и некоторым старым оборудованием. Можно еще отметить, что шнуры на микросхеме CH34x на различных экстремальных тестах показывают себя гораздо более надежными и стабильными по сравнению с преобразователями на PL-2303. Впрочем, при обычном использовании разница не заметна.

При выборе конвертера USB-RS232TTL следует также обращать внимание на совместимость его драйвера с версией используемой операционной системы.

Рассмотрим подробнее принцип соединения микроконтроллера и конвертера USB-RS232TTL на примере четырех разных моделей МК: ATtiny13, ATtiny44, ATmega8 и ATmega128. На рисунке 3 показана общая схема такого соединения. Вас может удивить, что сигналы RS232 (RTS, TXD, DTR и CTS) используются не по назначению. Но не стоит об этом беспокоиться: программа "Перпетуум М" способна работать с ними напрямую - устанавливать значения на выходах и читать состояния входа. Во всяком случае широко распространенные конвертеры USB-RS232TTL на микросхемах CH34x и PL-2303 такую возможность обеспечивают - это проверено. С другими популярными конвертерами также проблем быть не должно, так как для доступа к порту используются стандартные функции Windows.

Резисторы, показанные на общей схеме, в принципе можно не устанавливать, но все-таки лучше установить. Каково их назначение? Используя ТТЛ"овские входы и выходы конвертера и пятивольтное питание микроконтроллера, мы тем самым избавляемся от необходимости согласования логических уровней - все и так вполне корректно. Значит, соединения могут быть непосредственными. Но во время экспериментов бывает всякое. Например по закону подлости отвертка может упасть как раз в то место, куда она никак не могла бы упасть, и замкнуть то, что ни в коем случае нельзя замыкать. В роли "отвертки", конечно, может оказаться все, что угодно. Резисторы в этом случае иногда уменьшают последствия. Еще одно их назначение состоит в устранении возможного конфликта выходов. Дело в том, что по окончании программирования микроконтроллер переходит в обычный режим работы, и может так получиться, что его вывод, соединенный с выходом конвертера (RTS, TXD или DTR) тоже становится выходом, согласно только что записанной в МК программе. В этом случае будет очень нехорошо, если два напрямую соединенных выхода будут "бороться" - пытаться установить разные логические уровни. В такой "борьбе" кто-то может и "проиграть", а нам этого не надо.

Номиналы трех резисторов выбраны на уровне 4,3 КОм. Это касается соединений выход конвертера - вход микроконтроллера. Точность резисторов роли не играет: можно уменьшить их сопротивление до 1 КОм или увеличить до 10 КОм (но во втором случае увеличивается риск помех при использовании длинных проводов на пути к МК). Что же касается соединения вход конвертера (CTS) - выход микроконтроллера (MISO), то здесь применен резистор сопротивлением 100 Ом. Это объясняется особенностями входа использованного конвертера. Во время испытаний был использован конвертер на микросхеме PL-2303, входы которой, судя по всему, подтянуты к плюсу питания относительно низким сопротивлением (порядка нескольких сот Ом). Чтобы "перебить подтяжку" пришлось поставить резистор со столь маленьким сопротивлением. Впрочем, можно его вообще не ставить. На конвертере это всегда вход. Выходом он стать не может, а значит, конфликта выходов не будет при любом развитии событий.

Если микросхема имеет отдельный вывод AVCC для питания аналогово-цифрового преобразователя (например, ATmega8 или ATmega128), его следует соединить с выводом общего питания VCC. Некоторые микросхемы имеют более одного вывода питания VCC или более одного GND. Например, ATmega128 имеет 3 вывода GND и 2 вывода VCC. В постоянной конструкции одноименные выводы лучше соединить между собой. В нашем же случае на время программирования можно задействовать по одному выводу VCC и GND.

А вот как выглядит подключение ATtiny13. На рисунке показано назначение выводов, используемых при программировании через SPI. Рядом на фото - как временное подключение выглядит в реальности.


Кто-то может сказать, что это несерьезно - соединения на проводках. Но мы же с вами люди здравомыслящие. Наша цель состоит в том, чтобы запрограммировать микроконтроллер, затратив на это минимум времени и прочих ресурсов, а не в том, чтобы перед кем-то покрасоваться. Качество при этом не страдает. Метод "на проводках" в данном случае вполне эффективен и оправдан. Прошивка контроллера - процедура разовая, поэтому нет смысла обвешивать ее "стразиками". Если же предполагается менять прошивку в дальнейшем, не извлекая контроллер из схемы (в готовом изделии), то это учитывается в монтаже при изготовлении устройства. Обычно для этой цели устанавливается разъем (RESET, SCK, MOSI, MISO, GND), а МК может быть прошит даже после установки на плату. Но это уже творческие изыски. Мы же рассматриваем самый простой случай.

Теперь перейдем к МК ATtiny44. Здесь все примерно так же. По рисунку и фото даже новичку не составит труда разобраться с подключением. Подобно ATtiny44 можно подключать МК ATtiny24 и ATtiny84 - назначение выводов у этой троицы совпадает.


Еще один пример временного подключения контроллера для его программирования - ATmega8. Здесь выводов побольше, но принцип тот же - несколько проводков, и вот уже контроллер готов к "заливке" в него информации. Лишний черный провод на фото, идущий от вывода 13, в программировании участия не принимает. Он предназначен для снятия с него звукового сигнала после выхода МК из режима программирования. Это связано с тем, что во время отладки скрипта для "Перпетуум М" в МК закачивалась программа музыкальной шкатулки.


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

Для полноты картины посмотрим подключение микросхемы МК с большим числом "ножек". Назначение лишнего черного провода на фото, идущего от вывода 15, точно такое же, как в случае с ATmega8.


Вероятно, вы уже убедились, что все достаточно просто. Кто умеет считать выводы у микросхем (от метки по кругу против часовой стрелки), тот разберется. И не забывайте про аккуратность. Микросхемы любят аккуратных и не прощают небрежного к себе отношения.

Прежде чем переходить к программной части, убедитесь, что драйвер конвертера USB-RS232TTL корректно установлен (проверьте диспетчер устройств Windows). Запомните или запишите номер виртуального COM-порта, появляющегося при подключении конвертера. Этот номер нужно будет вписать в текст скрипта, о котором читайте ниже.

4. Скрипт - программатор для "Перпетуум М"

С аппаратной частью "программатора" разобрались. Это уже полдела. Теперь осталось разобраться с программной частью. Ее роль будет выполнять программа "Перпетуум М" под управлением скрипта, в котором и реализованы все необходимые функции по взаимодействию с микроконтроллером.

Архив со скриптом следует распаковать в ту же папку, где находится программа perpetuum.exe. В этом случае при запуске файла perpetuum.exe на экран будет выводиться меню со списком установленных скриптов, среди которых будет строка "Программатор МК AVR" (она может быть единственной). Именно эта строка нам и потребуется.

Скрипт находится в папке PMS в файле "Программатор МК AVR.pms". Этот файл можно просматривать, изучать и править при необходимости в обычном текстовом редакторе вроде "Блокнота" Windows. Перед использованием скрипта скорее всего потребуется внести изменения в текст, связанные с настройкой порта. Для этого уточните в диспетчере устройств Windows имя используемого порта и, при необходимости, внесите соответствующую поправку в строку "ИмяПорта="COM4";" - вместо цифры 4 может стоять другая цифра. Также при использовании другой модели конвертера USB-RS232TTL может потребоваться изменение настроек инвертирования сигналов (строки скрипта, начинающиеся со слова "Высокий"). Проверить инвертирование сигналов конвертером USB-RS232TTL можно с помощью одного из примеров, содержащегося в инструкции к программе "Перпетуум М" (раздел функций для работы с портом).

Во вложенной папке MK_AVR находятся файлы с описаниями поддерживаемых контроллеров. Если нужного контроллера среди них не окажется, вы можете добавить нужный самостоятельно, действуя по аналогии. Возьмите за образец один из файлов, и при помощи текстового редактора введите необходимые данные, взяв их из документации на свой микроконтроллер. Главное - будьте внимательны, вводите данные без ошибок, иначе МК не запрограммируется, или запрограммируется неправильно. В исходной версии поддерживаются 6 микроконтроллеров: ATtiny13, ATtiny24, ATtiny44, ATtiny84, ATmega8 и ATmega128. В скрипте реализовано автоматическое распознавание подключенного контроллера - вручную указывать не нужно. При отсутствии считанного из МК идентификатора среди имеющихся описаний, выдается сообщение, что распознать контроллер не удалось.

В архиве со скриптом содержится также дополнительная информация. В папке "inc-файлы контроллеров AVR" находится очень полезная и обширная коллекция файлов описаний контроллеров. Эти файлы используются при написании собственных программ для МК. Еще четыре папки "MusicBox_..." содержат файлы с программой на Ассемблере и готовой к закачке в МК прошивкой отдельно для ATtiny13, ATtiny44, ATmega8 и ATmega128. Если вы уже подключили один из этих МК для программирования, как это предложено в данной статье, то можете прямо сейчас его прошить - получится музыкальная шкатулка. Об этом ниже.

При выборе в меню скриптов строчки "Программатор МК AVR", скрипт начинает исполняться. При этом он открывает порт, посылает в МК команду перехода в режим программирования, принимает подтверждение от МК об успешном переходе, запрашивает идентификатор МК и отыскивает описание данного МК по его идентификатору среди имеющихся файлов с описаниями. Если не находит нужного описания, выдает соответствеющее сообщение. Если же описание найдено, далее открывается главное меню программатора. Его скриншот вы можете видеть на рисунке 8. Далее разобраться не сложно - меню очень простое.

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

Некоторые ограничения функциональности описаны непосредственно в тексте скрипта:
//реализована запись только с нулевого адреса (Extended Segment Address Record игнорируется, LOAD OFFSET - тоже)
//порядок и непрерывность следования записей в HEX-файле не проверяется
//контрольная сумма не проверяется
Это касается работы с HEX-файлом, из которого берется код прошивки для МК. Если этот файл не искажен, проверка контрольной суммы ни на что не повлияет. Если искажен - средствами скрипта это выявить не удастся. Остальные ограничения в большинстве случаев не помешают, но иметь в виду их все-таки нужно.

5. Музыкальная шкатулка - простая поделка для начинающих

Если у вас есть один из этих микроконтроллеров: ATtiny13, ATtiny44, ATmega8 или ATmega128, вы можете легко превратить его в музыкальную шкатулку или музыкальную открытку. Для этого достаточно записать в МК соответствующую прошивку - одну из тех четырех, которые размещены в папках "MusicBox_..." в одном архиве со скриптом. Коды прошивок хранятся в файлах с расширением ".hex". Использовать ATmega128 для такой поделки, конечно, "жирновато", как и ATmega8. Но это может быть полезно для тестирования или экспериментов, иначе говоря - в учебных целях. Тексты программ на Ассемблере также прилагаются. Программы создавались не с нуля - за основу была взята программа музыкальной шкатулки из книги А.В.Белова "Микроконтроллеры AVR в радиолюбительской практике". Исходная программа претерпела ряд существенных изменений:
1. адаптирована для каждого из четырех МК: ATtiny13, ATtiny44, ATmega8 и ATmega128
2. ликвидированы кнопки - к контроллеру вообще ничего не нужно подключать, кроме питания и звукоизлучателя (мелодии воспроизводятся одна за другой в бесконечном цикле)
3. длительность каждой ноты уменьшена на длительность паузы между нотами для устранения нарушения музыкального ритма
4. подключена восьмая мелодия, незадействованная в книжной версии
5. из субъективного: некоторые "улучшайзинги" для оптимизации и более легкого восприятия алгоритма

В некоторых мелодиях слышится фальшь и даже грубые ошибки, особенно в "Улыбке" - в середине. Коды мелодий взяты из книги (а точнее - скачаны с сайта автора книги вместе с исходным asm-файлом) и не подвергались изменениям. Судя по всему, в кодировке мелодий имеются ошибки. Но это не проблема - кто "дружит" с музыкой, без труда во всем разберется и исправит.

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

Насчет конфигурационных бит. Их настройка должна соответствовать состоянию нового микроконтроллера. Если ваш МК ранее где-то использовался, нужно проверить состояние его конфигурационных бит, и, при необходимости, привести их в соответствие настройкам нового микроконтроллера. Узнать состояние конфигурационных бит нового микроконтроллера можно из документации на этот МК (раздел "Fuse Bits"). Исключение составляет ATmega128. У этого МК имеется бит M103C, который включает режим совместимости с более старым ATmega103. Активизация бита M103C сильно урезает возможности ATmega128, причем у нового МК этот бит активен. Нужно сбросить M103C в неактивное состояние. Для манипуляций с конфигурационными битами используйте соответствующий раздел меню скрипта-программатора.

Схему музыкальной шкатулки приводить нет смысла: в ней только микроконтроллер, питание и пьезозвукоизлучатель. Питание подается точно так же, как мы это проделали при программировании МК. Звукоизлучатель подключается между общим проводом (вывод GND контроллера) и одним из выводов МК, номер которого можно посмотреть в файле с ассемблерным кодом программы (*.asm). В начале текста программы для каждого МК в комментариях имеется строчка: "звуковой сигнал формируется на выводе ХХ". При завершении работы скрипта - программатора микроконтроллер выходит из режима программирования и переходит в обычный режим работы. Сразу же начинается воспроизведение мелодий. Подключив звукоизлучатель, можно это проверить. Оставлять звукоизлучатель подключенным во время программирования кристалла можно только в том случае, если звук снимается с вывода, не задействованного в SPI, иначе дополнительная емкость на выводе может помешать программированию.

Я не раз и не два говорил, что изучение МК надо начинать с ассемблера. Этому был посвящен целый курс на сайте (правда он не очень последовательный, но постепенно я его причесываю до адекватного вида) . Да, это сложно, результат будет не в первый день, но зато ты научишься понимать что происходит у тебя в контроллере. Будешь знать как это работает, а не по обезьяньий копировать чужие исходники и пытаться понять почему оно вдруг перестало работать. Кроме того, Си намного проще натворить быдлокода, который вылезет вилами в самый неподходящий момент.

К сожалению все хотят результат немедленно. Поэтому я решил пойти с другой стороны — сделать обучалку по Си, но с показом его нижнего белья. Хороший программист-эмбеддер всегда крепко держит свою железку за шкварник, не давая ей ни шагу ступить без разрешения. Так что будет вначале Си код, потом то что родил компилятор и как все это работает на самом деле:)

С другой стороны у Си сильная сторона это переносимость кода. Если, конечно, писать все правильно. Разделяя алгоритмы работы и их железные реализации в разные части проекта. Тогда для переноса алгоритма в другой МК достаточно будет переписать только интерфейсный слой, где прописано все обращение к железу, а весь рабочий код оставить как есть. И, конечно же, читаемость. Сишный исходник проще понять с первого взгляда (хотя.. мне, например, уже пофигу на что фтыкать — хоть си, хоть асм:)), но, опять же, если правильно все написать. Этим моментам я тоже буду уделять внимание.

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

Первая программа на Си для AVR

Выбор компилятора и установка среды
Для AVR существует множество разных компиляторов Си:
В первую очередь это IAR AVR C — почти однозначно признается лучшим компилятором для AVR, т.к. сам контроллер создавался тесном сотрудничистве Atmel и спецов из IAR. Но за все приходится платить. И этот компилятор мало того, что является дорогущим коммерческим софтом, так еще обладает такой прорвой настроек, что просто взять и скомпилить в нем это надо постраться. У меня с ним правда не срослось дружбы, проект загнивал на странных ошибках на этапе линковки (позже выяснил, что это был кривой кряк).

Вторым идет WinAVR GCC — мощный оптимизирующий компилятор. Полный опенсорц, кроссплатформенный, в общем, все радости жизни. Еще он отлично интегрируется в AVR Studio позволяя вести отладку прямо там, что адски удобно. В общем, я выбрал его.

Также есть CodeVision AVR C — очень популярный компилятор. Стал популярен в связи со своей простотой. Рабочую программу в нем получить можно уже через несколько минут — мастер стартового кода этом сильно способствует, штампуя стандартыне инициализации всяких уартов. Честно говоря, я как то с подозрением к нему отношусь — как то раз приходилось дизасмить прогу написаную этим компилером, каша какая то а не код получалась. Жуткое количество ненужных телодвижений и операций, что выливалось в неслабый обьем кода и медленное быстродействие. Впрочем, возможно тут была ошибка в ДНК писавшего исходную прошивку. Плюс он хочет денег. Не так много как IAR, но ощутимо. А в деморежиме дает писать не более чем 2кб кода.
Кряк конечно есть, но если уж воровать, так миллион, в смысле IAR:)

Еще есть Image Craft AVR C и MicroC от микроэлектроники. Ни тем ни другим пользоваться не приходилось, но вот SWG очень уж нахваливает MicroPascal , мол жутко удобная среда программирования и библиотеки. Думаю MicroC не хуже будет, но тоже платный.

Как я уже сказал, я выбра WinAVR по трем причинам: халявный, интегрируется в AVR Studio и под него написана просто прорва готового кода на все случаи жизни.

Так что качай себе инсталяху WinAVR с и AVR Studio. Далее вначале ставится студия, потом, сверху, накатывается WinAVR и цепляется к студии в виде плагина. Настоятельно рекомендую ставить WinAVR по короткому пути, что то вроде C:\WinAVR тем самым ты избежишь кучи проблем с путями.

Cоздание проекта
Итак, студия поставлена, Си прикручен, пора бы и попробовать что нибудь запрограммировать. Начнем с простого, самого простого. Запускай студию, выбирай там новый проект, в качестве компилятора AVR GCC и вписывай название проекта.

Открывается рабочее поле с пустым *.c файлом.

Теперь не помешает настроить отображение путей в закладках студии. Для этого слазь по адресу:
Меню Tools — Options — General — FileTabs и выбираем в выпадающем списке «Filename Only». Иначе работать будет невозможно — на вкладке будет полный путь файла и на экране будет не более двух трех вкладок.

Настройка проекта
Вообще, классическим считается создание make файла в котором бы были описаны все зависимости. И это, наверное, правильно. Но мне, выросшему на полностью интегрированных IDE вроде uVision или AVR Studio этот подход является глубоко чуждым. Поэтому буду делать по своему, все средствами студии.

Тыкай в кнопку с шестеренкой.


Это настройки твоего проекта, а точнее настройки автоматической генерации make файла. На первой странице надо всего лишь вписать частоту на которой будет работать твой МК. Это зависит от фьюз битов, так что считаем что частота у нас 8000000Гц.
Также обрати внимание на строку оптимизации. Сейчас там стоит -Os это оптимизация по размеру. Пока оставь как есть, потом можешь попробовать поиграться с этим параметром. -O0 это отстутсвие оптимизации вообще.

Следующим шагом будет настройка путей. Первым делом добавь туда директорию твоего проекта — будешь туда подкладывать сторонние библиотеки. В списке появится путь «.\»

Make файл сгенерирован, его ты можешь поглядеть в папке default в своем проекте, просто пробегись глазами, посмотри что там есть.


На этом пока все. Жми везде ОК и переходи в исходник.

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

Работать будет так:
При приходе по COM порту единички (код 0х31) будем зажигать диодик, а при приходе нуля (код 0х30) гасить. Причем сделано будет все на прерываниях, а фоновой задачей будет мигание другого диода. Простенько и со смыслом.

Собираем схему
Нам надо соединить модуль USB-USART конвертера с выводами USART микроконтроллера. Для этого берем перемычку из двух проводков и накидывам на штырьки крест накрест. То есть Rx контроллера соединяем с Tx конвертера, а Tx конвертера с Rx контроллера.

Получится, в итоге вот такая схема:


Подключение остальных выводов, питания, сброса не рассматриваю, оно стандартное

Пишем код

Сразу оговорюсь, что я не буду углубляться конкретно в описание самого языка Си. Для этого существует просто колоссальное количество материала, начиная от классики «Язык программирования Си» от K&R и заканчивая разными методичками.

Одна такая метода нашлась у меня в загашнике, я когда то именно по ней изучал этот язык. Там все кратко, понятно и по делу. Я ее постепенно верстаю и перестаскиваю на свой сайт.

Там правда еще не все главы перенесены, но, думаю, это ненадолго.

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

Добавляем библиотеки.
Первым делом мы добавляем нужные библиотеки и заголовки с определениями. Ведь Си это универсальный язык и ему надо обьяснить что мы работаем именно с AVR, так что вписывай в исходник строку:

1 #include

#include

Этот файл находится в папке WinAVR и в нем содержится описание всех регистров и портов контроллера. Причем там все хитро, с привязкой к конкретному контроллеру, который передается компилятором через make файл в параметре MCU и на основании этой переменной в твой проект подключается заголовочный файл с описанием адресов всех портов и регистров именно на этот контроллер. Во как! Без него тоже можно, но тогда ты не сможешь использовать символические имена регистров вроде SREG или UDR и придется помнить адрес каждого вроде «0xC1», а это голову сломать.

Сама же команда #include <имя файла> позволяет добавить в твой проект содержимое любого текстового файла, например, файл с описанием функций или кусок другого кода. А чтобы директива могла этот файл найти мы и указывали пути к нашему проекту (директория WinAVR там уже по дефолту прописана).

Главная функция.
Программа на языке Си вся состоит из функций. Они могут быть вложенными и вызываться друг из друга в любом порядке и разными способами. Каждая функция имеет три обязательных параметра:

  • Возвращаемое значение, например, sin(x) возвращает значение синуса икс. Как в математике, короче.
  • Передаваемые параметры, тот самый икс.
  • Тело функции.

Все значения передаваемые и возвращаемые обязаны быть какого либо типа, в зависимости от данных.

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

1 2 3 4 5 int main(void ) { return 0 ; }

int main(void) { return 0; }

Все, первая простейшая программа написана, не беда что она ничего не делает, мы же только начали.

Разберем что же мы сделали.
int это тип данных которая функция main возвращает.

Конечно, в микроконтроллере main ничего вернуть в принципе не может и по идее должна быть void main(void) , но GCC изначально заточен на PC и там программа может вернуть значение операционной системе по завершении. Поэтому GCC на void main(void) ругается Warning’ом.

Это не ошибка, работать будет, но я не люблю варнинги.

void это тип данных которые мы передаем в функцию, в данном случае main также не может ничего принять извне, поэтом void — пустышка. Заглушка, применяется тогда когда не надо ничего передавать или возвращать.

Вот такие вот { } фигурные скобочки это программный блок, в данном случае тело функции main , там будет распологаться код.

return — это возвращаемое значение, которое функция main отдаст при завершении, поскольку у нас int, то есть число то вернуть мы должны число. Хотя это все равно не имеет смысла, т.к. на микроконтроллере из main нам выходить разве что в никуда. Я возвращаю нуль. Ибо нефиг. А компилятор обычно умный и на этот случай код не генерит.
Хотя, если извратиться, то из main на МК выйти можно — например вывалиться в секцию бутлоадера и исполнить ее, но тут уже потребуется низкоуровневое ковыряние прошивки, чтобы подправить адреса перехода. Ниже ты сам увидишь и поймешь как это сделать. Зачем? Вот это уже другой вопрос, в 99.999% случаев это нафиг не надо:)

Сделали, поехали дальше. Добавим переменную, она нам не особо нужна и без нужны вводить переменные не стоит, но мы же учимся. Если переменные добавляются внутри тела функции — то они локальные и существуют только в этой функции. Когда из функции выходишь эти переменные удаляются, а память ОЗУ отдается под более важные нужды. .

1 2 3 4 5 6 int main(void ) { unsigned char i; return 0 ; }

int main(void) { unsigned char i; return 0; }

unsigned значит беззнаковый. Дело в том, что в двоичном представлении у нас старший бит отводится под знак, а значит в один байт (char) влазит число +127/-128, но если знак отбросить то влезет уже от 0 до 255. Обычно знак не нужен. Так что unsigned .
i — это всего лишь имя переменной. Не более того.

Теперь надо проинициализировать порты и UART . Конечно, можно взять и подключить библиотеку и вызвать какой нибудь UartInit(9600); но тогда ты не узнаешь что же произошло на самом деле.

Делаем так:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int main(void ) { unsigned char i; #define XTAL 8000000L #define baudrate 9600L #define bauddivider (XTAL/(16*baudrate)-1) #define HI(x) ((x)>>8) #define LO(x) ((x)& 0xFF) UBRRL = LO(bauddivider) ; UBRRH = HI(bauddivider) ; UCSRA = 0 ; UCSRB = 1 << RXEN| 1 << TXEN| 1 << RXCIE| 0 << TXCIE; UCSRC = 1 << URSEL| 1 << UCSZ0| 1 << UCSZ1; }

int main(void) { unsigned char i; #define XTAL 8000000L #define baudrate 9600L #define bauddivider (XTAL/(16*baudrate)-1) #define HI(x) ((x)>>8) #define LO(x) ((x)& 0xFF) UBRRL = LO(bauddivider); UBRRH = HI(bauddivider); UCSRA = 0; UCSRB = 1<

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

Они облегчат твои рутинные операции по вычислении нужных коэффициентов. В первой строке мы говорим что вместо XTAL можно смело подставлять 8000000, а L — указание типа, мол long — это тактовая частота процессора. То же самое baudrate — частота передачи данных по UART.

bauddivider уже сложней, вместо него будет подставлятся выражение вычисленное по формуле из двух предыдущих.
Ну, а LO и HI из этого результата возьмут младший и старший байты, т.к. в один байт оно явно может не влезть. В HI делается сдвиг икса (входной параметр макроса) восемь раз в вправо, в результате от него останется только старший байт. А в LO мы делаем побитовое И с числом 00FF, в результате останется только младший байт.

Так что все что сделано как #define можно смело выкинуть, а нужные числа подсчитать на калькуляторе и сразу же вписать их в строки UBBRL = …. и UBBRH = …..

Можно. Но! Делать этого КАТЕГОРИЧЕСКИ НЕЛЬЗЯ !

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

Дальше все просто:
Все эти «UBRRL и Со» это регистры конфигурации UART передатчика с помощью которого мы будем общаться с миром. И сейчас мы присвоили им нужные значения, настроив на нужную скорость и нужный режим.

Запись вида 1< Означает следующее: взять 1 и поставить ее на место RXEN в байте. RXEN это 4й бит регистра UCSRB , так что 1< образует двоичное число 00010000, TXEN — это 3й бит, а 1< даст 00001000. Одиночная «|» это побитовое ИЛИ , так что 00010000 | 00001000 = 00011000. Таким же образом выставляются и добавляются в общуюу кучу остальные необходимые биты конфигурации. В итоге, собраное число записывается в UCSRB. Подробней расписано в даташите на МК в разделе USART. Так что не отвлекаемся на технические детали.

Готово, пора бы посмотреть что получилось. Жми на компиляцию и запуск эмуляции (Ctrl+F7).

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

Дело в том, что изначально, на самом деле, она стояла на строке UBRRL = LO(bauddivider); Ведь то что у нас в define это не код, а просто предварительные вычисления, вот симулятор немного и затупил. Но теперь он осознал, первая инструкция выполнена и если ты залезешь в дерево I/O View , в раздел USART и поглядишь там на байт UBBRL то увидишь, что там значение то уже есть! 0х33.

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

Вскрытие
Теперь сбрось симуляцию в ноль. Нажми там Reset (Shift+F5) . Открывай дизассемблированный листинг, сейчас ты увидишь что происходит в контроллере в самом деле. View -> Disassembler . И не ЫЫАААА!!! Ассемблер!!! УЖОС!!! А НАДО. Чтобы потом, когда что то пойдет не так, не тупил в код и не задавал ламерских вопросах на форумах, а сразу же лез в потроха и смотрел где у тебя затык. Ничего там страшного нет.

Вначале будет ботва из серии:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 +00000000: 940C002A JMP 0x0000002A Jump +00000002: 940C0034 JMP 0x00000034 Jump +00000004: 940C0034 JMP 0x00000034 Jump +00000006: 940C0034 JMP 0x00000034 Jump +00000008: 940C0034 JMP 0x00000034 Jump +0000000A: 940C0034 JMP 0x00000034 Jump +0000000C: 940C0034 JMP 0x00000034 Jump +0000000E: 940C0034 JMP 0x00000034 Jump +00000010: 940C0034 JMP 0x00000034 Jump +00000012: 940C0034 JMP 0x00000034 Jump +00000014: 940C0034 JMP 0x00000034 Jump +00000016: 940C0034 JMP 0x00000034 Jump +00000018: 940C0034 JMP 0x00000034 Jump +0000001A: 940C0034 JMP 0x00000034 Jump +0000001C: 940C0034 JMP 0x00000034 Jump +0000001E: 940C0034 JMP 0x00000034 Jump +00000020: 940C0034 JMP 0x00000034 Jump +00000022: 940C0034 JMP 0x00000034 Jump +00000024: 940C0034 JMP 0x00000034 Jump +00000026: 940C0034 JMP 0x00000034 Jump +00000028: 940C0034 JMP 0x00000034 Jump

00000000: 940C002A JMP 0x0000002A Jump +00000002: 940C0034 JMP 0x00000034 Jump +00000004: 940C0034 JMP 0x00000034 Jump +00000006: 940C0034 JMP 0x00000034 Jump +00000008: 940C0034 JMP 0x00000034 Jump +0000000A: 940C0034 JMP 0x00000034 Jump +0000000C: 940C0034 JMP 0x00000034 Jump +0000000E: 940C0034 JMP 0x00000034 Jump +00000010: 940C0034 JMP 0x00000034 Jump +00000012: 940C0034 JMP 0x00000034 Jump +00000014: 940C0034 JMP 0x00000034 Jump +00000016: 940C0034 JMP 0x00000034 Jump +00000018: 940C0034 JMP 0x00000034 Jump +0000001A: 940C0034 JMP 0x00000034 Jump +0000001C: 940C0034 JMP 0x00000034 Jump +0000001E: 940C0034 JMP 0x00000034 Jump +00000020: 940C0034 JMP 0x00000034 Jump +00000022: 940C0034 JMP 0x00000034 Jump +00000024: 940C0034 JMP 0x00000034 Jump +00000026: 940C0034 JMP 0x00000034 Jump +00000028: 940C0034 JMP 0x00000034 Jump

Это таблица векторов прерываний. К ней мы еще вернемся, пока же просто посмотри и запомни, что она есть. Первая колонка — адрес ячейки флеша в которой лежит команда, вторая код команды третья мнемоника команды, та самая ассемблерная инструкция, третья операнды команды. Ну и автоматический коммент.
Так вот, если ты посмотришь, то тут сплошные переходы. А код команды JMP четырех байтный, в нем содержится адрес перехода, записанный задом наперед — младший байт по младшему адресу и код команды перехода 940C

0000002B: BE1F OUT 0x3F,R1 Out to I/O location

Запись этого нуля по адресу 0x3F, Если ты поглядишь в колонку I/O view, то ты увидишь что адрес 0x3F это адрес регистра SREG — флагового регистра контроллера. Т.е. мы обнуляем SREG, чтобы запустить программу на нулевых условиях.

1 2 3 4 +0000002C: E5CF LDI R28,0x5F Load immediate +0000002D: E0D4 LDI R29,0x04 Load immediate +0000002E: BFDE OUT 0x3E,R29 Out to I/O location +0000002F: BFCD OUT 0x3D,R28 Out to I/O location

0000002C: E5CF LDI R28,0x5F Load immediate +0000002D: E0D4 LDI R29,0x04 Load immediate +0000002E: BFDE OUT 0x3E,R29 Out to I/O location +0000002F: BFCD OUT 0x3D,R28 Out to I/O location

Это загрузка указателя стека. Напрямую грузить в I/O регистры нельзя, только через промежуточный регистр. Поэтому сначала LDI в промежуточный, а потом оттуда OUT в I/O. О стеке я тоже еще расскажу подробней. Пока же знай, что это такая динамическая область памяти, висит в конце ОЗУ и хранит в себе адреса и промежуточные переменные. Вот сейчас мы указали на то, откуда у нас будет начинаться стек.

00000032: 940C0041 JMP 0x00000041 Jump

Прыжок в сааааамый конец программы, а там у нас запрет прерываний и зацикливание наглухо само на себя:

1 2 +00000041: 94F8 CLI Global Interrupt Disable +00000042: CFFF RJMP PC-0x0000 Relative jump

00000041: 94F8 CLI Global Interrupt Disable +00000042: CFFF RJMP PC-0x0000 Relative jump

Это на случай непредвиденых обстоятельств, например выхода из функции main. Из такого зацикливания контроллер можно вывести либо аппаратным сбросом, либо, что вероятней, сбросом от сторожевой собаки — watchdog. Ну или, как я говорил выше, подправить это мест в хекс редакторе и ускакать куда нам душе угодно. Также обрати внимание на то, что бывает два типа переходов JMP и RJMP первый это прямой переход по адресу. Он занимает четыре байта и может сделать прямой переход по всей области памяти. Второй тип перехода — RJMP — относительный. Его команда занимает два байта, но переход он делает от текущего положения (адреса) на 1024 шага вперед или назад. И в его параметрах указывается смещение от текущей точки. Используется чаще, т.к. занимает в два раза меньше места во флеше, а длинные прееходы нужны редко.

1 +00000034: 940C0000 JMP 0x00000000 Jump

00000034: 940C0000 JMP 0x00000000 Jump

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

Функция main. Все аналогично, даже можно и не описывать. Посмотри только что в регистры заносится уже вычисленное число. Препроцессор компилятора рулит!!! Так что никаких «магических» чисел!

1 2 3 4 5 6 7 8 9 10 11 12 <

00000036: E383 LDI R24,0x33 Load immediate +00000037: B989 OUT 0x09,R24 Out to I/O location 15: UBRRH = HI(bauddivider); +00000038: BC10 OUT 0x20,R1 Out to I/O location 16: UCSRA = 0; +00000039: B81B OUT 0x0B,R1 Out to I/O location 17: UCSRB = 1<

А вот тут косяк:

1 2 3 +0000003E: E080 LDI R24,0x00 Load immediate +0000003F: E090 LDI R25,0x00 Load immediate +00000040: 9508 RET Subroutine return

0000003E: E080 LDI R24,0x00 Load immediate +0000003F: E090 LDI R25,0x00 Load immediate +00000040: 9508 RET Subroutine return

Спрашивается, для чего это компилятор добавляет такую ботву? А это не что иное, как Return 0, функцию то мы определили как int main(void) вот и просрали еще целых четыре байта не пойми на что:) А если сделать void main(void) то останется только RET, но появится варнинг, что мол у нас функция main ничего не возвращает. В общем, поступай как хошь:)

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

Продолжение следует через пару дней …

Offtop:
Alexei78 сварганил плагинчик для файрфокса облегчающий навигацию по моему сайту и форуму.
Обсуждение и скачивание,

Похожие статьи