AVR-программирование. Программирование микроконтроллеров AVR для начинающих. Программирование микроконтроллеров AVR на C Программирование avr для начинающих

Министерство образования и науки Российской Федерации

Государственное образовательное учреждение

высшего профессионального образования

«САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ

МОРСКОЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ»

Е. В. Коротицкий, Ю. Е. Коротицкая

Основы языка си для микроконтроллеров avr

Учебное пособие

Санкт-Петербург

1. Основы языка Си для микроконтроллеров avr

Универсальный язык С был разработан как инструмент для написания операционной среды UNIX.

Язык С поддерживает процедурно-ориентированную парадигму программирования, т.е. парадигма – взаимосвязанный набор процедур.

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

    1. Препроцессор языка Cи его команды

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

Препроцессор компилятора CodeVisionAVRимеет несколько директив. В Табл. 1 даётся их краткое описание.

Табл. 1 –Директивы препроцессора компилятора CodeVisionAVR

Директива

Назначение

Используется для включения в программу

другого файла

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

Используется для отмены действия директивы #define

Используются для условной компиляции

Используется для изменения встроенных макросов _LINE_и_FILE_

Позволяет остановить компиляцию и отобразить сообщение об ошибках

Используются для включения в исходную программу ассемблерного кода

Разрешает специальные директивы компилятора

ВСЕ директивы препроцессора начинаются со знака #. После директив препроцессора точка с запятой НЕ СТАВИТСЯ.

      1. Директива #include

Пример:

Директива # include

#include "имя_файла" и #include <имя_файла>

Имя_файла состоит из имени файла.

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

#include "имя_файла.h"

      1. Директивы #define, #undef

Директива # define служит для замены часто использующихся одних лексических единиц языка Си (констант, ключевых слов, операторов или выражений) на другие, так называемыеидентификаторы. Идентификаторы, заменяющие текстовые или числовые константы, называютименованными константами. Идентификаторы, заменяющие фрагменты программ, называютмакроопределениями, причём макроопределения могут иметь аргументы.

Директива # define имеет две синтаксические формы:

#define идентификатор текст

#define идентификатор (список параметров) текст

Перед компиляцией программы препроцессор в соответствии с директивой # define заменит все идентификаторы, встречающиеся в программе, на соответствующий им текст.

Пример:

#define А 15 #define В (А+20) // Эти директивы заменят в тексте программы

каждый идентификатор А на число 15, а каждый идентификатор В на выражение (15+20) вместе с окружающими его скобками.

Пример:

#define X(a,b,c) ((а)*(b)-(с)) // Препроцессор в соответствии с этой директивой заменит фрагмент Y=X(k+m,k-m,n); на фрагмент

Как-то сразу потянуло давать советы по поводу выбора среды программирования для AVR контроллеров. Только не надо кидать в меня тапками. Я совсем чуть-чуть 🙂

Языков программирования для микроконтроллеров много. Сред программирования так же не мало и сравнивать их между собой некорректно. Лучших языков программирования не существует. Значит, придется выбрать наиболее подходящие для Вас язык и среду программирования.

Если Вы, в данный момент, стоите перед выбором, на чем начать работать, то вот Вам несколько рекомендаций.

Прежний опыт программирования. Не стоит пренебрегать прежним опытом в программировании. Даже если это был Бейсик. Даже если это было давно в школе. Программирование как езда на велосипеде – стоит только начать и быстро вспоминаешь все забытое. Начните с Бейсика – освойтесть – позже будет проще выбрать что-то более подходящее для Ваших целей.

Помощь окружения. Ваши друзья пишут на Паскале? Для Вас вопрос решен – пишите на Паскале! Вам всегда помогут советом, подкинут библиотек, дадут на изучение готовые проекты. Вобщем рады будут принять в свое сообщество. Если поступите наоборот — получите обратный результат. Друзья сишники заклюют Вас, решившего изучать Ассемблер. Помощи не ждите.

Хорошая книга по программированию AVR очень здорово поможет. К сожалению их очень мало. Если Вам в руки попалась книга, и вы считаете что в ней очень доступно все расписано – попробуйте. Не советую учиться по электронным книгам, в крайнем случае, распечатайте. Очень неудобно переключаться между средой и текстом файла книги. Гораздо приятнее читая книгу тут же пробовать, не отвлекаясь на переключения, кроме того, на полях можно делать пометки, записывать возникшие идеи.

