Мигающий светодиод на микроконтроллере AVR Atmega8. Программирование микроконтроллеров AVR #define и регистры

разработчик 80-го уровня 17 июля 2015 в 11:33

Мигаем светодиодами на AVR без Arduino

  • DIY или Сделай сам *

События в статье происходили год назад, поэтому что-то могло устареть, а что-то я мог упустить. Начну с того, что я не программист, не разбираюсь в цифровой электронике, и всю свою жизнь интересуюсь аналоговой электроникой. Я не знал что такое микроконтроллеры ибо не читал Habr. Признаюсь честно, если бы знал тогда про ардуино то сделал бы на ардуино. Итак начнем.

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

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

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

Для тек кто не знает что это за программа, очень советую, очень удобно наблюдать как все работает без пайки и прошивки контроллера. Микроконтроллер фирмы Atmel Atmega8. Почему именно атмел, предлагаю обсудить в комментариях.

Собственно сам код:

#include //Включаем библиотеку для работы с микроконтроллером ATMega8 #include //Включаем библиотеку для организации задержек #include //Включаем библиотеку для генерации псевдослучайных чисел (rand) void main(void) //Обязательный заголовок (тело) { char i; //Объявляем переменную (i) PORTD=0x00; //Выставляем все выходы порта D на 0, то есть, выключаем весь порт D DDRD=0xFF; //Делаем порт D, как выход, чтобы на выходах порта было напряжение 5В PORTC=0x00; //Выставляем все выходы порта C на 0, то есть, выключаем весь порт C DDRC=0xFF; //Делаем порт C, как выход, чтобы на выходах порта было напряжение 5В PORTB=0x00; //Выставляем все выходы порта B на 0, то есть, выключаем весь порт B DDRB=0xFF; //Делаем порт B, как выход, чтобы на выходах порта было напряжение 5В while (1) //Добавляем бессконечный цикл, код ниже выполняется в цикле { i = rand() % 22 + 1; /*Присваеваем переменной функцию, которая принимает значения от 0 до 32767. (В нашем случае делим по модулю)*/ delay_ms(5); //Исполюзуем задержку 5мс if (i==1){PORTD.0=1;} else PORTD.0=0; //Если переменная (i) равна (1) то 0-вой бит порта (D) равен (1), if (i==2){PORTD.1=1;} else PORTD.1=0; // если иначе, то 0-лю if (i==3){PORTD.2=1;} else PORTD.2=0; //Во всех остальных ниже условиях по аналогии if (i==4){PORTD.3=1;} else PORTD.3=0; if (i==5){PORTD.4=1;} else PORTD.4=0; if (i==6){PORTD.5=1;} else PORTD.5=0; if (i==7){PORTD.6=1;} else PORTD.6=0; if (i==8){PORTD.7=1;} else PORTD.7=0; if (i==9){PORTC.0=1;} else PORTC.0=0; if (i==10){PORTC.1=1;} else PORTC.1=0; if (i==11){PORTC.2=1;} else PORTC.2=0; if (i==12){PORTC.3=1;} else PORTC.3=0; if (i==13){PORTC.4=1;} else PORTC.4=0; if (i==14){PORTC.5=1;} else PORTC.5=0; if (i==15){PORTB.0=1;} else PORTB.0=0; if (i==16){PORTB.1=1;} else PORTB.1=0; if (i==17){PORTB.2=1;} else PORTB.2=0; if (i==18){PORTB.3=1;} else PORTB.3=0; if (i==19){PORTB.4=1;} else PORTB.4=0; if (i==20){PORTB.5=1;} else PORTB.5=0; if (i==21){PORTB.6=1;} else PORTB.6=0; if (i==22){PORTB.7=1;} else PORTB.7=0; }; }

Код писал 2 дня, пока понял что к чему. Опишу кратко. Подключаем необходимые библиотеки, в цикле с помощью функции rand() генерируем числа, условия открывают закрывают порты в зависимости от значения переменной «i». А как бы сделали вы? Можно ли сократить код?

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

После необходимо прошить сам микроконтроллер. Тут такая же ситуация. Существует всеми хвалимая программа AVRDUDE, но мне понравилась KhazamaAVRProgrammer. В качестве программатора выступал обычный USBASP:

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

Печатную плату в формате lay можете скачать

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

