Мигание светодиодом avr. Программирование микроконтроллеров AVR

Мигание светодиодом avr. Программирование микроконтроллеров AVR

22.11.2022
разработчик 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 можете скачать

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

Большинство команд микроконтроллеров 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

Добавлено: 28.06.2017 в 13:00

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

Итак, создаём новый проект и приступаем. Приведу сразу полный код программы, а затем поясню всё более подробно:

/* * tiny13_board_blink * Демо-прошивка отладочной платы на ATtiny13 * с целью проверки работоспособности МК. * Мигаем светодиодом. */ #define F_CPU 1200000UL // Указываем тактовую частоту МК #define LED PB2 // Используем светодиод, подключенный к PB2 (7 пин) #include // Подключаем определения ввода/вывода #include // Подключаем библиотеку функций задержки int main(void) { // Светодиод DDRB |= (1<

В самом начале задаём значения констант и подключаем заголовочные файлы и библиотеки.
Файл avr/io.h подключает определения ввода/вывода для конкретного типа микроконтроллера (тип МК указывается в виде опции для компилятора).
Библиотеку util/delay.h подключаем для использования функций задержки, в нашем случае: _delay_ms() . Для работы функций задержки мы должны указать тактовую частоту процессора. Поэтому ДО подключения util/delay.h определяем константу F_CPU (в данном случае - 1,2МГц).

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

while (1) { ... }

Для начала, мы должны сконфигурировать порт ввода/вывода. В МК AVR старших моделей портов в/в может быть несколько (A, B, C, D). К каждому порту может быть подключено до восьми ножек. Каждая из ножек может быть настроена как на вход, так и на выход. ATtiny13 имеет только один порт (B), к которому подключены шесть ножек (PB0-PB5, см. datasheet). По умолчанию все ножки настроены на вход, а чтобы управлять светодиодом, мы должны использовать соответствующую ножку как выход. В микроконтроллерах AVR вся аппаратная часть настраивается посредством восьмибитных регистров. Направление (вход-выход) устанавливается битами регистров DDRx (где x - буква порта, в нашем случае B). Значение бита "0" - соответствует входу, "1" - выходу. Таким образом, чтобы использовать ножку PB2, как выход, мы должны установить второй бит регистра DDRB в единицу:

DDRB |= (1<

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

PORTB &= ~(1<

Чтобы включить (подать высокий уровень сигнала) - соответственно, записываем единицу:

PORTB |= (1<

Теперь, когда порт в/в сконфигурирован, мы запускаем основной цикл, в котором будем инвертировать состояние выхода PB2 (чередовать высокий и низкий уровень сигнала) с задержкой 500мс. Таким образом, светодиод у нас будет мигать с частотой 1 раз в секунду.

На прошлых уроках мы с вами разобрались в том как устроен МК и посмотрели что такое порты ввода/вывода и их альтернативные функции. Давайте попробуем написать простенькую программу "Мигание светодиодом". Писать программу будем на языке С. Конечно многие начнут возмущаться, мол это жлобство, надо только на ассемблере, ну да и бог с ними. Если я начну рассказывать как писать программу на ассемблере, то скорее всего часть из вас просто подумает: "Как тут все заморочено! А ну его к черту". И от части будут правы. Для того чтобы писать программу, нам потребуется специальная программа. Я пользуюсь 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.
На схеме отсутствуют питание, кварц и конденсаторы. Я их не стал рисовать так как мы про них уже распинались в предыдущих уроках. Просто эта обвязка обязательна в любом проекте и в дальнейшем не будем рисовать, а при сборке не забудем о них. В следующем уроке мы сваяем программатор и прошьем наш МК.


Мигающий светодиод проект на AVR


Схема мигающего светодиода проста, это типовая обвязка микроконтроллера AVR Atmega8 (смотри ) + резистор со светодиодом подключенные к выводу 14.

Надеюсь что с установкой и скачкой утилиты вопросов не возникает. Поэтому после инсталляции и перезагрузки ПК запускаем программу и создаем новый проект. Для этого нажимаем в меню File->New. Появится окно "Createe New File" (Создать новый файл). В нем выбираем File Type (Тип файла) - Project (проект) и нажимаем Ok. Появляется новое окно "Вы хотите создать новый проект и использовать генератор кода?" Отвечаете Да (Yes). В окне генератора кода мы выбираем что будет проинициализировано нашим микроконтроллером перед стартом основной программы. Начнем с вкладки Chip , в ней выбираем наш МК: Atmega 8 с частотой 4 МГЦ.

Переходим в следующую вкладку Ports . Здесь в Port B нулевой бит переключаем с In на Out. Значение по умолчанию на выходе оставляем нулевым. Это значит что при запуске МК на порту B в нулевом разряде будет логический ноль. Далее сохраняем наш проект жесткий диск, например в C:/my_cvavr/project_led.

Для генерации стартового кода жмем на иконку "шестеренка" (немного правее иконки сохранить) и дважды под тем же именем сохраняем проект. Давайте теперь рассмотрим отдельные моменты полученного кода.


Строка: include mega8.h в ней мы говорим компилятору о том, что требуется подключить файл с описаниями всех регистров ATmega8

Строка: PORTB=0x00; по умолчанию установлен логический ноль на выходе. Правда этим выражением выводится нули в весь порт. А можно чуть по проще PORTB.0=0x00;

Строка: DDRB=0x01 ; Вспоминаем статью " ". Так как цифра 0x01 в hex равна 0b00000001 и bin. переведя это выражение из шестнадцатеричной системы в двоичную, мы поймем, что в нулевой разряд регистра направления DDRB записали 1, т.е будем выводить данные через нулевой разряд порта В.

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

Так как светодиодом мы управляем через нулевой разряд порта "В", то пишем:

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, После сборки мы увидим окно в котором говорится, что все сделано без ошибок и нет различных предупреждений. Две строки: No errors и No warnings.

© 2024 soundpad-voice.ru - Компьютер. Ноутбуки. ОС. Программы