Среда программирования попроще. Если есть на выбор несколько сред программирования Вашего языка – не сомневайтесь, выбирайте ту, что проще. Пусть она менее функциональна. Пусть она компилирует страшно раздутый код. Главное чтобы было просто начать работать. После того как Вы освоитесь в простой среде вы с легкостью перейдете на более продвинутую и «правильную» среду. И не слушайте тех, кто говорит, что вы потеряете больше времени – они не правы. Ученикам младших классов не задают читать «Войну и мир» им дают книги попроще – с картинками.

Библиотеки. Наличие библиотек спорно для изучения языка. Конечно, позже они очень облегчат жизнь, но поначалу «Черные ящики»-библиотеки непонятны и не очень способствуют пониманию языка. С другой стороны облегчают чтение программы и позволяют новичку, не особо напрягаясь, строить сложные программы. Так что, их наличием особо не заморачивайтесь. По крайней мере, по началу.

Эффективный код. Выбор среды программирования для изучения программирования только по тому, насколько эффективный код та компилит – плохая идея. Вам главное комфортно начать изучение – что там получается «на выходе» дело десятое. Конечно, позже можно над этим и поработать.

Визарды. Любое устройство на борту кристалла нуждается в настройке при помощи портов. Процедура довольно муторная и даташиты обязательны. Кроме того, есть нюансы, в которые новичку не просто вкурить. Поэтому в среде очень желательно наличие визардов. Вызарды это автоматические настройщики SPI, I2C, USART и т.д. Чем больше устройств поддерживается, тем лучше. Выставляешь необходимые параметры периферии, а визард сам генерирует код, который обеспечит заданные параметры. Очень упрощает жизнь.


Общие рекомендации такие – программирование на начальном этапе должно быть максимально простым (пусть даже примитивным). Среда программирования должна быть легка в освоении (так как Вам надо, для начала, освоить программирование а не тратить время на ковыряние в настройках). Желательно русифицирована. Также не помешает русский мануал и примеры программ. Желательна возможность прошивки кристалла из среды. Далее при освоении основ программирования можно переходить и на более сложные оболочки.


Еще одна рекомендация, напоследок – работайте с реальным кристаллом. Не бойтесь его спалить. Нарабатывайте практический опыт. Работа с эмуляторами (например Proteus) хоть и освободит от возни с паяльником, но никогда не сможет дать то удовлетворение которое Вы получите от заработавшей программы, первых помигиваний светодиодом! Понимание того, что вы сделали своими руками реальную рабочую схему вселяет уверенность и стимул двигаться дальше!

(Visited 7 377 times, 1 visits today)

Киселев Роман, Май 2007 Статья обновлена 26 Мая 2014

Итак, что вообще такое микроконтроллер (далее МК)? Это, условно говоря, маленький компьютер, размещенный в одной интегральной микросхеме. У него есть процессор (арифметическо-логическое устройство, или АЛУ), flash-память, EEPROM-память, множество регистров, порты ввода-вывода, а также дополнительные «навороты», такие как таймеры, счетчики, компараторы, USARTы и т. п. Микроконтроллер после подачи питания загружается и начинает выполнять программу, записанную в его flash-памяти. При этом он может через порты ввода/вывода управлять самыми разнообразными внешними устройствами.

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

AVR – это 8-битные микроконтроллеры, т. е. их АЛУ может за один такт выполнять простейшие операции только с 8-ми битными числами. Теперь пора поговорить о том, какой МК мы будем использовать. Я работаю с МК ATMega16. Он очень распространенный и приобрести его можно практически в любом магазине радиодеталей где-то за 100 руб. Если вы его не найдете – тогда можно купить любой другой МК серии MEGA, но в этом случае придется искать к нему документацию, т. к. одни и те же «ножки» разных МК могут выполнять разные функции, и, подключив, казалось бы, правильно все выводы, вы, может быть, получите рабочее устройство, а, может быть, лишь облако вонючего дыма. При покупке ATMega16 проверьте, чтобы он был в большом 40-ножечном DIP-корпусе, а также купите к нему панельку, в которую его можно будет вставить. Для работы с ним потребуются также дополнительные устройства: светодиоды, кнопки, разъемы и т. п..