Для начала мы создадим новый проект. Создадим мы его приблизительно по той же схеме, как и напрошлом занятии, за исключением только лишь, того, что назовём мы его Test02 .

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

Теперь начнем исправления. То есть мы создадим новый код, а старый у нас будет нетронутым в прошлом проекте. Порт D мы полностью весь установим в 0, изменив 1 на 0 в следующей строке

PORTD = 0b00000000;

В принципе, у нас уже все ножки порта по умолчанию установлены в ноль, но все равно сделаем это для порядка.

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

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

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

Все три операции делаются побитно, то есть логически сравниваются одноимённые биты — нулевой с нулевым, первый с первым и так далее.

Первая — это операция "И ", обозначаемая в языке C/C++ знаком "& ". Любой бит в результате будет установлен в единицу только в том случае, если оба соответствующих ему бита в операндах будут единицами . В любом другом случае мы получаем ноль .

Поэтому результат у нас будет следующий

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

Сделующая логическая операция — это операция "ИЛИ ", обозначаемая вертикальной чертой "| ". В результате данной операции мы получим единицу , если хотя бы один бит в сравниваемых одноименных битах будет установлен в единицу . Таким образом, ноль мы получим только тогда, когда в обеих сравниваемых битах будет ноль .

Поэтому здесь будет уже вот такой результат

Третий вид операции — операция "Исключающее ИЛИ ". Эта операция обозначается знаком "^ ". Здесь шансы у бита стать нулём или единицей уравниваются. Единицей результирующий бит станет тогда, когда сравниваемые биты обязательно будут разными — один будет нулём , другой — единицей . А нулём будет результирующий бит тогда, когда сравниваемые биты будут одинаковыми.

Итак, получим следующий результат

А четвертая операция — операция "НЕ ", обозначаемая знаком тильда — "~ ", проделывается над одним байтом. В результате данной операции все биты меняются обратный. То есть ноль становится единицей , а единица нулём . Данную операцию ещё называют операцией инвертирования .

Вот такой вот получим результат

Теперь вернёмся к коду. Напишем следующую очень непонятную на первый взгляд команду

while (1)

PORTD |=(1<<(PORTD0 ));

Первый непонятный оператор в данной команде — это сдвоенный оператор "|= ". Такого рода операторов существует несколько. В данном случае сначала результат, который получится в правой части, сразу не присваивается переменной. которая находится в правой части, а складывается логически по "ИЛИ " со значением, хранящемся до этого в перменной, которая находится справа. Затем уже результат, полученный после применения данной логической операции, присваивается переменной, находящейся в правой части. Также существуют подобные операции типа "+= и "-= ", которыми мы в дальнейшем будем очень часто пользоваться.

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

Битовые сдвиги бывают двух видов:

"<< " — сдвиг влево,

">> " — сдвиг враво.

Давайте остановимся на них немного поподробнее.

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

Давайте проделаем данные операции над определённым байтом

Ну, я думаю, со сдвигом всё ясно. Давайте проделаем данную операцию над нашим примером в коде

Единичка, стоящая слева — это число, над которым проводится операция, а не число, на которое мы сдвигаем байт побитно, как очень многие путают. Запомните это накрепко! То есть в нашем случае — это единица или в двоичном выражении 0b00000001 . А PORTD0 — это константа, которая определена в макросе в файле io.h . Вот разновидности данных констант

#define PORTD7 7

#define PORTD6 6

#define PORTD5 5

#define PORTD4 4

#define PORTD3 3

#define PORTD2 2

#define PORTD1 1

#define PORTD0 0

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

Дальше мы делаем операцию "ИЛИ" между значением, находящимся в переменной PORTD и данным результатом, равным единице. Так как у нас в порте D в данный момент находится ноль на всех ножках, то мы только установим единицу на нулевой ножке, так как 0b00000000 и 0b00000001 в результате данной операции дадут на 0b00000001 . Но если бы в регистре порта D находилось бы какое-нибудь другое число, то мы также бы установили бы единицу в нулевом бите, а остальные биты бы не тронулись. То есть мы в результате команды получили и лояльную к остальным лапкам операцию, и в то же время эта операция у нас имеет очень наглядный и читабельный вид.

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

Delay_ms (число в милисекундах )

Delay_us (число в микросекундах )

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

#include

#include

Мы воспользуемся первой командой и применим задержку на 500 милисекунд

