Категории публикаций
Подписка на рассылку по Email
новости (подписчиков: 4)
комментарии (подписчиков: 2)

Отменить подписку
Популярные публикации
Интересный опрос
Какую платежную систему вы предпочетаете?

Счет в Банке
Webmoney
PayPal
BitCoin и другие криптовалюты
okPay
ePayServices
AdvCash
Ее нет в списке
Поблагодарить автора
donate
1B4ZZ2PXUd9E7AqEB82AqFnkpk2bt5hQG7

Простая программа для AVR микроконтроллера на языке Си

Размещено в категории: Микроконтроллеры

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

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

Язык Си является компилируемым статически типизированным языком программирования, который был разработан сотрудниками Bell Labs - Кеном Томпсоном (Ken Thompson) и Деннисом Ритчи (Dennis MacAlistair Ritchie) в начале 1970-х годов. Это универсальный язык программирования с современным набором операторов и типов, также Си является языком достаточно "низкого уровня" (приближенный к машинным кодам) что позволяет нам работать с памятью, адресами и минимальными единицами данных.

Изначально язык Си использовался в операционной системе Unix для написания приложений и самого ядра ОС. Позже он был портирован и на другие платформы, что принесло ему очень широкую популярность.

При разработке программ на языке Си для AVR микроконтроллеров используется набор библиотек avr-libc и компилятор avr-gcc, с установкой которых в Linux мы уже разобрались в одной из прошлых статей.

 

Исходный код программы на языке Си

Вполне может быть что вы никогда не писали программ на языке Си - в этом нет ничего страшного. Для того чтобы написать первую программу на AVR C и провести с ней эксперименты вполне достаточно базовых знаний по работе с консолью в Linux. Позже вы сами сможете найти недостающую информации и изучить все необходимое самостоятельно.

Приведенный ниже код программы на Си будет выполнять следующие действия (алгоритм действий):

  • зажечь светодиод 1 и погасить его с небольшой задержкой (два раза подряд);
  • выполнить более длительную задержку;
  • зажечь светодиод 2 и погасить его с небольшой задержкой (два раза подряд);
  • начать все сначала.

Вот исходный код программы который работает по приведенному выше алгоритму:

/* Светодиодная мигалка на микроконтроллере ATmega8
 * https://ph0en1x.net
 */
#define F_CPU 1000000UL  // укажем компилятору частоту ЦПУ
#include <avr/io.h>      // Подключим файл io.h
#include <util/delay.h>  // Подключим файл delay.h

void main(void) {                // начало программы
    // -- установим параметры --
    int delay_ms_1 = 100;        // задержка для светодиода
    int delay_ms_2 = 300;        // задержка между светодиодами
    
    // -- настроим пины порта --
    DDRD |= (1 << PD0);          // пин PD0 порта DDRD на вывод
    DDRD |= (1 << PD1);          // пин PD1 порта DDRD на вывод
    
    // -- основной цикл программы --
    while (1) {                  // реализация бесконечного цикла
        PORTD |= (1 << PD0);     // на пине PD0 высокий уровень
        _delay_ms(delay_ms_1);   // задержка по времени 1
        PORTD &= ~(1 << PD0);    // на пине PD0 низкий уровень
        _delay_ms(delay_ms_1);
        PORTD |= (1 << PD0);
        _delay_ms(delay_ms_1);
        PORTD &= ~(1 << PD0);
        
        _delay_ms(delay_ms_2);   // задержка по времени 2
        
        PORTD |= (1 << PD1);     // на пине PD1 высокий уровень
        _delay_ms(delay_ms_1);   // задержка по времени 1
        PORTD &= ~(1 << PD1);    // на пине PD1 низкий уровень
        _delay_ms(delay_ms_1);
        PORTD |= (1 << PD1);
        _delay_ms(delay_ms_1);
        PORTD &= ~(1 << PD1);
    }
}

Рассмотрим все строки в исходном коде более подробно. Строки или части строк что начинаются с двух слешей "//", а также блоки текста что начинается с символов "/*" и заканчивается символами "*/" - это комментарии. В комментариях может размещаться полезная информация и примечания.

Строкой "#define F_CPU 1000000UL" мы объявляем константу, которая говорит компилятору что частота ЦПУ нашего микроконтроллера равна 1000000Гц (1МГц). Данное объявление необходимо для правильной работы некоторых функций, в нашей программе это функция "_delay_ms". В моем примере микроконтроллер ATmega8 без установки битов-фьюзов по умолчанию работает на внутреннем тактовом RC-генераторе с частотой 1МГц.