ATMega16 обладает очень большим количеством самых разнообразных функций. Вот некоторые его характеристики:

  • Максимальная тактовая частота – 16 МГц (8 МГц для ATMega16L)
  • Большинство команд выполняются за один такт
  • 32 8-битных рабочих регистра
  • 4 полноценных 8-битных порта ввода/вывода
  • два 8-битных таймера/счетчика и один 16-битный
  • 10-разрядный аналогово-цифровой преобразователь (АЦП)
  • внутренний тактовый генератор на 1 МГц
  • аналоговый компаратор
  • интерфейсы SPI, I2C, TWI, RS-232, JTAG
  • внутрисхемное программирование и самопрограммирование
  • модуль широтно-импульсной модуляции (ШИМ)

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

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

Что для этого понадобится?

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

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

Поясним, какие ножки нас сейчас интересуют.

  • VCC – сюда подается питание (4,5 – 5,5 В) от стабилизированного источника
  • GND – земля
  • RESET – сброс (при низком уровне напряжения)
  • XTAL1, XTAL2 – сюда подключается кварцевый резонатор
  • PA, PB, PC, PD – порты ввода/вывода (A, B, C и D соответственно).

В качестве источника питания можно использовать все, что выдает 7-11 В постоянного тока. Для стабильной работы МК нужно стабилизированное питание. В качестве стабилизатора можно использовать микросхемы серии 7805. Это линейные интегральные стабилизаторы, на вход которых подают 7-11 В постоянного нестабилизированного тока, а на выходе получают 5 В стабилизированного. Перед 7805 и после него нужно поставить фильтрующие конденсаторы (электролитические для фильтрации помех низких частот и керамические для высоких). Если не удается найти стабилизатор, то можно в качестве источника питания использовать батарейку на 4,5 В. От нее МК нужно питать напрямую.

Ниже приведу схему подключения МК:

Давайте теперь разберемся, что здесь для чего.

BQ1 – это кварцевый резонатор, задающий рабочую частоту МК. Можно поставить любой до 16 МГц, но, поскольку мы планируем работать в будущем и с COM-портом, то рекомендую использовать резонаторы на следующие частоты: 14,7456 МГц, 11,0592 МГц, 7,3725 МГц, 3,6864 МГц или 1,8432 МГц (позже станет ясно, почему). Я использовал 11,0592 МГц. Понятное дело, что чем больше частота, тем выше и скорость работы устройства.

R1 – подтягивающий резистор, который поддерживает напряжение 5 В на входе RESET. Низкий уровень напряжения на этом входе означает сброс. После сброса МК загружается (10 – 15 мс) и начинает выполнять программу заново. Поскольку это высокоомный вход, то нельзя оставлять его «болтающимся в воздухе» - небольшая наводка на нем приведет к непредвиденному сбросу МК. Именно для этого и нужен R1. Для надежности рекомендую также установить конденсатор С6 (не более 20 мкФ).

SB1 – кнопка сброса.

Кварцевый резонатор и фильтрующий конденсатор C3 должны располагаться как можно ближе к МК (не далее 5-7 см), т. к. иначе могут возникать наводки в проводах, приводящие к сбоям в работе МК.

Синим прямоугольником на схеме обведен собственно программатор. Его удобно выполнить в виде провода, один конец которого втыкается в LPT порт, а другой – в некий разъем рядом с МК. Провод не должен быть чрезмерно длинным. Если возникнут проблемы с этим кабелем (обычно не возникают, но всякое бывает) то придется спаять адаптер Altera ByteBlaster. О том, как это сделать, написано в описании к программатору AVReal.

Теперь, когда разобрались с железом, пора перейти к программному обеспечению.

Для программирования AVR есть несколько сред разработки. Во-первых, это AVR Studio – официальная система программирования от Atmel. Она позволяет писать на ассемблере и отлаживать программы, написанные на ассемблере, С и С++. IAR – это коммерческая система программирования на C, С++ и ассемблере. WinAVR – компилятор с открытыми исходниками. AtmanAVR – система программирования для AVR с интерфейсом, почти «один в один» таким же, как у Visual C++ 6. AtmanAVR также позволяет отлаживать программы и содержит множество вспомогательных функций, облегчающих написание кода. Эта система программирования коммерческая, но, согласно лицензии, ее можно в течение месяца использовать «нахаляву».