PORTD |=(1<<(PORTD0 ));

_delay_ms (500);

Теперь можно погасить светодиод. Делается это с помощью следующей команды

Delay_ms (500);

PORTD &=~(1<<(PORTD0 ));

Здесь мы видим, что у нас команда похожа на нашу предыдущую, хотя есть некоторые существенные изменения. Во-первых наш логический оператор превратился в "И ", а также мы применили инверсию результата справа.

Давайте разберём данную команду. Результат справа нам известен — это 0b00000001 , если мы его инвертируем то получим соответственно 0b11111110 . Тепрь его нужно умножить логически на значение в переменной справа. В данный момент там у нас также 0b00000001 . Соответственно получим мы все нули, так как сравниваемые биты в операндах у нас разные. А если бы было какое-то другое чесло в переменной PORTD, то у нас бы сбросился только нулевой бит, остальные бы не тронулись. Поэтому вот такой операцией в будущем мы и будем сбрасывать определенные биты в регистрах и переменных. Также можно сбрасывать сразу несколько битов или устанавливать, но об этом позже, а то и так мы слишком много уже узнали. Осталось нам добавить ещё такую же задержку, чтобы светодиод находился в погасшем состоянии столько же. сколько и в светящемся

PORTD &=~(1<<(PORTD0 ));

_delay_ms (500);

Но это ещё не всё. Чтобы задержка корректно работала, мы обязаны ещё в начале модуля или файла main.c написать макрос, говорящий компилятору о том, с какой частотой работает наш контроллер, иначе он неправлиьно применит задержку, и нам даст предупрежедение. Напишем данный макрос

#define F_CPU 8000000

#include

Соберём проект. У нас ошибок и предупреждений нет

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

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

Запустим его. Щёлкнем двойным щелчком по изображению контроллера и выберем там другую прошивку, соответствующую новому проекту, а также выставим в свойствах соответствующую частоту, иначе светодиод будет мигать в 8 раз медленнее

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

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

Смотреть ВИДЕОУРОК

Post Views: 11 097

