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

Делаем первые шаги в программировании микроконтроллера Atmega8 – пишем
программу прошивки. Как задавать значения битов в регистрах ввода/вывода?

Итак, будем считать, что всё необходимое для эволюционного развития отечественного МК-строения у нас куплено, скачано и собрано воедино. Теперь остаётся придумать простенький проект, написать программу в Atmel Studio, скомпилировать код, после чего проверить его работоспособность с помощью симулятора микроконтроллеров, а также прочих электронных устройств – Proteus.

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

Забегая вперёд, приведу схему готового проекта на Atmega8, собранного в Протеусе, которую нам ещё предстоит запрограммировать в Atmel Studio и только потом проверить в симуляторе.

Мигающий светодиод на микроконтроллере Atmega8

Рис.1 Мигающий светодиод на микроконтроллере Atmega8

Теперь по порядку.

1. Для начала запускаем программу Atmel Studio:
— Кликаем по вкладке New и далее Project.
— В открывшемся окне выбираем язык программирования C/C++.
     В строке для ввода Name – название проекта, например, Svetodiod.
     В графе Location – место желаемого расположения папки с файлами проекта.
     В качестве типа проекта выбираем – GCC C Executable Project.
     Жмём – ОК.
— В новом окне находим и выделяем микроконтроллер ATmega8 и опять нажимаем кнопку OK.
— После проделанных манипуляций Atmel Studio нам автоматически сформировала заготовку
     (шаблон) программы:

/*
* GccApplication2.c
*
* Created: 22.06.2021 11:37:14
* Author : Vpayaem.ru
*/

#include <avr/io.h>

int main(void)
{
     /* Replace with your application code */
     while (1)
     {
     }
}


Текст, в нашем случае выделенный зелёным цветом, является комментариями, которые полностью игнорируются компилятором.
Комментарии могут быть многострочными (как в приведённом шаблоне), где текст помещается между /* и */, а могут быть однострочными.
Однострочный комментарий обозначается двумя косыми линиями перед текстом и действует в пределах одной строки, например: // Здесь написан однострочный комментарий.

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

1. #include <avr/io.h>
В данной строке подключается библиотека микроконтроллера, в которой находятся определения констант, имён регистров и всего прочего, что может понадобиться для выполнения базового ввода-вывода информации.

Для корректного выполнения микроконтроллером некоторых функций (например, таких как задержка), в настройках проекта необходимо прописать директиву, указывающую – с какой тактовой частотой будет работать микроконтроллер. Такой директивой является:
2. #define F_CPU 1000000UL
Так как частота по умолчанию для ATmega8 (при работе со встроенным генератором) равна 1 000 000 Гц, то это значение мы и объявим в виде числа 1000000.

При написании кода на языке Си в Atmel Studio имеется очень удобная функция задержки выполнения программы _delay_ms(). Для включения данной функции её необходимо предварительно прописать директивой:
3. #include <util/delay.h>

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

Далее заготовка из Atmel Studio выдаёт нам следующую строку:

4. int main(void)
Здесь ничего мудрить не надо. В данной строке объявляется функция main, с которой начинается выполнение программы.


А вот теперь нам необходимо сконфигурировать единственный задействованный нами сигнальный вывод Atmega8 PC0 как выход (Рис.1).

Давайте-ка немного отвлечёмся и поговорим об этом поподробнее. Ведь мы знаем, что большинство выводов (портов) микроконтроллера могут работать и как входы, и как выходы. Поэтому любой задействованный разряд МК предварительно нужно настроить на соответствующий режим. Для этого в микроконтроллере есть специальный регистр, который называется DDRx (direct data register – регистр направления данных), где x обозначает букву соответствующего порта, т. е. DDRС, к примеру, определяет направление передачи данных для интересующего нас порта С.
Чтобы настроить вывод порта как вход, в регистр DDRх необходимо записать ноль, а для настройки в качестве выхода – единицу.

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

5. DDRC |= ( 1 << 0 );
Эта запись означает, что для нулевого разряда порта С (номер разряда указывает последняя цифра), т. е. для вывода PC0 мы прописали в DDR единицу и тем самым настроили его как выход.