Я предлагаю начать работу с IAR как с наиболее «прозрачной» средой разработки. В IAR проект целиком создается «ручками», соответственно, сделав несколько проектов, вы уже будете четко знать, что означает каждая строчка кода и что будет, если ее изменить. При работе же с AtmanAVR придется либо пользоваться предварительно созданным шаблоном, который очень громоздкий и трудный для понимания для человека, не имеющего опыта, либо иметь множество проблем с заголовочными файлами при сборке проекта «с нуля». Разобравшись с IAR, мы впоследствии рассмотрим другие компиляторы.

Итак, для начала раздобудьте IAR. Он очень распространен и его нахождение не должно быть проблемой. Скачав где-либо IAR 3.20, устанавливаем компилятор / рабочую среду, и запускаем его. После этого можно начинать работу.

Запустив IAR, выбираем file / new / workspace , выбираем путь к нашему проекту и создаем для него папку и даем имя, например, «Prog1». Теперь создаем проект: Project / Create new project… Назовем его также – «Prog1». Щелкаем правой кнопкой мыши на заголовке проекта в дереве проектов и выбираем «Options»

Здесь будем настраивать компилятор под конкретный МК. Во-первых, нужно выбрать на вкладке Target тип процессора ATMega16, на вкладке Library Configuration установить галочку Enable bit definitions in I/O-include files (чтобы можно было использовать в коде программы имена битов различных регистров МК), там же выбрать тип библиотеки С/ЕС++. В категории ICCAVR нужно на вкладке Language установить галочку Enable multibyte support, а на вкладке Optimization выключить оптимизацию (иначе она испортит нашу первую программу).

Далее выбираем категорию XLINK. Здесь нужно определить формат откомпилированного файла. Поскольку сейчас мы задаем опции для режима отладки (Debug), о чем написано в заголовке, то на выходе нужно получить отладочный файл. Позже мы его откроем в AVR Studio. Для этого нужно выбрать расширение.cof, а тип файла – ubrof 7.

Теперь нажимаем ОК, после чего меняем Debug на Release.

Снова заходим в Options, где все параметры, кроме XLINK, выставляем те же. В XLINK меняем расширение на.hex, а формат файла на intel-standart.

Вот и все. Теперь можно приступать к написанию первой программы. Создаем новый Source/text и набираем в нем следующий код:

#include "iom16.h" short unsigned int i; void main (void ) { DDRB = 255; PORTB = 0; while (1) { if (PORTB == 255) PORTB = 0; else PORTB++; for (i=0; i

Файл «iom16.h» находится в папке (C:\Program Files)\IAR Systems\Embedded Workbench 3.2\avr\inc . Если вы используете другой МК, например, ATMega64, то выбирайте файл «iom64.h». В этих заголовочных файлах хранится информация о МК: имена регистров, битов в регистрах, определены имена прерываний. Каждая отдельная «ножка» порта A, B, C или D может работать либо как вход, либо как выход. Это определяется регистрами Data Direction Register (DDR). 1 делает «ножку» выходом, 0 – входом. Таким образом, выставив, например, DDRA = 13, мы делаем выходами «ножки» PB0, PB2, PB3, остальные – входы, т.к. 13 в двоичном коде будет 00001101.

PORTB – это регистр, в котором определяется состояние «ножек» порта. Записав туда 0, мы выставляем на всех выходах напряжение 0 В. Далее идет бесконечный цикл. При программировании МК всегда делают бесконечный цикл, в котором МК выполняет какое-либо действие, пока его не сбросят или пока не произойдет прерывание. В этом цикле пишут как бы «фоновый код», который МК выполняет в самую последнюю очередь. Это может быть, например, вывод информации на дисплей. В нашем же случае увеличивается содержимое регистра PORTB до тех пор, пока он не заполнится. После этого все начинается сначала. Наконец, цикл for на десять тысяч тактов. Он нужен для формирования видимой задержки в переключении состояния порта В.



Теперь сохраняем этот файл в папке с проектом как Prog1.c, копируем в папку с проектом файл iom16.h, выбираем Project/Add Files и добавляем «iom16.h» и «Prog1.c». Выбираем Release, нажимаем F7, программа компилируется и должно появиться сообщение:


Total number of errors: 0
Total number of warnings: 0

Приведу фотографию своего программатора:

Скачиваем программатор AVReal. Копируем его (AVReal32.exe) в папку Release/exe, где должен лежать файл Prog1.hex. Подаем питание на МК, подключаем кабель-программатор. Открываем Far Manager (в нем наиболее удобно прошивать МК), заходим в эту папку, нажимаем Ctrl+O. Поскольку у нас совершенно новый МК, то набиваем