На прошлых уроках мы с вами разобрались в том как устроен МК и посмотрели что такое порты ввода/вывода и их альтернативные функции. Давайте попробуем написать простенькую программу "Мигание светодиодом". Писать программу будем на языке С. Конечно многие начнут возмущаться, мол это жлобство, надо только на ассемблере, ну да и бог с ними. Если я начну рассказывать как писать программу на ассемблере, то скорее всего часть из вас просто подумает: "Как тут все заморочено! А ну его к черту". И от части будут правы. Для того чтобы писать программу, нам потребуется специальная программа. Я пользуюсь CodeVisionAVR V2.04.4a и найти его можно . Или прямо у нас на сайте, это . Надеюсь что с установкой вопросов не возникнет. Запускаем программу. Рисунок 1. Теперь давайте создадим новый проект. Для этого нажимаем File->New . Рисунок 2. Появится вот такое окно. Рисунок 3. Ну тут все понятно, выбираем Project и давим "Ок". И снова вопрос. Рисунок 4. Здесь программа предлагает вам запустить генератор кода. Для начала, а может и всегда, тут уж сами решайте, давим "Yes". Рисунок 5. Вот здесь мы с вами остановимся по подробнее. Это окно, как раз и есть тот самый генератор кода. Здесь мы выбираем что будет проинициализировано МК перед выполнением основной программы. Начнем с вкладки Chip . Ну тут я думаю все понятно. Выбираем наш контроллер, а именно как с вами договорились, ATmega8 и частоту кварца 4 МГц. Рисунок 6. Больше в этой вкладке ничего трогать не надо. Переходим в вкладку Ports . Рисунок 7. Здесь выбираем Port B и нулевой бит меняем с In на Out . Значение по умолчанию на выходе оставим 0. Это значит что при первом старте МК на порту B в нулевом разряде будет висеть логический ноль. В общем смотрите на рисунок выше. Более подробно по настройкам я напишу в отдельных статьях по программированию в среде CodeVisionAVR , а сейчас у нас задача поморгать светодиодом. Ну вроде ничего не забыли. Далее надо сохранить наш проект. Для этого жмем на иконку дискетки и выбираем путь куда сохраним наш проект. Я выбрал так: C:/my_cvavr/project_led , а проект назвал led . Получилось как-то так. Рисунок 8.
Но это еще не все. Теперь давайте попросим программу сгенерить нам наш стартовый код. Для этого жмем на иконку "шестеренка" и дважды под тем же именем, а именно led сохраняем проект. Все. Программа с генерировала нам код и теперь давайте взглянем на него. Расписывать весь код не буду, так как это другая тема, а вот на что мы обратим внимание. Смотрим на картинку ниже. Рисунок 9.
Строка: #include В этой строке мы просим компилятор подключить файл с описаниями всех регистров МК ATmega8. Это сделано для понимания. Что проще понять PORTB или 0x18 . По моему тут все очевидно. Просто в этом файле прописаны эквиваленты понятные простому человеку. Легче понять название порта не же ли его адрес. Строка: PORTB=0x00; Помним, мы хотели чтоб у нас был ноль на выходе по умолчанию, вот он. Правда здесь выстрел и пушки по воробьям, так как это выражение выводит нули в весь порт. А можно по проще PORTB.0=0x00; но это не суть важно. Строка: DDRB=0x01; Вспоминаем предыдущий урок, кто не читал вам сюда . Так как цифра 0x01 в hex равна 0b00000001 и bin. Учимся переводить из шестнадцатеричной системы в двоичную! То мы видим что в нулевой разряд регистра направления DDRB записали 1. То есть будем выводить данные через нулевой разряд порта В . Ну вот, просмотрев код мы убедились что генератор кода с генерировал код как нам надо. Заметьте, очень удобно и быстро. Теперь давайте посмотрим где же нам-то писать код. Для этого бежим вниз до вот этих строк. Рисунок 10.
Да, да. Внутри этого бесконечного цикла и будет наш код. О чем говорит коментарий написанный в теле цикла. Кажется мы хотели поморгать светодиодом. Ну давайте. Так как светодиодом мы будем управлять через нулевой разряд порта "В", то пишем вот такой код. PORTB.0 = 0x01; PORTB.0 = 0x00; Это самый простой способ. Сначала мы записываем в нулевой разряд порта, о чем говорит точка с нулем после порта, единицу, а затем ноль. Вроде светодиод сначала включится, а потом погаснет. А так как это все в бесконечном цикле, то все завертится по кругу. Но к сожалению если мы соберем схему и включим ее, наш светодиод будет гореть постоянно. А почему же, спросите вы. А вот почему. Помните мы выбрали частоту кварца 4 МГц. Во... 4 миллиона импульсов за секунду. И вы хотите уследить за ними. Не получится! Что же делать? А выход есть и даже не один. Вставить паузы длинной, ну к примеру в пол секунды. Вариант первый, использовать аппаратный таймер с прерыванием, но для нашего примера я думаю рановато. Вариант два, мы же работаем в такой замечательной программе как CodeVisionAVR . И для нас сделали хорошую библиотеку полезных функций. И одна из них пауза delay_ms(int x) или delay_us(int x) . Первая задает паузу длинной х миллисекунд, а вторая х микросекунд. Но чтоб ей воспользоваться давайте вернемся на самый верх и после строки #include и напишем под ней #include . Тем самым мы подключили библиотеку и можем смело пользоваться функциями пауз. Давайте допишем наш код. PORTB.0 = 0x01; delay_ms(500); PORTB.0 = 0x00; delay_ms(500); Теперь собираем наш проект. Project->Build All После сборки мы увидим окно Рисунок 11. в котором говорится, что все сделано без ошибок и нет предупреждений. Две строки No errors No warnings Рисунок 12. Вот и все. Для тех кто все же не понял что куда подключать. Рисунок 13.
На схеме отсутствуют питание, кварц и конденсаторы. Я их не стал рисовать так как мы про них уже распинались в предыдущих уроках. Просто эта обвязка обязательна в любом проекте и в дальнейшем не будем рисовать, а при сборке не забудем о них. В следующем уроке мы сваяем программатор и прошьем наш МК.

Пишем первую программу!

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

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

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

Что делать с этим.hex файлом? А он нужен для программатора, чтобы записать код программы в ПЗУ (постоянное запоминающее устройство) микроконтроллера. Мы вместо программатора будем использовать программу Proteus , которая модулирует реальную работающую схему.
В AVR Studio 5 редактор, компилятор, отладчик и программатор все в одном флаконе!

