Вторник, 23.04.2024, 14:18
Приветствую Вас Гость | RSS
Главная | Каталог статей | Регистрация | Вход
Меню сайта
Реклама Google
Форма входа
Категории раздела
Это нужно знать! [17]
Изучаем AVR [30]
Программаторы [12]
Необходимое ПО [8]
Готовые устройства [73]
Справочная [38]
Инструмент [0]
Технология [8]
Литература [0]
Arduino скетчи [18]
Поиск
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Микроконтроллеры - это просто!
Главная » Статьи » Изучаем AVR

Программирование микроконтроллеров в AtmelStudio 6. Часть 2

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

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

Вспомните, первая электрическая вычислительная машина была сделана на простых реле.
Эти переключения происходят по командам, которые мы даем. Самый простейший вариант команды – это тумблер. Включили тумблер – замкнется реле, выключили тумблер – разомкнется. Допустим у нас 8 реле, к которым подключены лампочки, и есть 8 тумблеров. Можно включить любую в любой момент. Захотелось мне сделать бегущий огонек, и буду я последовательно включать и выключать каждый тумблер. Так вот последовательность включения тумблеров и есть программа. Эта последовательность команд определяется только вами. Но, ведь так неудобно и очень медленно. Можно ускорить работу, если применить перфорированные карты. Помните, были такие бумажки с дырками. Можно эту бумажку протаскивать между расположенными в ряд контактами. Там где дырка, там и контакт. А если возникает контакт, то включается реле, которая зажигает лампочку. Таким образом, можно запрограммировать последовательность включения лампочек. В современных микроконтроллерах такие переключения происходят в миллионы раз быстрее, а вот принципы не поменялись. В микроконтроллерах команды записываются в виде конкретных чисел, определяемых производителем, но мы, люди, привыкли говорить словами, и обозначать наши действия символами, а не числами. Вот и были созданы аббревиатуры – сокращения от основных слов, каждое из которых означает какую-то команду. Команда для микроконтроллера - это определенное число, и оно конкретно для данного вида контроллера. Конечно - же есть переводчики, которые переводят аббревиатуры в цифры, их называют макропроцессором языка. 

Для повышения функциональности микроконтроллеров производители стали внедрять готовые функциональные блоки. Например, такое устройство как АЦП (аналогово-цифровой преобразователь) и ШИМ (широтно-импульсная модуляция) или USART (модуль последовательной передачи данных). Входы и выходы этих электронных модулей привязаны к определенным выводом микроконтроллера и подключаются с помощью определенных команд (как реле). На какие выводы подключаются эти устройства, хорошо видно на схематических изображениях микроконтроллера в даташите. Процесс включения, настройки модулей и выводов микроконтроллера называется инициализацией устройства. В микроконтроллере может быть достаточно много встроенных модулей, для каждого из них есть соответствующие команды. 
Таким образом, микроконтроллеры разных производителей работают по одинаковым принципам, отличаются лишь структурой внутренней памяти, системой команд (аббревиатурой и их количеством), разрядностью, наличием встроенных устройств и быстродействием.
Изучив один тип микроконтроллеров, достаточно просто начать работать с другими.
Из всего сказанного можно сделать вывод, что микроконтроллер, по сути своей, простой коммутатор, где последовательность переключений определяется программой, которую создает программист.

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

При составлении алгоритма вы должны придерживаться нескольких обязательных правил:
Дискретность (прерывность, раздельность) – алгоритм должен представлять процесс решения задачи как последовательное выполнение простых (или ранее определенных) шагов. Каждое действие, предусмотренное алгоритмом, исполняется только после того, как закончилось исполнение предыдущего.
Определенность – каждое правило алгоритма должно быть четким, однозначным и не оставлять места для произвола. Благодаря этому свойству выполнение алгоритма носит механический характер и не требует никаких дополнительных указаний или сведений о решаемой задаче.
Результативность (конечность) – алгоритм должен приводить к решению задачи за конечное число шагов.
Массовость – алгоритм решения задачи разрабатывается в общем виде, то есть, он должен быть применим для некоторого класса задач, различающихся только исходными данными. При этом исходные данные могут выбираться из некоторой области, которая называется областью применимости алгоритма. 

На рисунке представлены основные структурные элементы алгоритмов. Как видите их немного, всего 8. 