Строка "#include <avr/io.h>" производит подключение файла "io.h" к текущему файлу исходного кода, а строка "#include <util/delay.h>" - подключает файл "delay.h".

Узнать где размещаются данные файлы в Linux можно при помощи команды "locate". Например  выполним поиск путей где размещаются файлы "io.h" и отфильтруем результаты что содержат сочетание "avr":

locate io.h | grep avr

В результате получим список путей ко всем файлам где в имени встречается "io.h", а также путь содержит подстроку "avr":

/usr/lib/avr/include/stdio.h
/usr/lib/avr/include/avr/io.h
/usr/share/doc/avr-libc/avr-libc-user-manual/group__avr__io.html
/usr/share/doc/avr-libc/avr-libc-user-manual/group__avr__stdio.html
/usr/share/man/man3/io.h.3avr.gz
/usr/share/man/man3/stdio.h.3avr.gz

Здесь мы можем видеть что нужный нам файл находится по пути "/usr/lib/avr/include/avr/io.h". Посмотрев его содержимое можно увидить что он содержит в себе включение других файлов с определениями (AVR device-specific IO definitions), которые в свою очередь зависят от выбранного нами типа микроконтроллера. Тип микроконтроллера (MCU Type) в даном случае указывается как параметр "-mmcu=atmega8" (для ATmega8) при вызове команды-компилятора "avr-gcc".

В моем случае для микроконтроллера ATmega8 через файл "io.h" подключается следующий файл - "iom8.h" (Input Output Mega8), в нем хранятся все определения таких переменных как PD0, PD1, PB8, DDRD, DDRB, RAMSTART, RAMEND и много всего другого.

Файлы с определениями IO (io*.h) для каждого типа МК хранятся в директории по адресу "/usr/lib/avr/include/avr/", рекомендую зайти туда и посмотреть что в ней творится для более глубокого понимания.

Полистать содержимое файла iom8.h можно в редакторе nano, для этого выполним команду:

nano /usr/lib/avr/include/avr/iom8.h

Для поиска в редакторе nano используйте комбинацию клавиш CTRL+W (для запоминания: where, где).

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

cat /usr/lib/avr/include/avr/iom8.h | grep RAM

Данная команда выведет вот такой текст:

#define RAMSTART         (0x60)
#define RAMEND           0x45F
#define XRAMEND          RAMEND

Таким образом можно посмотреть какие есть константы и определения в библиотеке avr-gcc для работы с операциями ввода-вывода(Input-Output), их значения и многое другое для вашего типа микроконтроллера!

Файл "delay.h" содержит в себе определения функций задержки, в частности там содержится код функции "_delay_ms", которую мы будем использовать в примере. Для просчета временной задержки такие функции используют константу "F_CPU", которую мы объявили раньше в начале кода.

Строкой "void main(void) {" с левосторонней фигурной скобки начинается тело нашей программы и заканчивается оно правосторонней фигурной скобкой "}" в самом низу листинга. Таким образом мы объявили основную функцию "main" с которой начнется выполнение программы, тело функции взято в фигурные скобки, а ключевые слова "void" означают что функция не принимает и не возвращает никаких данных, в данном случае.

Важно знать что в языке Си символ точка с запятой ";" является специальным символом - пустым оператором (который ничего не выполняет) и используется для указанию компилятору что это конец команды.

В строчке "int delay_ms_1 = 100;" мы объявили новую переменную "delay_ms_1" с типом "int" (Integer, Целый тип, значения от -32768 до 32767) и присвоили ей значение 100. Служит она в нашей программе для установки задержки в миллисекундах при мелькании каждого из светодиодов.

В следующей строке "int delay_ms_2 = 300;" мы также выполнили инициализацию переменной, которая будет служить для установки времени задержки между мельканиями отдельных светодиодов - 300 миллисекунд.

Дальше идет команда "DDRD |= (1 << PD0);" которой мы настраиваем канал PD0 порта DDRD на вывод. Для этого, при помощи левостороннего сдвига битов (разряов) числа 1, создается битовая маска которую мы накладываем на содержимое регистра управления каналами порта DDRD при помощи битовой операции "|" (логическое ИЛИ).

Следующая команда идентична предыдущей за исключением того что она устанавливает на вывод канал PD1 порта DDRD.