Директива – это предписание редактору, с которым мы будем работать. В большинстве своем название директивы и ее функция в разных редакторах совпадают. Например, если сравнить редактор MPLAB IDE для микроконтроллеров PIC, то разницы практически нет!
А вот команды отличаются. Список команд формируется производителем и в какой-то степени зависит от технологии производства. Даже у одного производителя в разных семействах может быть разный список команд по мере изменения их структуры.
Команда – это единичная функция, с помощью которой мы можем управлять микроконтроллером.
Программа - это последовательность команд, соответствующая определенному алгоритму (составленная нами).
Любая программа начинается с “шапки”, где прописывается основные функции программы, ее автор, название контроллера, который будет использоваться, тактовая частота и другие данные. “Шапка” - это “правило хорошего тона” в программировании.
Обычно в “шапке” прописывается файл инициализации.includeххххdef.inc. директивы.device, .list, .nolist, и другие.
В AVR Studio 5 эти директивы прописаны по умолчанию. В предыдущей версии AVR Studio 4 эти директивы нужно было прописывать, поэтому я их оставил в виде комментариев.
Комментарии пишутся произвольно, как вам нравиться и главное, чтобы они для вас оставались понятными. Нередко, из-за неполных комментариев, через некоторый промежуток времени сам автор не может разобраться в собственной программе. Перед комментариями ставиться знак (;) .
Я ввел “шапку” предложенную Джоном Мортоном…

Редактор AVR Studio 5 – понимает написанные, директивы и команды выделяя их в синий цвет, числовые данные остаются черными, непонятные выражения выделяет в красный, комментарии в зеленый.

Прежде чем начать, что-то писать рассмотрим директивы ассемблера микроконтроллеров AVR.
Перед каждой директивой ставиться точка:
Директива Описание.BYTE Резервировать байты в ОЗУ.CSEG Сегмент программы.DB Определить байт – константу во Flash-памяти или EEPROM .DEF Назначить регистру символическое имя.DEVICE Определяет устройство, для которого компилируется программа.DSEG Сегмент данных.DW Определяет слово во Flash-памяти или EEPROM .ENDM Конец макроса.ENDMACRO Конец макроса.EQU Установить постоянное выражение.ESEG Сегмент EEPROM .EXIT Выход из файла.INCLUDE Вложить другой файл.LIST Включить генерацию листинга.LISTMAC Включить разворачивание макросов в листинге.MACRO Начало макроса.NOLIST Выключить генерацию листинга.ORG Установить положение в сегменте.SET Установить для переменной эквивалентное выражение

Полное описание команд и директив ассемблера в русском переводе Руслана Шимкевича можно посмотреть тут:
🕗 24/09/11 ⚖️ 397,28 Kb ⇣ 244 Здравствуй, читатель! Меня зовут Игорь, мне 45, я сибиряк и заядлый электронщик-любитель. Я придумал, создал и содержу этот замечательный сайт с 2006 года.
Уже более 10 лет наш журнал существует только на мои средства.

Хорош! Халява кончилась. Хочешь файлы и полезные статьи - помоги мне!

--
Спасибо за внимание!

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

--
Спасибо за внимание!
Игорь Котов, главный редактор журнала «Датагор»


Нужно лишь распаковать и стартовать его (при условии, что Proteus уже установлен).
Должна появиться вот такая картинка:

Запустим AVR Studio 5 , и в редакторе напишем небольшую программу с подробными комментариями:
.def temp=r16 ; директива.def назначает регистру r16 имя temp ;==================================================== ; Начало программы.cseg ; директива.cseg определяет начало сегмента, где будет расположен; основной код программы. В AVR Studio 5 это директива не; обязательна.org 0 ; начало первой строки программы rjmp Start ; относительный переход к метке Start (в PIC соответствует; команде goto) ; ==================================================== Start: ser temp; устанавливает все биты регистра temp в 1 out DDRB,temp; переводит все биты out DDRD,temp; порта B и D на вывод clr temp; обнуляет регистр temp (устанавливает все биты регистра temp в 0) out PortB,temp; отключает подтягивающие резисторы out PortD,temp; портов B и D Cicle: ldi temp,0b11001100; включает светодиоды out PortB, temp; порта B rjmp Cicle; Возвращаемся к метке Cicle, зацикливаемся