avreal32.exe +MEGA16 -o11.0592MHZ -p1 -fblev=0,jtagen=1,cksel=F,sut=1 –w

Не забудьте правильно указать частоту, если используете не 11059200 Гц! При этом в МК прошиваются т.н. fuses – регистры, управляющие его работой (использование внутреннего генератора, Jtag и т.п.). После этого он готов к приему первой программы. Программатору в качестве параметров передают используемый LPT-порт, частоту, имя файла и другие (все они перечислены в описании к AVReal). Набираем:

Avreal32.exe +Mega16 -o11.0592MHz -p1 -e -w -az -% Prog1.hex

В случае правильного подключения программатор сообщит об успешном программировании. Нет гарантии, что это получится с первого раза (при первом вызове программы). У меня самого бывает программируется со второго раза. Возможно, LPT-порт глючный или возникают наводки в кабеле. При возникновении проблем тщательно проверьте свой кабель. По своему опыту знаю, что 60% неисправностей связаны с отсутствием контакта в нужном месте, 20% - с наличием в ненужном и еще 15% - с ошибочной пайкой не того не к тому. Если ничего не получится, читайте описание к программатору, попробуйте собрать Byte Blaster.

Предположим, у вас все работает. Если теперь подключить к порту В МК восемь светодиодов (делайте это в выключенном состоянии МК, и желательно последовательно со светодиодами включить резисторы в 300-400 Ом) и подать питание, то произойдет маленькое чудо – по ним побежит «волна»!

© Киселев Роман
Май 2007

Здравствуйте, уважаемые Хабражители!

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

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

1. Программатор
На рынке предлагается много вариантов - от самых дешевых ISP (In-System Programming) программаторов за несколько долларов, до мощных программаторов-отладчиков за пару сотен. Не имея большого опыта в этом деле, для начала я решил попробовать один из самых простых и дешевых - USBasp. Купил в свое время на eBay за $12, сейчас можно найти даже за $3-4. На самом деле это китайская версия программатора от Thomas Fischl . Что могу сказать про него? Только одно - он работает. К тому же поддерживает достаточно много AVR контроллеров серий ATmega и ATtiny. Под Linux не требует драйвера.

Для прошивки надо соединить выходы программатора VCC, GND, RESET, SCK, MOSI, MISO с соответствующими выходами микроконтроллера. Для простоты я собрал вспомогательную схему прямо на макетной плате:

Слева на плате - тот самый микроконтроллер, который мы собираемся прошивать.

2. Микроконтроллер
С выбором микроконтроллера я особо не заморачивался и взял ATmega8 от Atmel - 23 пина ввода/вывода, два 8-битных таймера, один 16-битный, частота - до 16 Мгц, маленькое потребление (1-3.6 мА), дешевый ($2). В общем, для начала - более чем достаточно.

Под Linux для компиляции и загрузки прошивки на контроллер отлично работает связка avr-gcc + avrdude. Установка тривиальная. Следуя инструкции , можно за несколько минут установить все необходимое ПО. Единственный ньюанс, на который следует обратить внимание - avrdude (ПО для записи на контроллер) может потребовать права супер-пользователя для доступа к программатору. Выход - запустить через sudo (не очень хорошая идея), либо прописать специальные udev права. Синтаксис может отличаться в разных версиях ОС, но в моем случае (Linux Mint 15) сработало добавление следующего правила в файл /etc/udev/rules.d/41-atmega.rules:

# USBasp programmer SUBSYSTEM=="usb", ATTR{idVendor}=="16c0", ATTR{idProduct}=="05dc", GROUP="plugdev", MODE="0666"

После этого, естественно, необходим перезапуск сервиса
service udev restart
Компилировать и прошивать без проблем можно прямо из командной строки (кто бы сомневался), но если проектов много, то удобнее поставить плагин и делать все прямо из среды Eclipse.

Под Windows придется поставить драйвер. В остальном проблем нет. Ради научного интереса попробовал связку AVR Studio + eXtreme Burner в Windows. Опять-таки, все работает на ура.

Начинаем программировать

Программировать AVR контроллеры можно как на ассемблере (AVR assembler), так и на Си. Тут, думаю, каждый должен сделать свой выбор сам в зависимости от конкретной задачи и своих предпочтений. Лично я в первую очередь начал ковырять ассемблер. При программировании на ассемблере архитектура устройства становится понятнее и появляется ощущение, что копаешься непосредственно во внутренностях контроллера. К тому же полагаю, что в особенно критических по размеру и производительности программах знание ассемблера может очень пригодиться. После ознакомления с AVR ассемблером я переполз на Си.