А вот, если бы нам туда понадобилось прописать ноль, т. е. настроить PC0 как вход, то команда для начинающего программиста выглядела бы ещё более странной:

DDRC &= ~( 1 << 0 );

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

Довольно распространён способ настройки DDR регистров и в виде двоичного кода. Данная форма записи удачно сочетается с количеством битов порта, так как количество битов соответствует количеству выводов порта, а порядковый номер бита отвечает номеру бита внутри порта.
В нашем случае, чтобы настроить PC0 на выход следует написать следующую команду:

DDRC = 0b0000001;

Здесь префикс 0b идентифицирует следующее за ним число как двоичное, а порядковый номер бита отвечает номеру бита внутри порта. То есть последний (крайний правый) разряд этого числа соответствует PC0, предпоследний – PC1 и т. д. Как итог: PC0 настроен как выход, все остальные разряды (PC1...PC6) – как высокоимпедансные входы.

По большому счёту данная команда (в виде двоичного кода) выполняет ту же функцию, что и побитовая настройка порта. Однако, если побитовая команда DDR аккуратно настраивает каждый отдельный выбранный нами разряд, то двоичная форма записи конфигурирует сразу все разряды порта, что повышает вероятность (особенно на первых этапах освоения программирования) всяческих неприятных ошибок.

Далее в нашей заготовке прописана функция:

while (1)
{
}


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

Теперь внутри цикла можно записывать команды управления светодиодом.
Для того, чтобы управлять уровнем напряжения на любом выходе служит регистр PORTх, где, опять же, x - обозначает букву соответствующего порта. Если бит установлен в единицу, то на выходе будет уровень близкий к напряжению питания микроконтроллера, если бит установлен в ноль, то на выводе будет напряжение близкое к нулю.
Установим логическую 1 на выходе PC0, т. е. заставим светодиод светиться:

PORTC |= (1 << PC0);
Здесь всё записывается аналогично форме, которую мы использовали для программирования регистров DDRx. В виде двоичного кода эта форма имела бы вид:
PORTС = 0b0000001;

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

_delay_ms(500);
будет поддерживать высокий уровень на PC0 в течении 500 мс (0,5 сек) или до бесконечности, пока мы его не сбросим в ноль.

А сбросим мы его в ноль сразу по прошествии 0,5 сек посредством команды:

PORTC &= ~(1 << PC0);

Теперь опять полсекунды подождём:

_delay_ms(500);

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

/*
* GccApplication2.c
*
* Created: 22.06.2021 11:37:14
* Author : Vpayaem.ru
*/

#include <avr/io.h>
#define F_CPU 1000000UL   // Выбираем частоту МК
#include <util/delay.h>   // Включаем функцию задержек
int main(void)
{   // Начало основной программы

DDRC |= ( 1 << 0 );   // Устанавливаем вывод порта PC0 - как выход

     while (1)
     {   // начало цикла

PORTC |= (1 << PC0);   // Лог. 1 на выходе PC0
_delay_ms(500);   // Задержка 500мс
PORTC &= ~(1 << PC0);   // Лог. 0 на выходе PC0
_delay_ms(500);   // Задержка 500мс

     }   // конец цикла

}   // конец программы


Ну вот и всё!
Теперь нужно перенести готовый код в Atmel Studio и скомпилировать его. Для этого необходимо кликнуть по кнопке Build и в выпавшем меню выбрать Build Solution.
Если ошибок нет, то файл успешно скомпилируется, а в нижней части экрана появится надпись:
==== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ====

Теперь нам надо войти в папку, в которой мы сохранили наш проект, найти там ещё одну папку с названием Debug и убедиться в существовании там файла с расширением HEX. При помощи этого файла производится прошивка микроконтроллера или проверка работоспособности устройства в программе для автоматизированного проектирования Proteus.

Скачать файл svetodiod.hex можно по ссылке – скачать файл

А на следующей странице будем подключать к Atmega8 кнопку, устранять дребезг контактов и управлять ей свечением светодиода.




 

Главная страница | Наши разработки | Полезные схемы | Это нужно знать | Вопросы-ответы | Весёлый перекур
© 2017 Vpayaem.ru   All Rights Reserved