Для того чтобы составить алгоритм программы, необходимо из условия выделить те величины, которые будут входными данными и четко сформулировать, какие именно результаты должны получиться. Другими словами, условие задачи требуется сформулировать в виде “Что имеем ... Что с этим данными требуется сделать” – это называется - Постановка задачи.

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

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

А теперь вернемся к практическому применению алгоритмов. Попробуем написать программу, где мы будем включать и выключать всего один светодиод. Начнем с технического задания и постановки задачи. Не путайте эти два разных понятия. Техническое задание более объемное понятие, включающее все исходные условия, на основе которых необходимо получить конечные заданные параметры. Например: имеется микроконтроллер ATMEGA8, кнопка, гасящий резистор на 200 Ом, светодиод, источник питания 5 вольт. Задание: составить принципиальную схему, где программно, последовательным нажатием на кнопку нужно включать или выключать светодиод (оговариваются, номера контактов, типоразмеры деталей, разводка печатной платы и еще множество технических параметров… ). В техническое задание входит постановка задачи. Например: при установке единичного состояния на входе 0 порта PB программа возвращается в начало, при сбросе входа 0 порта PB, программа инвертирует значение выхода 7 порта PB. На основе поставленной задачи составляем алгоритм будущей программы. Так как решений поставленной задачи может быть несколько, то и алгоритмов может быть несколько.

Один из алгоритмов может выглядеть таким образом.


Как видно, никаких сложных или запутанных структур здесь нет, все логично и представлено простыми элементами. Теперь можно приступить к самой программе.
Любая программа начинается с описания. Описание может быть произвольным, как вам нравится, но желательно указать какой микроконтроллер используется, его тактовую частоту, особенности программы и её функции. Нередко в описании указывается имя автора программы, время, когда была написана программа. Описание программы называют “шапкой”. Описание выполняется в виде комментариев и начинается звездочкой с косой чертой в конце - /* [Текст] */ . Текст может занимать несколько строк. Если текст занимают одну строчку, то перед комментариями ставится двойными косыми черточками - //[Текст] 

Так будет выглядеть программа на языке СИ. 

/*
 * Project.c Включение и выключение светодиода при нажатии на кнопку.
 * Author: Galrad
 * ATMEGA8 16MGz
 */ 

#define F_CPU 16000000UL         // указываем частоту в герцах
#include <avr/io.h>                    // стандартная библиотека ввода-вывода

int main(void)
{
    DDRB &=~(1<<0);         // Порт PB0 на вход
    PORTB |=(1<<0);         // Подтягивающий резистор к порту PB0
    DDRB |=(1<<7);         // Порт PB7 на вывод
    
while(1)
    {
    while(PINB & (1<<0)){}              //Бесконечный цикл если PB0=1
    PORTB ^= (1<<7);                //Инверсия бита PB7
    while ((PINB & (1<<0)) == 0){}  //Бесконечный цикл если PB0=0            
    }
    return 0;                                   //конец программы
}


В начале программы указаны название проекта, функции выполняемые программой, используемый процессор и его тактовая частота.
После шапки прописаны директивы. 
Директива – это предписание компилятору языка (ассемблер, СИ или другому языку программирования) выполнить то или иное действие в момент компиляции. В свою очередь – компиляция - это перевод написанной программы в машинный код и запись в виде специального файла. Если файл не формируется, а происходит простой перевод, то такой процесс называется трансляцией. Запомните, что директивы не транслируются в код, они указывают каким образом этот код формировать. 
Директивы начинаются с решетки, лишь затем идет ее название. Мы их рассмотрим в процессе изучения программирования.
В нашей программе встречаются директивы: 
#Define – директива замены. Везде, где будет встречаться выражение F_CPU, будет подставлена частота 16000000UL
#Include – директива вставки файла или библиотеки, устанавливает библиотеку ввода-вывода io.h (расширение h - хедер файл), который содержит описание адресов регистров, векторов прерываний, имен битов регистров микроконтроллера ATMega8
Любая программа, написанная на языке Си, должна иметь следующий минимальный фрагмент текста, который называют функцией main: 

int main(void)
{    
while(1) { }
}