После знакомства с архитектурой и основными принципами, решил собрать что-то полезное и интересное. Тут мне помогла дочурка, она занимается шахматами и в один прекрасный вечер заявила, что хочет иметь часы-таймер для партий на время. БАЦ! Вот она - идея первого проекта! Можно было конечно заказать их на том же eBay, но захотелось сделать свои собственные часы, с блэк… эээ… с индикаторами и кнопочками. Сказано - сделано!

В качестве дисплея решено было использовать два 7-сегментных диодных индикатора. Для управления достаточно было 5 кнопок - “Игрок 1” , “Игрок 2” , “Сброс” , “Настройка” и “Пауза” . Ну и не забываем про звуковую индикацию окончания игры. Вроде все. На рисунке ниже представлена общая схема подключения микроконтроллера к индикаторам и кнопкам. Она понадобится нам при разборе исходного кода программы:

Разбор полета

Начнем, как и положено, с точки входа программы - функции main . На самом деле ничего примечательного в ней нет - настройка портов, инициализация данных и бесконечный цикл обработки нажатий кнопок. Ну и вызов sei() - разрешение обработки прерываний, о них немного позже.

Int main(void) { init_io(); init_data(); sound_off(); sei(); while(1) { handle_buttons(); } return 0; }
Рассмотрим каждую функцию в отдельности.