К каналам PD0 и PD1 (ножки 2 и 3 чипа) у нас подключены светодиоды, свечением которых мы и будем управлять.

Строкой "PORTD |= (1 << PD0);" мы выполняем установку 1-го бита в регистре (1 байт) порта PORTD, чем мы подаем на канал PD0 высокий уровень и тем самым зажигаем подключенный к нему светодиод. Также данную запись можно записать вот так: "PORTD = PORTD | (1 << PD0);". Здесь мы присваиваем переменной PORTD содержимое этой же переменной применив к ней битовую операцию "|" (логическое ИЛИ), в качестве аргумента которой выступает результат выражения "1 << PD0", который в свою очередь представляет число один, биты которого сдвинуты на PD0 разрядов влево.

Дальше мы выполняем небольшую задержку по времени "_delay_ms(delay_ms_1);" вызывая функцию _delay_ms и передав ей в качестве аргумента переменную delay_ms_1, которая уже содержит число 100.

Строкой "PORTD &= ~(1 << PD0);" мы выполняем сброс 1-го бита в регистре порта PORTD и гасим светодиод, который подключен к каналу PD0. Более развернуто данную строку можно записать так: "PORTD = PORTD & ~(1 << PD0);". Здесь мы выполняем запись в порт PORTD его начального значения, перед тем применив к нему операцию "&" (логическое И), в качестве второго аргумента которой передаем результат комплексного выражения "~(1 << PD0)", которое в свою очередь представляет число один, сдвинутое на PD0 разрядов влево с применением к результату оператора инверсии "~" (смена всех битов на противоположные).

В следующих строках мы снова выполняем установку и сброс бита PD0 в регитре порта PORTD с установленной задержкой "delay_ms_1", чем мы заставляем светодиод подключенный к каналу PD0 зажечься и погаснуть (мелькнуть, blink).

Строкой "_delay_ms(delay_ms_2);" выполняется более длительная задержка по времени с использованием значения переменной "delay_ms_2" которая выше получила значение 300 (задержка в 300 миллисекунд).

Дальше мы дважды производим установку и сброс бита PD1 (2-й бит в байте регистра) в регистре порта PORTD, чем заставляем мелькать светодиод что подключен к каналу PD1 порта PORTD микроконтроллера.

По завершению приведенных команд все начинается снова в бесконечном цикле "while (1)".

Самое сложное к пониманию здесь это, пожалуй, работа с установкой нужных битов в портах. Более подробно данная тема освещена в статье: Работа с регистрами AVR микроконтроллера на Си, битовые операции.

 

Компиляция и прошивка программы в МК

Для компиляции программы сохраним исходный код в файле под названием "leds_blinking.c". Если у вас уже настроена среда Geany то для компиляции достаточно нажать на панели инструментов кнопку "Compile". Для компиляции файла с программой на Си в консоли нужно выполнить команду:

avr-gcc -mmcu=atmega8 -Os leds_blinking.c -o leds_blinking.o

В результате работы, если нет ошибок, получим объектный файл leds_blinking.o с которого нам нужно извлечь необходимые данные для прошивки нашего микроконтроллера (в моем случае ATmega8, параметр "-mmcu=atmega8").

Для извлечения данных и построения файла прошивки в формате Intel Hex нужно нажать в Geany кнопку "Build". Из консоли получить нужный файл можно при помощи команды:

avr-objcopy -j .text -j .data -O ihex leds_blinking.o leds_blinking.hex

Теперь, когда у нас есть файл с прошивкой в формате Intel HEX останется записать его содержимое (прошить) во флешь-памяти микроконтроллера, выполнить эту операцию можно нажав в подготовленной нами среде Geany кнопку "Run or view current file" (Execute). В консоли выполнить прошивку можно при помощи avrdude командой (для ATmega8 параметр "-p m8", программатор USBAsp "-c usbasp"):

avrdude -c usbasp -p m8 -P usb -U flash:w:leds_blinking.hex

Сразу после прошивки на МК будет послана команда сброса(RESET) и программа начнет выполняться в кристалле, о чем будут свидетельствовать помигивающие светодиоды. Также RESET можно выполнить и вручную, переподключив для этого питание к микроконтроллеру.

Желательно выполнять все шаги (компиляция+построение hex-файла + прошивка) поочередно и вести наблюдение за информацией что появляется в консоли или на информационной панели Geany. Это поможет обнаружить ошибки и замечания если что-то не будет работать так как нужно.

 

Документация по языку Си и AVR Си