У функции main есть заголовок – int main(void) и тело – оно ограниченно фигурными скобками {}. В тело функции прописывается основная программа.
В Си часто говорят о возвращаемом значении. Дело в том, что в любой функции происходит дейстсвия связанные с изменением переменных, например сложение, вычитание, сопоставление определенных значений, математические вычисления. Результатом подобных действий и будет возвращаемое значение функции. Если заведомо не нужно ничего возвращать, то прописывается слово void (пустой).
Перед именем функции main указывается тип возвращаемого (вычисляемого) значения int, которому соответствует целое 2-х байтное число с диапазоном значений от – 32768 до 32767.
После имени функции в скобках () указываются параметры, которые определяют условия работы функции при ее вызове. Если параметров нет – используется ключевое слово void
Для программирования микроконтроллеров, внутри тела функции main, обязательно прописывается бесконечный цикл while(1) {}, у которого так же есть заголовок и тело. Единица в параметрах означает, что значения цикла всегда истина и цикл будет выполнятся бесконечно т.е.как только основная программа дойдет до конца, она не зависнет, а начнется с начала. В теле цикла и будет наш код.
В Си нередко для завершения выполнения функции используется ключевое слово return. 

Далее следует инициализация портов микроконтроллера. Инициализация нужна для того, чтобы подготовить к работе необходимые разъемы микроконтроллера или активировать встроенные устройства. В нашем случае, мы разъем порта PB0 включаем на вход и подсоединяем к нему встроенный (называется подтягивающий) резистор, который подключен к плюсу источника питания контроллера. А разъем PB7 включаем на выход. 

Рассмотрим подробнее, как можно управлять портами.
Чтобы можно было управлять работой микроконтроллера, включать определенные устройства устанавливать их режим работы, и в частности портами, существует специальная область памяти, где есть множество ячеек, представляющие из себя единичные биты т.е. в каждую ячейку можно прописать или 0 или 1. Каждая ячейка определяет конкретную функцию, которую выполнит контроллер. Чем сложнее микроконтроллер, тем больше управляющих ячеек, и тем сложнее их запомнить.
Чтобы упростить управление микроконтроллером, разработчики решили систематизировать биты в функциональные группы по 8 бит. Получились 8 битные регистры, которые расположены один за другим, имеющие каждый свое название. Чтобы стало понятно, создадим произвольную таблицу, где каждая строка состоит из 8 ячеек и имеет свой порядковый номер, соответствующий одному регистру. Пусть наша таблица состоит из 128 ячеек или из 16 восьмибитных регистров.

Таким же образом, произвольно создадим три регистра: DdrB, PinB, PortB, с помощью которых будем управлять портами микроконтроллера.
Регистр DdrB (DdrB0….DdrB7) будет переключать соответствующий порт в два режима: ввод и вывод. В режиме ввода можно будет считывать состояние на разъемах контроллера и сохранять значения портов в регистр PinB (PinB0….PinB7) , а в режиме вывода выдавать в порт данные из регистров 
PortB(PortB0….PortB7).
Следующая схема показывает принцип работы портов. Если упрощенно, то регистр DdrX переключает выводы контроллера между регистрами PinX и PortX как контактами реле.

По такому же принципу сгруппированы в регистры управления и другие встроенные устройства микроконтроллера. Таким образом, управлять микроконтроллером становиться логично и очень просто. Регистры, которые управляют встроенными устройствами, называются – регистрами ввода – вывода, и распологаются в оперативной статической памяти контроллера - SRAM.

Таблица регистров ввода-вывода для микроконтроллера ATMEGA8 представлена ниже. Если присмотреться, то видно, что регистры портов расположены по адресам 0х10…0х18 и ничем не отличаются от тех, которые мы привели в качестве примера.

По умолчанию, все порты микроконтроллера находятся в режиме ввода. Для того, чтобы включить 7 порт в режим вывода нужно записать в регистр общего назначения число 0b10000000 в двоичном дредставлении или 0х80 в шестнадцетиричном или 128 в деятичном . Регистрами общего назначения называются первые 32 регистра (R0….R31) оперативной памяти – SRAM, из них для обмена данными чаще всего пользуются (R16…R31).

Команда DDRB &=~(1<<0); выставляет PB0 в режим ввода, прописывая в DDRB0 значение 0.К биту PB0 в режиме ввода можно подключить подтягивающий резистор, если выполнить команду PORTB |=(1<<0). Прописав в PORTB0 логическую 1 командой DDRB |=(1<<7); включаем PB7 на вывод.