Скомпилируем ее, кликнув по F7
В окошке Output появятся информация о проведенной компиляции, в конце должна быть надпись Build succeeded, которая подтверждает удачную сборку.hex файла.


Этот файл по умолчанию размещен в Моих документах, в проекте AVR Studio 5.

Переходим в Proteus , кликаем на рисунок контроллера и вводим путь, где находиться.hex файл:




Запускаем эмуляцию программы.
После нажатия кнопки Старт видим результат!



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

Попробуем разобраться, с программой:

Строка:
.def temp=r16 ; директива.def назначает регистру r16 имя temp
Для удобства мы назначили регистру r16 имя temp, это значит что в любом месте программы, при написании слова temp программа будет обращаться к регистру r16.
Что это за регистр – r16?
Он относится к регистрам общего назначения, которые обозначаются как r0….r31, т.е их всего 32 по 8 бит. Вторая половина регистров общего назначения r16…r31 выполняют функцию временного хранения информации (как аккумуляторы, могут принимать и могут отдавать информацию, для сравнения - у PIC контроллеров всего один аккумулятор W). Последние 6 регистров общего назначения условно объединены в три 16-битных:

Обратите внимание, что старший байт пишется раньше, младший позже.

r27:r26 называется регистром X,
r29:r28 называется регистром Y,
r31:r30 называется регистром Z
Программа будет так же работать если слово temp будет присвоено любому другому регистру общего назначения от r16 до r31 например.def temp=r20. После компиляции (клавиша F7 в AVR Studio 5), разницы в работе программы никакой нет.
(Можете попробовать, теперь вы знаете, как это сделать!)

Строка:
.cseg ; директива.cseg определяет начало сегмента, где будет расположен основной код программы
В AVR Studio 5 это директива прописана по умолчанию.
В микроконтроллерах AVR выделяют разные области памяти, для хранения программного кода, данных в постоянной памяти или EEPROM, для обозначения этой области существуют директории:
.cseg - Программный сегмент
.dseg – Сегмент данных
.eseg – сегмент EEPROM

Строка:
.org 0 ; начало первой строки программы
Программа начинается со строки указанной директивой.org, и при завершении будет возвращаться к ней.

Строка:
rjmp Start ; относительный переход к метке Start (в PIC соответствует команде goto)
Это команда относительного перехода к метке Start.
Программа выполняется последовательно от верхней стоки к нижней. Если требуется перейти в другую область программы то используется команда rjmp

Строки:
ser temp ; устанавливает все биты регистра temp в 1 clr temp ; обнуляет регистр temp (устанавливает все биты регистра temp в 0)
Команда ser выполняется с регистрами r16…r31, а команда clr может выполнятся ко всем регистрам, в том числе и регистрам ввода-вывода (регистрам специального назначения). Назначение этих регистров мы рассмотрим позже.

Строка:
ldi temp,0b11001100
Команда ldi записывет в регистр temp двоичное число 11001100 (не будет ошибкой если вместо 0b11001100 написать 204 или CCh, в одном случае в десятичной форме в другом в шестнадцатеричной)

Строка:
out DDRB,temp
Команда out выводит значение регистра temp в регистр порта DDRB.

Что такое порт и как он работает?

Если упростить до минимума, то порт можно представить как ножку-вывод микроконтроллера, на который в режиме вывода можно произвольно подать напряжение или отключить, а в режиме ввода определить, есть ли напряжение, поданное извне или нет.
Есть три режима работы порта: это вывод, ввод и отключенное состояние.
Управляют портами регистры PinX, PortX, DDRX.
Х – означает порт, которым управляют эти регистры.
Регистр DDRX управляет режимами ввода-вывода, как реле – включили (записали 1) – подключился на линию вывода, отключили (записали 0) – переключился на линию ввода (по умолчанию).

Чтобы вывести данные в порт Х, нужно регистр DDRX переключить в режим вывода (прописать 1) а данные переслать в регистр PortX.
Считать значение порта Х можно, если регистр DDRX переключить в режим ввода (прописать 0), из регистра PinX. Чтобы подключить подтягивающие резисторы в режиме ввода, надо отправить данные в PortX.
Подтягивающие резисторы избавляют нас от необходимости подключать внешние резисторы на положительный провод и с помощью одной команды сами подают логическую единицу на вход.

Читательское голосование

Статью одобрили 23 читателя.

Для участия в голосовании зарегистрируйтесь и войдите на сайт с вашими логином и паролем.

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