Брайан Керниган и Dennis Ritchie - Язык программирования C: brian-kernighan-and-dennis-ritchie-c-language.pdf.zip (2,1Мб, PDF).

Герберт Шилдт - Полный справочник по C: gerbert-shildt-c-complete-guide.zip (912Кб, HTML).

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

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

Библиотека Си для AVR микроконтроллеров (AVR C Runtime Library) - https://savannah.nongnu.org/projects/avr-libc/

По приведенной выше ссылке можно почитать документацию (на английском языке) прямо на сайте или же скачать ее одним файлом в форматах HTML и PDF, там есть вся необходимая информация по использованию библиотеки avr-libc для программирования AVR микроконтроллеров.

 

Заключение

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

В отличие от предыдущей программы на Ассемблере, здесь светодиоды мелькают немного по другому, вот небольшая видео-демонстрация работы собранной схемы с прошитой программой на Си:

В этом видео программатор USBAsp уже отключен, а питание схемы на микроконтроллере осуществляется от батареи КРОНА с напряжением 9В через схему стабилизатора напряжения которая обеспечивает на выходе стабильные 5В.

Начало цикла статей: Программирование AVR микроконтроллеров в Linux на языках Asembler и C.

 (0/5) голосов: 0   просмотров: 837


Тематика:  AVR  микроконтроллер  Linux  Си  avrdude

Комментарии к публикации (4)
SeWIR 1 SeWIR 
26 Ноябрь 2016 14:21

Не слишком ли сложно для новичков работать с портами побитно через сдвиги и логические операции?
Может проще для их понимания:
// -- настроим пины порта --
DDRD = 0x01; // пин PD0 порта DDRD на вывод
DDRD = 0x02; // пин PD1 порта DDRD на вывод

// -- основной цикл программы --
while (1) { // реализация бесконечного цикла
PORTD = 0x01; // на пине PD0 высокий уровень
_delay_ms(100); // задержка по времени 1
PORTD = 0x00; // на пине PD0 низкий уровень
_delay_ms(100);
PORTD = 0x01;
_delay_ms(100);
PORTD = 0x00;

0 
ph0en1x 2 ph0en1x 
26 Ноябрь 2016 16:09

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

Чем раньше будущий разработчик программ для МК ознакомится с этой информацией - тем лучше, поскольку при программировании МК и периферии очень часто придется иметь дело с отдельными битами и не важно на каком языке пишется программа - на Си или Ассемблере.

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

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

0 
SeWIR 3 SeWIR 
09 Декабрь 2016 17:41

Всёже для полных новичьков будет сложно понять как работать с битами через сдвиги, непонимая, того же асемблера, а здесь речь о Си. Статью нужно дополнить более низкоуровневыми примерами и подробными пояснениями как и что делает побитовая логика И(&) ИЛИ(|) НЕ(!) ИЛИНЕ(^), инверсия битов(~). Банально на примерах из одной байтных чисел. И я бы не советовал новичькам такую литературу как Язык программирования C (c) Керниган и Ричи, там материал подан для тех кто знает поднаготную С и просто хочет закрепить свою базу. В самом крайнем случае можно посоветовать литературу из разряда С для чайников (как по мне там уж больно для даунов все расписано) - она даст возможность постепенно въехать в курс.
Для серьйозной разработки софта для МК обязательно нужно знать асемблер и ещё какойнить высокоуровневый язык (в данном случае Си). Для ардуиншиков хватит и простого Си, ведь как я понял ардуино это всеголишь задефайненый Си?

0 
ph0en1x 4 ph0en1x 
09 Декабрь 2016 21:55

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

Ардуино - это такой себе удобный конструктор, набор для обучения, который состоит из микроконтроллера с минимально необходимой обвязкой + разнообразные платы расширения + удобная интегрированная среда разработки (IDE) для языка C++ (используется компилятор AVR-GCC) с набором разных подготовленных констант, макросов, библиотек и упрощений.
При разработке программ в среде Ардуино оболочка прячет от пользователя много разной нужной "черновой" работы, многое делается "за кулисами" чтобы обезопасить пользователя от ошибки и упростить процесс написания программы.
Возможно что для полных новичков Arduino будет более подходящим вариантом для знакомства чем изучение голого AVR + Asm/Си, поскольку последняя связка может немного спугнуть человека без опыта какого-либо программирования и разработки устройств, показаться очень сложной в работе и в понимании ее структуры.

+1 

Добавить комментарий captcha