Таким образом, мы подготовили выводы микроконтроллера для считывания и вывода информации.
Программа имеет всего 3 строчки, соответствующие 3 блокам нашего алгоритма:    
1.    while(PINB & (1<<0)){} Программа зацикливается, если кнопка не нажата, т.е. на выводе PB0 присутствует логическая 1.
2.    PORTB ^= (1<<7) Инверсия сигнала на выводе PB7.
3.    while ((PINB & (1<<0)) == 0){} Программа зацикливается, если кнопка нажата, т.е. на выводе PB0 присутствует логическая 0.

Возможно, сейчас не совсем понятно, что это за символы и их функциональное назначение. Итак, все по порядку. Это логические побитовые операции. С их помощью можно любую ножку микроконтроллера установить в логическую 1 или 0, а так же включать любую ножку микроконтроллера в режим ввода или вывода. Рассмотрим их подробно.

Двоичное побитовое отрицание НЕ (NOT) – называется инверсия. Меняет на противоположенные, разряды двоичного числа. Обозначается знаком тильда ~

Побитовое И (AND) – обозначается знаком амперсанд - &, называется конъюнкция и выполняется по следующей таблице:

Операцию & используют для тестирования состояния 
двоичного разряда числа - определения его содержимого. Для этого выполняют операцию И над числом а и вторым числом b, которое должно содержать 1 в интересующем разряде и 0 в остальных. Если в результате операции получится 0, то и в интересующем разряде находится 0, в противном случае 1.

Если число находится в переменной (a), то выражение для выполнения тестирования третьего разряда в этой переменной, на языке Си будет выглядеть следующим образом: (a & 8) == 0
(Разряды считаются справа налево от 0 разряда до 7, соответствующие степени числа 2)
Эта операция используется для сброса двоичного разряда числа в 0.
Для этого необходимо выполнить операцию И над числом 
и вторым числом, которое должно содержать в интересующем разряде 0 в остальных 1. Это число может быть получено операцией НЕ. 

Если необходимо в числе, находящемся в переменной (a), в четвертый двоичный разряд записать 0, то это может быть сделано следующим выражением: a = a & 0xEF ; // или 
a = a & ~16 ; 


Побитовое ИЛИ (OR) – обозначается знаком в виде вертикальной черты |, называется дизъюнкция, выполняется по следующей таблице: 

Эта операция применяется для установки двоичного разряда числа в 1. Для этого выполняют операцию ИЛИ над числом и вторым числом, которое должно содержать 1 в интересующем разряде и 0 в остальных.

Если необходимо в числе, находящемся в переменной (a), в четвертый двоичный разряд записать 1, то это может быть сделано следующим выражением: a = a | 16 ; 

Побитовое ИСКЛЮЧАЮЩЕЕ ИЛИ (XOR) – обозначается знаком циркумфлекс ^, Таблица для этой операции следующая: 

Позволяет восстановить одно из чисел, участвовавших в операции, если известно второе число и результат. Допустим, что известен результат операции 10110011 и одно из чисел, над которым выполнялась операция 11011001. Тогда, выполнив операцию ИСКЛЮЧАЮЩЕЕ ИЛИ над результатом и одним из чисел, получим второе число. 

Используя это свойство можно осуществить обмен содержимым двух переменных без применения дополнительной переменной. Ниже показана реализация этого алгоритма для переменных a и b. 
a = a ^ b ; //В переменной a получится результат операции. 
b = a ^ b ; //Операция над результатом и числом b даст число a 
a = a ^ b ; //Операция над результатом и числом a даст число b 

<< Cдвиг двоичных разрядов числа влево. 

>> Cдвиг двоичных разрядов числа вправо. 

Второй операнд у этих операции должен указывать, на сколько позиций сдвигать разряды. Те разряды, которые выходят за рамки числа, теряются, а вновь прибывающие приходят с нулями при сдвиге влево. При сдвиге вправо знакового числа, если оно отрицательное, происходит заполнение числа единицами, для сохранения знака. 
Сдвиговые операции можно использовать для формирования чисел, необходимых для тестирования и установки разрядов числа. Например, для того, что бы получить число для тестирования разряда с номером n, можно воспользоваться выражением 1 << n. 
Операция сдвига числа влево на n разрядов равносильна умножению этого числа на 2n, а сдвиг числа вправо на n разрядов равносилен делению числа на 2n. Поскольку сдвиговые операции выполняются в процессоре быстрее арифметических операций, то их часто используют для реализации умножения или деления числа на 2n. 
Операция присваивания – обозначается знаком =.
= простое присваивание. Операция присваивания всегда имеет результат. На языке Си можно встретить следующее предложение: 
a = b = c = d = 0 ; 
в результате которого всем переменным присваивается одно и то же значение. Кроме простого присваивания в Си существуют сложные операции присваивания, которые позволяют сократить запись выражений типа
a = a + b, где одна и та же переменная стоит справа и слева от операции присваивания и представить их в виде a += b. 
Все сложные операции присваивания показаны ниже: 
Для арифметических операций 
+= сложение с присваиванием 
-= вычитание с присваиванием 
*= умножение с присваиванием 
/= деление с присваиванием 
%= присваивание остатка от деления 
Для побитовых операций 
<<= сдвиг влево с присваиванием 
>>= сдвиг вправо с присваиванием 
&= побитовое И с присваиванием 
|= побитовое ИЛИ с присваиванием 
^= ИСКЛЮЧАЮЩЕЕ ИЛИ с присваиванием. 
Все операции присваивания – двухоперандные и требуют, чтобы левым операндом была переменная, значение которой они изменяют. Если тип правого операнда не совпадает с левым, правый операнд будет преобразован к типу левого. Результатом любой операции присваивания будет число, занесенное в переменную, стоящую слева от операции. 

Рассмотрим эту же программу на языке Ассемблер.
Программа соответствующая нашему алгоритму будет выглядеть таким образом:


/*
 * Project.asm
 * Включение и выключение светодиода при нажатии на кнопку.
  *  Author: Galrad
* ATMEGA8 16MGz
 */ 
 ; ==================================================

        .include "m8def.inc"            ; Присоединение файла описаний

; =================================================== Регистры общего назначения
        .def temp = r16                    ; Определение рабочего регистра temp                                  
; ===================================================
        .cseg                            ; Выбор сегмента программного кода
        .org 0                            ; Установка текущего адреса 
; =================================================== Инициализация портов
        ldi temp, (1<<PB0)                ; прописываем 0 бит порта В
        out PortB, temp                    ; включает подтягивающий резистор 0 бита порта В
        ldi temp, (1<<PB7)                ; прописываем 7 бит порта В
        out DDRB,temp                    ; выставляем 7 бит порта В на вывод                
; =================================================== Начало программы
loop_1:
        sbic PinB, 0                    ; опрашиваем 0 бит порта В
        rjmp loop_1                        ; возврат на начало - метку loop_1, если  0 бит порта В выставлен

        in temp,PortB                      ; чтение порта
        subi temp,0x80                    ; инвертирование бита с помощью функции вычитания 1 байтного числа
        out PortB,temp                    ; запись в порт 
loop_2:
        sbis PinB, 0                    ; опрашиваем 0 бит порта В
        rjmp loop_2                        ; возврат на метку loop_2: если 0 бит порта В обнулен
        rjmp loop_1                        ; возврат на начало - метку loop_1:


В ассемблере директивы имеют те же назначения, что и в СИ, но начинаются они не с решетки а с точки.
.INCLUDE "имя_файла" – директива вставки файлов – в программу будет вставлен файл с указанным именем. Содержимое этого файла будет откомпилировано полностью или до директивы .EXIT которая указывает место выхода из файла. В нашей программе мы вставляем файл, который описывает нумерацию контактов, встроенные устройства и дополнительные функции микроконтроллера ATmega 8.
.DEF Символическое_имя = Регистр назначить регистру символическое имя. В нашем случае символическим именем temp мы назвали регистр r16. Далее в программе имя temp всегда будет означать регистр r16.
.CSEG – программный сегмент памяти. Определяет начало программного сегмента с помощью директивы .ORG. 
.ORG выражение установить положение в сегменте. Если выбран всего один программный сегмент, и предполагается что сегмент начинается с нулевой строки то эти директивы можно не прописывать. Возможно использование нескольких программных сегментов, тогда каждому сегменту нужно указать, с какого номера строки он начинается. Строка в программном сегменте имеет длину одного слова (2 байта).
Более подробно и в качестве постоянного справочника рекомендую учебное пособие Зубарева Александра Александровича по ассемблеру для микроконтроллеров AVR.

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

Категория: Изучаем AVR | Добавил: Alex (13.11.2014)
Просмотров: 4750 | Рейтинг: 5.0/2
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Copyright MyCorp © 2024