Микроконтроллеры в системах управления. лабораторный практикум. Болдырев И.А., Герасимов М.И
.pdfЛабораторная работа № 4
УПРАВЛЕНИЕ МИКРОКОНТРОЛЛЕРОМ НА ЯЗЫКЕ СИ
Цель работы – изучение функционирования системы управления исполнительного уровня на микроконтроллерах серии ATmega, языка программирования С, составление программ и их отладка в составе аппаратно-программного комплекса.
Теоретические сведения
Общая характеристика языка Си Язык программирования Си относится к языкам высокого
уровня, не привязанным к конкретной элементной базе компьютера. Благодаря гибкости, выразительности и компактности своих конструкций Си и его версии (С++, С#) завоевали максимальную популярность в среде профессиональных программистов и прикладных пользователей.
Язык Си представляет собой удачный компромисс между стремлением воспользоваться теми возможностями, которые обычно предоставляют программисту языки высокого уровня, и необходимостью эффективно использовать особенности компьютера. Кроме набора средств, присущих современным языкам программирования высокого уровня (структурность, модульность, определяемые типы данных), в него включены средства для программирования, близкие к уровню ассемблера
– использование указателей, побитовые операции, операции сдвига, – а также непосредственные ассемблерные вставки типа asm volatile("nop"). Обширный набор операторов позволяет писать компактные и эффективные программы. Однако, такие мощные средства требуют от программиста внимательности, аккуратности и хорошего знания языка со всеми его преимуществами и недостатками, а также «поведения» транслятора
51
(см. ниже). При этом ответственность за корректность программ полностью ложится на программиста.
Язык Си относится к типу компилирующихся, т.е. текст программы на Си, подготовленный с помощью текстового редактора, для получения объектного модуля обрабатывается компилятором, причем компиляция выполняется в два прохода. При первом проходе (претрансляции) обрабатываются строки-директивы, начинающиеся со знака #, при втором – транслируется текст программы и создается объектный (машинный) код. Для получения загрузочного (исполняемого) модуля компилятор реализует внешние связи объектного модуля
– подсоединяет к нему соответствующие библиотечные модули, заданные программистом. Следует учесть, что в целях получения максимально компактного исполняемого модуля программы и минимального времени ее выполнения компилятор оптимизирует ее, перекомпоновывая исходный текст программы, и может даже исключить некоторые фрагменты (например, пустые циклы в ожидании возникновения прерываний), обнаружив, что их результаты не влияют не последующий ход вычислений. Для сохранения в программе таких фрагментов (например, заготовок для последующего расширения возможностей программы) можно использовать работу с незадействуемыми регистрами ввода-вывода, а также защиту таких фрагментов ключевым словом volatile, используемым при объявлении переменной. Оно сообщает компилятору, что значение переменной может изменяться в любой момент извне, без какоголибо действия со стороны кода, который компилятор обнаруживает поблизости, соответственно исключение связанного с ней фрагмента недопустимо. Более подробно, с примерами, вопросы оптимизации кода компилятором и возможные неожиданности для программиста многократно рассмотрены и интернете (например, в [19])
В настоящее время имеется большое количество систем программирования на Си для разных типов компьютеров. Разработано множество библиотек модулей, инструментальных
52
средств разработки и отладки, облегчающих создание новых программ.
Базовые навыки программирования на Си студентами должны быть получены на предшествующих данной дисциплине курсах, однако следует повторить основные введения о следующих разделах: типы данных, константы, переменные, указатели, выражения, операторы, функции, а также о структуре программ. Для этого можно использовать сведения из многочисленных интернет-источников.
Напомним, что рекомендуемая структура программы имеет вид:
Заголовок программы (наименование, авторство и т.п.) Директивы компилятора – с префиксом «шарп», т.е. # Тело программы:
Функции
{
}
Прерывания
{
}
main()
{
инициализация;
Главный БЕСКОНЕЧНЫЙ цикл
{
собственно программа
}
}
Программирование микроконтроллеров на языке Си в среде разработки «AVRStudio»
Язык Си – это язык относительно "низкого уровня" среди высокоуровневых. Это означает, что Си оперирует с объекта-
53
ми того же вида, что и большинство команд ЭВМ, а именно, с символами, числами и адресами. Они могут обрабатываться посредством обычных арифметических и логических операций, осуществляемых реальными ЭВМ. Соответственно, Си активно используется при программировании микроконтроллеров и к его использованию дается доступ в соответствующих средах программирования, в частности в среде разработки
«AVR Studio» («Atmel Studio»), описанной выше.
Ниже рассмотрены процедуры управления МК AVR, связанные с управлением отдельными битами регистров этих МК. Как показано выше, периферийные устройства МК представляются процессору в виде совокупностей регистров, причём регистры данных используются для приема и передачи единых слов данных, а регистры состояния и управления – для восприятия отдельных битов и воздействия на биты, управляющие режимами устройств. Простейшим случаем регистров состояния и управления являются порты. Дальнейшее описание битовых операций приводится на примере битов какого-либо порта, однако наибольшее значение имеют операции с регистрами таймеров, АЦП и других сложных периферийных устройств МК.
Работа с регистрами AVR микроконтроллера на Си, битовые операции
Для записи и чтения отдельных битов в портах микроконтроллера необходимо научиться выполнять битовые операции, уметь использовать битовые маски и выполнять запись в порт.
Операции битового сдвига Существует несколько разновидностей операций битового
сдвига:
• логический (сдвинутые в направлении биты теряются, а освободившиеся позиции заполняются нулями);
54
•арифметический (сдвиг влево аналогичен логическому,
апри сдвиге вправо свободные позиции заполняются значениями крайнего левого бита, который еще называют знаковым);
•циклический (потерянные с одной стороны биты перемещаются на освободившиеся позиции с другой, как замкнутое кольцо).
Операторы битового сдвига в языке программирования Си обозначаются как ">>" и "<<" и выполняют логический сдвиг битов в используемой переменной в указанном направлении и на указанное число элементов. При этом существенно то, какой тип данных подвергается сдвигу, т.е. какова разрядность переменной. Ниже рассмотрены сдвиги в пределах байта (биты, сдвинутые за пределы байта, теряются).
Для примера выполним сдвиги битов в разных числах, предварительно представив их в двоичном виде.
Для десятичного числа 217 (в двоичной системе
0b11011001):
•217 = 11011001;
•217 << 3 = 200 (11011001000);
•217 << 5 = 32 (1101100100000);
•217 >> 5 = 6 (0000011011001).
Еще один пример. Для числа 1 (в двоичной системе
0b00000001):
•1 << 0 = 1 (00000001);
•1 << 1 = 2 (00000010);
•1 << 2 = 4 (00000100)
•1 << 5 = 32 (00100000);
•1 >> 2 = 0 (0000000001).
55
Видно, что сдвиг влево на один разряд выполняет умножение числа на 2, а сдвиг вправо – деление числа на 2 нацело. Далее будет использоваться сдвиг на задаваемое количество разрядов именно числа 1.
Битовые операторы в языке Си
Рассмотрим операторы Си, действующие побитно:
•"&" (логическое И, AND) или умножение – бинарная операция, результат которой равен 1 только в том случае, если оба операнда равны 1, в противном случае будем иметь 0;
•"|" (логическое ИЛИ, OR) или сложение – бинарная операция, результат которой равен 1 в том случае, если хотя бы один из операндов равен 1;
•"^" (исключающее ИЛИ, XOR) – бинарная операция, результат которой равен 1 в том случае, если только один из двух операндов равен 1;
•"~" (логическое НЕ) или инверсия – унарная операция, результат которой равен 0, если операнд равен 1, и наоборот – результат равен 1, если операнд равен 0.
Рассмотрим примеры битовых операций над двоичными представлениями двух чисел 217 и 7:
1101 1001 |
(217) |
1101 1001 |
(217) |
& |
|
~ |
|
0000 0111 |
(7) |
---------------- |
|
---------------- |
|
0010 0110 |
(38) |
0000 0001 |
(1) |
|
|
1101 0001 |
(217) |
1101 1001 (217) |
|
| |
|
^ |
|
0000 0111 |
(7) |
0000 0111 |
(7) |
---------------- |
|
---------------- |
|
1101 1111 |
(223) |
1101 1110 |
(222) |
Результаты можно интерпретировать так – битовые операции позволяют установить или сбросить отдельные биты байта.
56
Установка битов в регистре порта Для примера сделаем установку (т.е. перевод в состояние
1) в регистре порта PORTB для разряда PB5. Допустим, что сейчас в регистре PORTB содержится число 0b10001000 (высокий уровень на разрядах PB7 и PB3).
Чтобы установить PB5, будем использовать битовую операцию логического ИЛИ в комплексе с битовой маской. Для получения битовой маски, при помощи которой позже будет установлен один бит, мы выполним левосторонний сдвиг битов числа 1 (00000001) на 5 разрядов:
0000 0001 << 5
----------------
0010 0000
В результате битовой операции получим число
0b00100000.
Теперь сделаем битовую операцию ИЛИ (итак, для УСТАНОВКИ – ИЛИ) над текущим числом в регистре и получившимся числом-маской:
1000 1000
|
0010 0000
--------------
1010 1000
А теперь сравните состояние регистра перед операцией и после – все состояния битов сохранены и дополнительно установлен 5-й разряд.
Для установки высокого уровня для разряда PB5 и последующей записи числа в регистр порта PORTB в данном примере можно использовать любую из следующих конструкций операторов, они все выполняют идентичную задачу:
•PORTB = PORTB | 0b00100000;
•PORTB = PORTB | (1 << 5);
•PORTB = PORTB | (1 << PB5);
•PORTB |= (1 << PB5);
57
Наиболее удобно использовать последнюю краткую запись, где используется комбинирования операция логического ИЛИ и присвоения, в данном случае PB5. К примеру, константа PB5 (разряд 5 порта B) определена в файле
/usr/lib/avr/include/avr/iom16.h для микроконтроллера
ATmega16 и она равна числу 5.
Как установить несколько бит в регистре? Можно вызвать поочередно две конструкции с операторами, но можно выполнить всё одной командой. Допустим, нужно установить те биты в регистре порта PORTD, которые соответствуют разрядам PD1 и PD5. Можно использовать любую из следующих конструкций операторов:
•PORTB |= ( 1 << 1 ) | ( 1 << 5 );
•PORTB |= ( 1 << PD1 ) | ( 1 << PD5 );
Если следует установить один или несколько разрядов регистра в 1, а остальные в 0, то достаточно только присвоения, например,
• PORTD = 1<< PD2;
даст PORTD = 0b00000100 независимо от предшествующего значения в этом порте.
Сброс битов в регистре порта Для сброса разрядов в регистре порта будем использовать
битовую операцию "&" (логическое "И"), которая применяется к двум битам (бинарная операция) и даёт единицу только в том случае, если оба исходных бита имеют единичное значение, кроме того, потребуется использовать битовую унарную операцию "~" (логическое "НЕ", инверсия).
Выполним для примера сброс разряда PD4 в регистре порта PORTD. Допустим, что сейчас в регистре PORTD содержит-
ся число 0b10011101.
Для того чтобы сбросить заданный разряд порта PORTD, подготовим маску (как при установке битов), инвертируем ее биты "~", а потом выполним битовую операцию "&" над теку-
58
щим значением регистра и полученной инвертированной маской.
Для подготовки маски выполним сдвиг битов на 4 разря-
дов в числе 1 (00000001).
0000 0001 << 4
----------------
0001 0000
Маска готова, получили число 16 (00010000), 2 в 4-й степени. Выполним инверсию битов:
0001 0000
~
-------------
1110 1111
Осталось применить маску к содержимому регистра порта PORTB, используя битовую операцию "&":
1001 1101
&
1110 1111
-------------
1000 1101
Теперь в содержимом регистра PORTD значение PD4 установлено в 0 с сохранением состояний остальных бит. В языке Си данные операции можно выполнить, используя любую из приведенных ниже, идентичных по результату команд:
•PORTD = PORTD & ~( 1 << 4 );
•PORTD = PORTD & ~( 1 << PD4 );
•PORTD &= ~( 1 << PD4 );
Итак, для сброса следует применять НЕ-И В данном случае наиболее удобной и информативной
формой команды будет последний укороченный вариант. Для одновременного сброса нескольких битов регистра можно использовать вот такие конструкции из операторов:
• PORTD = PORTD & ~( ( 1 << PD4 ) | ( 1 << PD6 ) );
59
• PORTD &= ~( ( 1 << PD4 ) | ( 1 << PD6 ) ); – следите за скобками!
Проверка разрядов регистра Теперь рассмотрим, каким образом можно проверить раз-
ряды регистра на наличие в них 1 или 0. Это может потребоваться, если нужно получить значение битов в регистрах специального назначения (флагов) микропроцессора, а также для чтения состояния различных устройств и модулей, которые передают свое состояние, используя битовую структуру.
Как проверить значение определенного бита в регистре на Си? Для этого нужно подобрать специальное выражение с использованием битовых операций, результатом работы которого будет значение: правда (True) или ложь (False). Имея булево (bool) значение выражения, мы можем использовать для работы условные операторы языка Си.
Например, нам нужно проверить есть ли единица в разряде PD2 порта PORTD. Примем, что текущее значение регистра –
10010101.
Для проверки используем выражение, которое состоит из битовой маски с установленным битом для проверки и проверяемого регистра, к которым применен битовый оператор "&" (логическое И).
Готовим маску, в которой только разряд 2 установлен в 1. Для этого выполним сдвиг битов числа 1 на 2 разряда влево:
0000 0001 << 2
-------------
0000 0100
Теперь применим битовую операцию "&" (логическое И) к содержимому регистра PORTD и получившейся маске:
1001 0101
&
0000 0100
-------------
0000 0100
60