Void init_io() { // set output DDRB = 0xFF; DDRD = 0xFF; // set input DDRC = 0b11100000; // pull-up resistors PORTC |= 0b00011111; // timer interrupts TIMSK = (1<

Настройка портов ввода/вывода происходит очень просто - в регистр DDRx (где x - буква, обозначающая порт) записивается число, каждый бит которого означает, будет ли соответствующий пин устройством ввода (соответствует 0) либо вывода (соответствует 1). Таким образом, заслав в DDRB и DDRD число 0xFF, мы сделали B и D портами вывода. Соответственно, команда DDRC = 0b11100000; превращает первые 5 пинов порта C во входные пины, а оставшиеся - в выходные. Команда PORTC |= 0b00011111; включает внутренние подтягивающие резисторы на 5 входах контроллера. Согласно схеме, к этим входам подключены кнопки, которые при нажатии замкнут их на землю. Таким образом контроллер понимает, что кнопка нажата.

Далее следует настройка двух таймеров, Timer0 и Timer1. Первый мы используем для обновления индикаторов, а второй - для обратного отсчета времени, предварительно настроив его на срабатывание каждую секунду. Подробное описание всех констант и метода настройки таймера на определенноый интервал можно найти в документации к ATmega8.

Обработка прерываний

ISR (TIMER0_OVF_vect) { display(); if (_buzzer > 0) { _buzzer--; if (_buzzer == 0) sound_off(); } } ISR(TIMER1_COMPA_vect) { if (ActiveTimer == 1 && Timer1 > 0) { Timer1--; if (Timer1 == 0) process_timeoff(); } if (ActiveTimer == 2 && Timer2 > 0) { Timer2--; if (Timer2 == 0) process_timeoff(); } }

При срабатывании таймера управление передается соответствующему обработчику прерывания. В нашем случае это обработчик TIMER0_OVF_vect, который вызывает процедуру вывода времени на индикаторы, и TIMER1_COMPA_vect, который обрабатывает обратный отсчет.

Вывод на индикаторы

Void display() { display_number((Timer1/60)/10, 0b00001000); _delay_ms(0.25); display_number((Timer1/60)%10, 0b00000100); _delay_ms(0.25); display_number((Timer1%60)/10, 0b00000010); _delay_ms(0.25); display_number((Timer1%60)%10, 0b00000001); _delay_ms(0.25); display_number((Timer2/60)/10, 0b10000000); _delay_ms(0.25); display_number((Timer2/60)%10, 0b01000000); _delay_ms(0.25); display_number((Timer2%60)/10, 0b00100000); _delay_ms(0.25); display_number((Timer2%60)%10, 0b00010000); _delay_ms(0.25); PORTD = 0; } void display_number(int number, int mask) { PORTB = number_mask(number); PORTD = mask; }

Функция display использует метод динамической индикации. Дело в том, что каждый отдельно взятый индикатор имеет 9 контактов (7 для управления сегментами, 1 для точки и 1 для питания). Для управления 4 цифрами понадобилось бы 36 контактов. Слишком расточительно. Поэтому вывод разрядов на индикатор с несколькими цифрами организован по следующему принципу:

Напряжение поочередно подается на каждый из общих контактов, что позволяет высветить на соответствующем индикаторе нужную цифру при помощи одних и тех же 8 управляющих контактов. При достаточно высокой частоте вывода это выглядит для глаза как статическая картинка. Именно поэтому все 8 питающих контактов обоих индикаторов на схеме подключены к 8 выходам порта D, а 16 управляющих сегментами контактов соединены попарно и подключены к 8 выходам порта B. Таким образом, функция display с задержкой в 0.25 мс попеременно выводит нужную цифру на каждый из индикаторов. Под конец отключаются все выходы, подающие напряжение на индикаторы (команда PORTD = 0;). Если этого не сделать, то последняя выводимая цифра будет продолжать гореть до следующего вызова функции display, что приведет к ее более яркому свечению по сравнению с остальными.

Обработка нажатий

Void handle_buttons() { handle_button(KEY_SETUP); handle_button(KEY_RESET); handle_button(KEY_PAUSE); handle_button(KEY_PLAYER1); handle_button(KEY_PLAYER2); } void handle_button(int key) { int bit; switch (key) { case KEY_SETUP: bit = SETUP_BIT; break; case KEY_RESET: bit = RESET_BIT; break; case KEY_PAUSE: bit = PAUSE_BIT; break; case KEY_PLAYER1: bit = PLAYER1_BIT; break; case KEY_PLAYER2: bit = PLAYER2_BIT; break; default: return; } if (bit_is_clear(BUTTON_PIN, bit)) { if (_pressed == 0) { _delay_ms(DEBOUNCE_TIME); if (bit_is_clear(BUTTON_PIN, bit)) { _pressed |= key; // key action switch (key) { case KEY_SETUP: process_setup(); break; case KEY_RESET: process_reset(); break; case KEY_PAUSE: process_pause(); break; case KEY_PLAYER1: process_player1(); break; case KEY_PLAYER2: process_player2(); break; } sound_on(15); } } } else { _pressed &= ~key; } }

Эта функция по очереди опрашивает все 5 кнопок и обрабатывает нажатие, если таковое случилось. Нажатие регистрируется проверкой bit_is_clear(BUTTON_PIN, bit) , т.е. кнопка нажата в том случае, если соответствующий ей вход соединен с землей, что и произойдет, согласно схеме, при нажатии кнопки. Задержка длительностью DEBOUNCE_TIME и повторная проверка нужна во избежание множественных лишних срабатываний из-за дребезга контактов. Сохранение статуса нажатия в соответствующих битах переменной _pressed используется для исключения повторного срабатывания при длительном нажатии на кнопку.
Функции обработки нажатий достаточно тривиальны и полагаю, что в дополнительных комментариях не нуждаются.

Полный текст программы

#define F_CPU 4000000UL #include #include #include #define DEBOUNCE_TIME 20 #define BUTTON_PIN PINC #define SETUP_BIT PC0 #define RESET_BIT PC1 #define PAUSE_BIT PC2 #define PLAYER1_BIT PC3 #define PLAYER2_BIT PC4 #define KEY_SETUP 0b00000001 #define KEY_RESET 0b00000010 #define KEY_PAUSE 0b00000100 #define KEY_PLAYER1 0b00001000 #define KEY_PLAYER2 0b00010000 volatile int ActiveTimer = 0; volatile int Timer1 = 0; volatile int Timer2 = 0; volatile int _buzzer = 0; volatile int _pressed = 0; // function declarations void init_io(); void init_data(); int number_mask(int num); void handle_buttons(); void handle_button(int key); void process_setup(); void process_reset(); void process_pause(); void process_timeoff(); void process_player1(); void process_player2(); void display(); void display_number(int mask, int number); void sound_on(int interval); void sound_off(); // interrupts ISR (TIMER0_OVF_vect) { display(); if (_buzzer > 0) { _buzzer--; if (_buzzer == 0) sound_off(); } } ISR(TIMER1_COMPA_vect) { if (ActiveTimer == 1 && Timer1 > 0) { Timer1--; if (Timer1 == 0) process_timeoff(); } if (ActiveTimer == 2 && Timer2 > 0) { Timer2--; if (Timer2 == 0) process_timeoff(); } } int main(void) { init_io(); init_data(); sound_off(); sei(); while(1) { handle_buttons(); } return 0; } void init_io() { // set output DDRB = 0xFF; DDRD = 0xFF; // set input DDRC = 0b11100000; // pull-up resistors PORTC |= 0b00011111; // timer interrupts TIMSK = (1< 5940 || Timer2 > 5940) { Timer1 = 0; Timer2 = 0; } } void process_reset() { init_data(); } void process_timeoff() { init_data(); sound_on(30); } void process_pause() { ActiveTimer = 0; } void process_player1() { ActiveTimer = 2; } void process_player2() { ActiveTimer = 1; } void handle_button(int key) { int bit; switch (key) { case KEY_SETUP: bit = SETUP_BIT; break; case KEY_RESET: bit = RESET_BIT; break; case KEY_PAUSE: bit = PAUSE_BIT; break; case KEY_PLAYER1: bit = PLAYER1_BIT; break; case KEY_PLAYER2: bit = PLAYER2_BIT; break; default: return; } if (bit_is_clear(BUTTON_PIN, bit)) { if (_pressed == 0) { _delay_ms(DEBOUNCE_TIME); if (bit_is_clear(BUTTON_PIN, bit)) { _pressed |= key; // key action switch (key) { case KEY_SETUP: process_setup(); break; case KEY_RESET: process_reset(); break; case KEY_PAUSE: process_pause(); break; case KEY_PLAYER1: process_player1(); break; case KEY_PLAYER2: process_player2(); break; } sound_on(15); } } } else { _pressed &= ~key; } } void handle_buttons() { handle_button(KEY_SETUP); handle_button(KEY_RESET); handle_button(KEY_PAUSE); handle_button(KEY_PLAYER1); handle_button(KEY_PLAYER2); } void display() { display_number((Timer1/60)/10, 0b00001000); _delay_ms(0.25); display_number((Timer1/60)%10, 0b00000100); _delay_ms(0.25); display_number((Timer1%60)/10, 0b00000010); _delay_ms(0.25); display_number((Timer1%60)%10, 0b00000001); _delay_ms(0.25); display_number((Timer2/60)/10, 0b10000000); _delay_ms(0.25); display_number((Timer2/60)%10, 0b01000000); _delay_ms(0.25); display_number((Timer2%60)/10, 0b00100000); _delay_ms(0.25); display_number((Timer2%60)%10, 0b00010000); _delay_ms(0.25); PORTD = 0; } void display_number(int number, int mask) { PORTB = number_mask(number); PORTD = mask; } void sound_on(int interval) { _buzzer = interval; // put buzzer pin high PORTC |= 0b00100000; } void sound_off() { // put buzzer pin low PORTC &= ~0b00100000; }

Прототип был собран на макетной плате.

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

Общая информация

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

Немного о AVR

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

  1. Бытовой. Стиральные машины, холодильники, микроволновые печи и прочее.
  2. Мобильной. Роботы, средства связи и так далее.
  3. Вычислительной. Системы управления периферийными устройствами, материнские платы.
  4. Развлекательной. Украшения и детские игрушки.
  5. Транспорт. Системы безопасности и управления двигателем автомобиля.
  6. Промышленное оборудование. Системы управления станками.

Это, конечно же, не все сферы. Они применяются там, где выгодно использовать не набор управляющих микросхем, а один микроконтроллер. Это возможно благодаря низкому энергопотреблению и Для написания программ используются языки С и Assembler, немного изменённые под семейство микроконтроллеров. Такие изменение необходимы из-за слабых вычислительных возможностей, которые исчисляются, как правило, в десятках килобайт. AVR-программирование без изучения этих языков не представляется возможным.

Как получить свой первый микроконтроллер?

AVR-программирование требует:

  1. Наличия необходимой среды разработки.
  2. Собственно самих микроконтроллеров.

Второй пункт рассмотрим подробнее. Существует три возможности обзавестись требуемым устройством:

  1. Купить непосредственно сам микроконтроллер.
  2. Обзавестись устройством в составе конструктора (например - Arduino).
  3. Собрать микроконтроллер самостоятельно.

В первом пункте ничего сложного нет, поэтому сразу перейдём ко второму и третьему.

Обзавестись устройством в составе конструктора

В качестве примера будет выбран известный 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!