Большинство команд микроконтроллеров AVR выполняются в один такт генератора задающей частоты. В качестве которого широко используют встроенную в МК RC-цепочку или подключают к выводам XTAL1 и XTAL2 кварцевый резонатор.

Например, если МК работает с частотой 1 Гц, то одна команда будет выполняться за одну секунду

По умолчанию у МК ATmega8 задействован собственный внутренний генератор частоты, а точнее RC-цепочка, которая работает на частоте 1000 000 Гц = 1 МГц. Поэтому время выполнения одной команды равно:

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

#include

int main (void )

DDRD = 0b000000011;

while (1)

PORTD = 0b000000001; // Подаем питание на 1-й светодиод

PORTD = 0b000000010; // Подаем питание на 2-й светодиод

Но на самом деле второй LED загорится с разницей во времени 0,000001 секунды от первого. Наши глаза не могут заметить такой малой разницы во времени. Уже при частоте изображений более 24 Гц (t = 1/24 ≈ 0,042 с) наше зрение формирует из отдельных картинок непрерывный фильм. Поэтому в большинстве случаев мы не различаем 25-й кадр.

Для того, чтобы оба светодиода засветились с разницей во времени 0,5 секунды необходимо между соответствующими двумя командами (PORTD = 0b000000001; и PORTD = 0b000000010; ) поместит еще 500 000 однотактных пустых команд, т. е. заставить МК полсекунды не выполнять никаких полезных действий. Или, как говорят, нужно “убить” 500 000 тактов. Если код пишется на Ассемблере, то программисты применяют различных циклы, которые “съедают” определенное число тактов и тем самым получают различные интервалы времени.

#include

int main (void )

DDRD = 0b000000011;

while (1)

PORTD = 0b000000001; // Подаем питание на 1-й светодиод

Для получения задержки 0,5 секунды сюда нужно вставить

500 000 однотактных команд

PORTD = 0b000000010; // Подаем питание на 2-й

Функция _delay_ms() и мигающий светодиод

При написании кода на Си в Atmel Studio имеется очень удобная функция _delay_ms () . Для работы данной функции ее необходимо предварительно подключить директивой препроцессора .

В круглых скобках данной функции можно задавать время в миллисекундах, тогда перед скобками нужно записать ms, или в микросекундах – us:

При использовании данной функции для того, чтобы при компиляции Atmel Studio не выдавала никаких предупреждений, следует объявить частоту с помощью оператора #define . Так как по умолчанию для ATmega8 она равна 1 000 000 Гц, то это значение мы и объявим. Это делается следующей строкой:

#define F_CPU 1000000UL

В дальнейшем, когда мы будем подключать к МК кварцевый резонатор, без данной строки уже не обойтись. Структура ее останется прежней, только вместо 1 000 000 нужно будет записать частоту кварцевого резонатора.

Давайте улучшим нашу программу, так, чтобы сначала загорался один светодиод, затем через полсекунды он гаснул и еще через полсекунды загорался второй и снова через 0,5 с гаснул.

# define

Давайте посмотрим на код, приведенной выше, еще раз. Если нам необходимо изменить значение задержки времени в функции _ delay из 500, например на 300, то мы должны отыскать все строки с ее именем и выполнить соответствующую замену. Теперь представим, что таких строк сотня, а то и тысяча. Изменять значение каждого числа по отдельности крайне неудобно и долго. К тому же можно случайно пропустить строку. Поэтому необходимо применять другой, более удобный и практичный подход.

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

С помощью оператора #define мы присвоим числовому значению какое-либо имя. Это имя называется константа . В отличие от переменной, константа не может изменяться в программе. Выглядит это так:

#define MIG 300

_ delay _ ms (MIG);

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

После строки с директивой препроцессора #define точка с запятой не ставится. Между именем константы и числовым значением ставится пробел.

Данная строка работает следующим образом. Перед началом компиляции выполняется замена числом 300 всех констант с именем MIG.

#define и регистры

Также оператор #define хорош тем, что с помощью него можно задавать имена регистрам. Например, если мы подключаем к порту D светодиоды, то вместо PORTD мы можем записать, например VD:

#define VD PORTD

VD = 0b00000001;

Давайте перепишем программу, применяю директиву #define :

#define F_CPU 1000000UL

#include

#include

Поделиться: