Микроконтроллеры Atmega8. Программирование Atmega8 для начинающих. Вы еще не программируете микроконтроллеры? Тогда мы идем к вам! Avr atmega 16 программирование c
В этом учебном курсе по avr я постарался описать все самое основное для начинающих программировать микроконтроллеры avr . Все примеры построены на микроконтроллере atmega8 . Это значит, что для повторения всех уроков вам понадобится всего один МК. В качестве эмулятора электронных схем используется Proteus - на мой взгляд, - лучший вариант для начинающих. Программы во всех примерах написаны на компиляторе C для avr CodeVision AVR. Почему не на каком-нибудь ассемблере? Потому что начинающий и так загружен информацией, а программа, которая умножает два числа, на ассемблере занимает около ста строк, да и в сложных жирных проектах используют С. Компилятор CodeVision AVR заточен под микроконтроллеры atmel, имеет удобный генератор кода, неплохой интерфейс и прямо с него можно прошить микроконтроллер.
В этом учебном курсе будет рассказано и показано на простых примерах как:
- Начать программировать микроконтроллеры, с чего начать, что для этого нужно.
- Какие программы использовать для написания прошивки для avr, для симуляции и отладки кода на ПК,
- Какие периферийные устройства находятся внутри МК, как ими управлять с помощью вашей программы
- Как записать готовую прошивку в микроконтроллер и как ее отладить
- Как сделать печатную плату для вашего устройства
- Proteus - программа-эмулятор (в ней можно разработать схему, не прибегая к реальной пайке и потом на этой схеме протестировать нашу программу). Мы все проекты сначала будем запускать в протеусе, а потом уже можно и паять реальное устройство.
- CodeVisionAVR - компилятор языка программирования С для AVR. В нем мы будем разрабатывать программы для микроконтроллера, и прямо с него же можно будет прошить реальный МК.
Написание программы для микроконтроллера
Программу мы будем писать на языке С в компиляторе CodeVisionAVR. После запуска CV, он спрашивает нас, что мы хотим создать: Source или Project- Выход
- DDRx.y = 0 - вывод работает как ВХОД
- DDRx.y = 1 вывод работает на ВЫХОД
#include
Здесь вам может показаться все страшным и незнакомым, но на самом деле все не так. Код можно упростить, выкинув инициализацию неиспользуемых нами периферийных устройств МК. После упрощения он выглядит так:
#include
Всё хорошо. Но для того, чтобы светодиод замигал, нам нужно менять логический уровень на ножке PC0. Для этого в главный цикл нужно добавить несколько строк:
#include
Все, теперь код готов. Кликаем на пиктограму Build all Project files, чтобы скомпилировать (перевести в инструкции процессора МК) нашу программу. В папке Exe, которая находится в нашем проекте, должен появиться файл с расширением hex, это и есть наш файл прошивки для МК. Для того, чтобы нашу прошивку скормить виртуальному микроконтроллеру в Proteus, нужно два раза кликнуть на изображении микроконтроллера в протеусе. Появится вот такое окошко
Я не раз и не два говорил, что изучение МК надо начинать с ассемблера. Этому был посвящен целый курс на сайте (правда он не очень последовательный, но постепенно я его причесываю до адекватного вида) . Да, это сложно, результат будет не в первый день, но зато ты научишься понимать что происходит у тебя в контроллере. Будешь знать как это работает, а не по обезьяньий копировать чужие исходники и пытаться понять почему оно вдруг перестало работать. Кроме того, Си намного проще натворить быдлокода, который вылезет вилами в самый неподходящий момент.
К сожалению все хотят результат немедленно. Поэтому я решил пойти с другой стороны — сделать обучалку по Си, но с показом его нижнего белья. Хороший программист-эмбеддер всегда крепко держит свою железку за шкварник, не давая ей ни шагу ступить без разрешения. Так что будет вначале Си код, потом то что родил компилятор и как все это работает на самом деле:)
С другой стороны у Си сильная сторона это переносимость кода. Если, конечно, писать все правильно. Разделяя алгоритмы работы и их железные реализации в разные части проекта. Тогда для переноса алгоритма в другой МК достаточно будет переписать только интерфейсный слой, где прописано все обращение к железу, а весь рабочий код оставить как есть. И, конечно же, читаемость. Сишный исходник проще понять с первого взгляда (хотя.. мне, например, уже пофигу на что фтыкать — хоть си, хоть асм:)), но, опять же, если правильно все написать. Этим моментам я тоже буду уделять внимание.
В качестве подопытной железки на которой будет ставиться львинная доля всех примеров будет моя отладочная плата .
Первая программа на Си для 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
уже сложней, вместо него будет подставлятся выражение вычисленное по формуле из двух предыдущих. Так что все что сделано как #define
можно смело выкинуть, а нужные числа подсчитать на калькуляторе и сразу же вписать их в строки UBBRL = …. и UBBRH = ….. Можно. Но! Делать этого КАТЕГОРИЧЕСКИ НЕЛЬЗЯ
! Работать будет и так и эдак, но у тебя в программе появятся так называемые магические числа
— значения взятые непонятно откуда и непонятно зачем и если ты через пару лет откроешь такой проект то понять что это за значения будет чертовски трудно. Да и сейчас, захочешь ты изменить скорость, или поменяешь частоту кварца и все придется пересчитывать заново, а так поменял пару циферок в коде и все само. В общем, если не хочешь прослыть быдлокодером, то делай код таким, чтобы он легко читался, был понятен и легко модифицировался. Дальше все просто:
Запись вида 1< Готово, пора бы посмотреть что получилось. Жми на компиляцию и запуск эмуляции (Ctrl+F7). Отладка
Дело в том, что изначально, на самом деле, она стояла на строке UBRRL = LO(bauddivider); Ведь то что у нас в define это не код, а просто предварительные вычисления, вот симулятор немного и затупил. Но теперь он осознал, первая инструкция выполнена и если ты залезешь в дерево I/O View
, в раздел USART и поглядишь там на байт UBBRL то увидишь, что там значение то уже есть! 0х33. Сделай еще один шаг. Погляди как изменится содержимое другого регистра. Так прошагай их все, обрати внимание на то, что все указаные биты выставляются как я тебе и говорил, причем выставляются одновременно для всего байта. Дальше Return дело не пойдет — программа кончилась. Вскрытие
Вначале будет ботва из серии: 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 Это таблица векторов прерываний. К ней мы еще вернемся, пока же просто посмотри и запомни, что она есть. Первая колонка — адрес ячейки флеша в которой лежит команда, вторая код команды третья мнемоника команды, та самая ассемблерная инструкция, третья операнды команды. Ну и автоматический коммент. 0000002B: BE1F OUT 0x3F,R1 Out to I/O location Запись этого нуля по адресу 0x3F, Если ты поглядишь в колонку I/O view, то ты увидишь что адрес 0x3F это адрес регистра SREG — флагового регистра контроллера. Т.е. мы обнуляем SREG, чтобы запустить программу на нулевых условиях. 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 Прыжок в сааааамый конец программы, а там у нас запрет прерываний и зацикливание наглухо само на себя: 00000041: 94F8 CLI Global Interrupt Disable
+00000042: CFFF RJMP PC-0x0000 Relative jump Это на случай непредвиденых обстоятельств, например выхода из функции main. Из такого зацикливания контроллер можно вывести либо аппаратным сбросом, либо, что вероятней, сбросом от сторожевой собаки — watchdog. Ну или, как я говорил выше, подправить это мест в хекс редакторе и ускакать куда нам душе угодно. Также обрати внимание на то, что бывает два типа переходов JMP и RJMP первый это прямой переход по адресу. Он занимает четыре байта и может сделать прямой переход по всей области памяти. Второй тип перехода — RJMP — относительный. Его команда занимает два байта, но переход он делает от текущего положения (адреса) на 1024 шага вперед или назад. И в его параметрах указывается смещение от текущей точки. Используется чаще, т.к. занимает в два раза меньше места во флеше, а длинные прееходы нужны редко. 00000034: 940C0000 JMP 0x00000000 Jump А это прыжок в самое начало кода. Перезагрузка своего рода. Можешь проверить, все вектора прыгают сюда. Из этого вывод — если ты сейчас разрешишь прерывания (они по дефолту запрещены) и у тебя прерывание пройзойдет, а обработчика нет, то будет программный сброс — программу кинет в самое начало. Функция main. Все аналогично, даже можно и не описывать. Посмотри только что в регистры заносится уже вычисленное число. Препроцессор компилятора рулит!!! Так что никаких «магических» чисел! 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< А вот тут косяк: 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:
Урок 0.
Итак, сегодня мы открываем цикл уроков программирования микроконтроллеров семейства AVR. Сегодня будут рассмотрены следующие вопросы: Вступление.
Микроконтроллеры везде. В телефонах, стиральных машинах, «умных домах»,станках на заводе а так же ещё в бесчисленном множестве технических устройств. Их повсеместное применение позволяет заменить сложные аналоговые схемы, более сжатыми цифровыми. Так что же такое, микроконтроллер?
Микроконтроллер
(Micro Controller Unit, MCU
) - микросхема, предназначенная для управления электронными устройствами.Можно представить его в виде простейшего компьютера, способного взаимодействовать с внешними устройствами.Например, открывать и закрывать транзисторы, получать данные с датчиков температуры, выводить данные на lcd экраны и т. д. . К тому же, микроконтроллер может производить различную обработку входных данных, как и Ваш персональный компьютер. То есть, микроконтроллеры открывают нам практически безграничные возможности управления какими либо устройствами, благодаря наличию портов I/0(портов ввода(input)/вывода(output)), а так же возможности их программирования. Где используются микроконтроллеры?
Это далеко не полный список сфер применения микроконтроллеров. Часто, очень выгодно заменить набор управляющих микросхем одним микроконтроллером, ввиду упрощения производства, снижения энергопотребления. Начало знакомства с AVR
AVR
— семейство микроконтроллеров фирмы Atmel.Обладают достаточной производительностью для большинства любительских устройств. Так же находят широкое применение в промышленности. Микроконтроллеры являются небольшими, но одновременно очень удобными приспособлениями для тех, кто желает создавать различные удивительные роботизированные или автоматизированные вещи у себя дома. В рамках этой статьи будет рассмотрено программирование AVR для начинающих, различные аспекты и нюансы этого процесса. Микроконтроллеры можно встретить везде. Они есть в холодильниках, стиральных машинах, телефонах, станках на производстве, умных домах и ещё во множестве различных технических устройств. Их повсеместное применение обусловлено возможностью замены более сложных и масштабных аналоговых схем устройств. Программирование МК AVR позволяет обеспечить автономное управление над электронными устройствами. Эти микроконтроллеры можно представить как простейший компьютер, что может взаимодействовать с внешней техникой. Так, им под силу открывать/закрывать транзисторы, получать данные с датчиков и выводить их на экраны. Также микроконтроллеры могут осуществлять различную обработку входной информации подобно персональному компьютеру. Если освоить программирование AVR с нуля и дойти до уровня профессионала, то откроются практически безграничные возможности для управления различными устройствами с помощью портов ввода/вывода, а также изменения их кода. В рамках статьи будет рассмотрено семейство микроконтроллеров, выпускаемых фирмой Atmel. Они имеют довольно неплохую производительность, что позволяет использовать их во многих любительских устройствах. Широко применяются и в промышленности. Можно встретить в такой технике: Это, конечно же, не все сферы. Они применяются там, где выгодно использовать не набор управляющих микросхем, а один микроконтроллер. Это возможно благодаря низкому энергопотреблению и Для написания программ используются языки С и Assembler, немного изменённые под семейство микроконтроллеров. Такие изменение необходимы из-за слабых вычислительных возможностей, которые исчисляются, как правило, в десятках килобайт. AVR-программирование без изучения этих языков не представляется возможным. AVR-программирование требует: Второй пункт рассмотрим подробнее. Существует три возможности обзавестись требуемым устройством: В первом пункте ничего сложного нет, поэтому сразу перейдём ко второму и третьему. В качестве примера будет выбран известный Arduino. Это по совместительству удобная платформа для быстрой и качественной разработки различных электронных устройств. Плата Arduino включает в себя определённый набор компонентов для работы (существуют различные конфигурации). В неё обязательно входит AVR-контроллер. Этот подход позволяет быстро начать разработку устройства, не требует специальных умений и навыков, имеет значительные возможности в плане подключения дополнительных плат, а также в интернете можно найти много информации на интересующие вопросы. Но не обошлось и без минусов. Покупая Arduino, человек лишает себя возможности более глубоко окунуться в AVR-программирование, лучше узнать микроконтроллер, специфику его работы. Также негатива добавляет и относительно узкая линейка моделей, из-за чего часто приходится покупать платы под конкретные задачи. Особенностью также является и то, что программирование на "СИ" здесь отличается довольно сильно от стандартной формы. Несмотря на все свои недостатки, Arduino подходит для изучения новичкам. Но злоупотреблять не стоит. Следует отметить, что микроконтроллеры AVR отличаются достаточной дружелюбностью к новичкам. Собрать их самостоятельно можно с доступных, простых и дешевых комплектующих. Если говорить о плюсах, то такой подход позволяет лучше ознакомиться с устройством, самостоятельно выбирать необходимые комплектующие, подгоняя конечный результат под выдвигаемые требования, использование стандартных языков программирования и дешевизна. Из минусов можно отметить только сложность самостоятельной сборки, когда она осуществляется впервые, и нет нужных знаний и навыков. Итак, допустим, что вопрос с микроконтроллером решился. Далее будет считаться, что он был приобретён или же куплен самостоятельно. Что ещё нужно, чтобы освоить AVR-программирование? Для этой цели нужна среда разработки (в качестве базиса подойдёт и обычный блокнот, но рекомендую остановиться на Notepad++). Хотя существуют и другие программы для программирования AVR, приведённое обеспечение сможет справиться со всеми требованиями. Также необходим программатор. Его можно приобрести в ближайшем магазине, заказать по интернету или собрать самостоятельно. Не помешает и печатная плата. Она не обязательна, но её использование позволяет сэкономить свои нервы и время. Также покупается/создаётся самостоятельно. И последнее - это источник питания. Для AVR необходимо обеспечить поступление напряжения на 5В. Создавать шедевры с нуля не получиться. Здесь необходимы знания, опыт и практика. Но где их взять? Существует несколько путей. Первоначально можно самостоятельно выискивать нужную информацию в мировой сети. Можно записать на курсы программирования (дистанционные или очные) для получения базовых навыков работы. Каждый подход имеет свои преимущества. Так, дистанционные курсы программирования будут более дешевыми, а может и бесплатными. Но если что-то не будет получаться, то при очных занятиях опытный разработчик сможет быстрее найти причину проблемы. Также не лишним будет ознакомиться с литературой, что находится в свободном доступе. Конечно, на одних книгах выехать не получится, но получить базовые знания про устройство, программирование на "СИ", "Ассемблере" и о других рабочих моментах можно. Это чрезвычайно важная тема. Без понимания того, как работают порты ввода/вывода, не представляется возможным внутрисхемное программирование AVR вообще. Ведь взаимодействие микроконтроллера с внешними устройствами осуществляется именно при их посредничестве. На первый взгляд новичка может показаться, что порт - это довольно запутанный механизм. Чтобы избежать такого впечатления, не будем детально рассматривать схему его работы, а только получим общее представление об этом. Рассмотрим программную реализацию. В качестве примера устройства был выбран микроконтроллер AtMega8 - один из самых популярных из всего семейства AVR. Порт ввода/вывода представляет собой три регистра, которые отвечают за его работу. На физическом уровне они реализовываются как ножки. Каждой из них соответствует определённый бит в управляющем реестре. Каждая ножка может работать как для ввода информации, так и для её вывода. Например, на неё можно повесить функцию зажигания светодиода или обработку нажатия кнопки. Кстати, три регистра, о которых говорилось, это: PORTx, PINx и DDRx. Каждый из них является восьмиразрядным (не забываем, что мы рассматриваем AtMega8). То есть один бит занимается определённой ножкой. Наиболее весомым в плане ориентации является управляющий DDRx. Он также является восьмиразрядным. Значения для него могут быть записаны 0 или 1. Как меняется работа контроллера при использовании нулей и единицы? Если в определённом бите выставить 0, то соответствующая ему ножка будет переключена в режим входа. И с неё можно будет считывать данные, что идут с внешних устройств. Если установить 1, то микроконтроллер сможет управлять чем-то (например, дать приказ транзистору пропустить напряжение и зажечь светодиод). Вторым по важности является PORTx. Он занимается управлением состояния ножки. Давайте рассмотрим пример. Допустим, у нас есть порт вывода. Если мы устанавливаем логическую единицу в PORTx, то посылается сигнал от микроконтроллера управляющему устройству начать работу. Например, зажечь светодиод. При установлении нуля он будет гаситься. То есть работать с управляющим регистром DDRx постоянно, нет надобности. И напоследок давайте о PINx. Этот регистр отвечает за отображение состояния ножки контроллера, когда она настроена на состояние ввода. Следует отметить, что PINx может работать исключительно в режиме чтения. Записать в него ничего не получится. Но вот прочитать текущее состояние ножки - это без проблем. AVR не являются единственными микроконтроллерами. Этот рынок поделен между несколькими крупными производителями, а также между многочисленными китайскими имитирующими устройствами и самоделками. Во многом они подобны. К примеру, программирование PIC/AVR сильно не отличается. И если есть понимание чего-то одного, то понять всё остальное будет легко. Но начинать путь рекомендуем всё же с AVR благодаря его грамотной структуре, дружелюбности к разработчику и наличию большого количества вспомогательных материалов, из-за чего процесс разработки можно значительно ускорить. Когда будет вестись программирование микроконтроллеров AVR на "СИ" или на "Ассемблере", то необходимо работать очень осторожно. Дело в том, что выставив определённую комбинацию регистров и изменив внутренние настройки, можно спокойно заблокировать микроконтроллер. Особенно это касается фьюзов. Если нет уверенности в правильности своих действий, то лучше отказаться от их использования. Это же относится и к программаторам. Если покупать заводскую аппаратуру, то она будет прошивать микроконтроллеры без проблем. При сборке своими руками может возникнуть печальная ситуация, при которой программатор заблокирует устройство. Это может произойти как из-за ошибки в программном коде, так и через неполадки в нём самом. Кстати, об ещё одном (на этот раз позитивном) моменте, который ранее вскользь упоминался, но так и не был раскрыт полностью. Сейчас практически все современные микроконтроллеры обладают функцией внутрисхемного программирования. Что это значит? Допустим, что устройство было запаяно на плате. И чтобы сменить его прошивку, сейчас не нужно его выпаивать, ведь такое вмешательство может повредить сам микроконтроллер. Достаточно подключиться к соответствующим выводам и перепрограммировать его при их посредстве. В рамках статьи была рассмотрена AtMega8. Это довольно посредственный за своими характеристиками микроконтроллер, которого, тем не менее, хватает для большинства поделок. Если есть желание создать что-то масштабное, то можно брать уже своеобразных монстров вроде Atmega128. Но они рассчитаны на более опытных разработчиков. Поэтому, если нет достаточного количества опыта, то лучше начинать с небольших и простых устройств. К тому же они и значительно дешевле. Согласитесь, одно дело случайно заблокировать микроконтроллер за сто рублей, а совсем иное - за полтысячи. Лучше набить себе руку и разобраться в различных аспектах функционирования, чтобы в последующем не терять значительные суммы. Первоначально можно начать с AtMega8, а потом уже ориентироваться по своим потребностям. Вот и была рассмотрена тема программирования AVR в самых общих чертах. Конечно, ещё о многом можно рассказывать. Так, к примеру, не было рассмотрено маркирование микроконтроллеров. А оно может о многом сказать. Так, в основном микроконтроллеры работают на напряжении в 5В. Тогда как наличие, к примеру, буквы L может сказать о том, что для работы устройства достаточно только 2,7 В. Как видите, порой знания о маркировке могут сыграть очень важную роль в плане корректной и долговечной работы устройств. Время функционирования микроконтроллеров - это тоже интересная тема. Каждое устройство рассчитано на определённый период. Так, некоторые могут отработать тысячу часов. Другие же имеют гарантийный запас в 10 000! Всем привет. Как и обещал, с сегодняшнего дня начинаем изучать программирования AVR микроконтроллеров (на примере Atmega8). Тем же читателям, которым интересно программирование платы ардуино, не волнуйтесь, статьи по данному направлению будут продолжаться 🙂 .
Можно задать логичный вопрос, почему из ряда других микроконтроллеров (далее — МК) в качестве подопытного выбран именно МК AVR
. На это есть несколько причин: Как говорил ранее, в качестве подопытного будем использовать МК Atmega8
. Почему именно его? Данный микроконтроллер может похвастаться наличием 3 портов ввода/вывода. Кроме этого он довольно дешевый. Под портами, понимают шины данных, которые могут работают в двух противоположных направлениях (то бишь на вывод и на ввод). У Atmega8
3 порта. Порт B состоит из 8 ножек-выводов (нумерация 0,1,2,3,4,5,6,7). Порт С состоит из 7 ножек-выводов (нумерация 0,1,2,3,4,5,6). Порт D состоит из 8 ножек-выводов (нумерация 0,1,2,3,4,5,6,7). Запитывать микроконтроллер можно от 3,3 и 5 В. При напряжении питания 5 В максимальная частота тактирования составляет 16 МГц, а при напряжении питания 3,3 В – максимальная частота тактирования 8 МГц. Пока не будем заморачиваться относительно частот тактирования. Питания подаётся на 7 ножку-вывод, а «земля» подводится к 8 ножке. Скачивается бесплатно. Скачали, установили, запустили 🙂 Первое, с чего следует начать знакомство с Atmel Studio – это создание проекта. Выбираем File -> new -> project . Откроется окно выбора. Выбираем папку «Browse», в которой будем сохранять написанные проекты. Папку для проектов создал заранее. Присваиваем имя проекту, в моём случае lesson_avr_1 Обратите внимание на галочку «create directory for solution». Если отметка стоит, то в той папке, которую мы выбрали для сохранения проектов, будет создана отдельная папка под текущий проект. На этом всё – проект создан. Займемся настройкой созданного нами проекта. Нажимаем Projest -> lesson_avr_1 properties или (alt+F7) Переходим на вкладку Tool. Выбираем – симулятор. Совершенные нами действия сделают возможным отлаживать написанный код. Сохраняем изменения. Можно сохранить изменения в одном (текущем) файле или же во всех файлах проекта сразу. Закрываем настройки.
Ну, а LO
и HI
из этого результата возьмут младший и старший байты, т.к. в один байт оно явно может не влезть. В HI
делается сдвиг икса (входной параметр макроса) восемь раз в вправо, в результате от него останется только старший байт. А в LO
мы делаем побитовое И с числом 00FF, в результате останется только младший байт.
Все эти «UBRRL и Со» это регистры конфигурации UART передатчика с помощью которого мы будем общаться с миром. И сейчас мы присвоили им нужные значения, настроив на нужную скорость и нужный режим.
Пробежали всякие прогресс бары, студия переменилась и возле входа в функцию main появилась желтая стрелочка. Это то где процессор в текущий момент, а симуляция на паузе.
Теперь сбрось симуляцию в ноль. Нажми там 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
Так вот, если ты посмотришь, то тут сплошные переходы. А код команды JMP четырех байтный, в нем содержится адрес перехода, записанный задом наперед — младший байт по младшему адресу и код команды перехода 940C 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
1
2
+00000041: 94F8 CLI Global Interrupt Disable
+00000042: CFFF RJMP PC-0x0000 Relative jump
1
+00000034: 940C0000 JMP 0x00000000 Jump
1
2
3
4
5
6
7
8
9
10
11
12
< 1
2
3
+0000003E: E080 LDI R24,0x00 Load immediate
+0000003F: E090 LDI R25,0x00 Load immediate
+00000040: 9508 RET Subroutine return
Alexei78
сварганил плагинчик для файрфокса облегчающий навигацию по моему сайту и форуму.
Обсуждение и скачивание, Общая информация
Немного о AVR
Как получить свой первый микроконтроллер?
Обзавестись устройством в составе конструктора
Самостоятельная сборка
Как работать?
Где и как учиться?
Порты ввода/вывода
Работа регистров
Работа с аналогами
Техника безопасности
Какую модель выбрать?
Заключение