Работа с битами в регистре PORTD контроллера AVR на С.

Работа с регистрами AVR микроконтроллера на Си, битовые операции

Показаны принципы работы с отдельными битами регистра порта в AVR микроконтроллере. Подробно рассмотрены битовые операции и операции сдвига битов в языке Си. Приведены примеры установки и сброса битов в регистре порта, чтение состояния битов и их инверсии.

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

Содержание:

  1. Структура байта
  2. Порты, байты и биты
  3. Операции битового сдвига
  4. Битовые операторы в языке Си
  5. Установка битов в регистре порта
  6. Сброс битов в регистре порта
  7. Проверка разрядов регистра
  8. Инверсия состояния бита в регистре
  9. Заключение

Структура байта

Мы знаем что один байт представляет собою 8 бит, а каждый бит это — 1 или 0, биты в байте считаются справа налево. Бит 1 является младшим, а бит 8 — старшим.

1 Байт
8 (старший бит) 7 6 5 4 3 2 1 (младший бит)

Порты, байты и биты

Представьте себе на минуточку, что регистр порта в микроконтроллере — это аппаратная панель, на которой расположены один за другим восемь одинаковых переключателей, каждый из которых может иметь два состояния: включен (1) или выключен (0).

В языке Си для AVR,  установка значения 1 для бита в порте — это как перевод нужного переключателя в состояние «включено». На выходе канала для указанного порта, к которому подвязан наш виртуальный выключатель, появится высокий уровень, а это в свою очередь подаст напряжение на какое-то устройство, например на светодиод, который сразу же засветится.

Названия каналов в порте микроконтроллера отсчитываются с нуля (0). Ниже приведен пример битовой структуры порта PORTD:

Регистр порта PORTD, 1 байт
Номер бита в регистре 8 7 6 5 4 3 2 1
Канал порта PD7 PD6 PD5 PD4 PD3 PD2 PD1 PD0

Аналогично по структуре выглядят и другие порты — PORTA, PORTB, DDRD, PINA…

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

Например, чтобы вывести на экран значения всех констант из IO-файла для микроконтроллера ATmega8, содержащих сочетание символов «PD» (ищем константы для порта D, PORTD), достаточно выполнить следующую команду:

cat /usr/lib/avr/include/avr/iom8.h | grep PD
Bash

Увидим следующий результат:

#define SPDR    _SFR_IO8(0x0F)
#define PD7      7
#define PD6      6
#define PD5      5
#define PD4      4
#define PD3      3
#define PD2      2
#define PD1      1
#define PD0      0
None

Теперь, при использовании константы PD0 в своей программе на Си, вы знаете что в ней содержится число 0, а в PD3 — 3 и т.д.

Операции битового сдвига

А сейчас, давайте подробно и с примерами разберемся с тем, как работают операторы битового сдвига.

Существует несколько разновидностей операций битового сдвига:

  • логический (сдвинутые в направлении биты теряются, а освободившиеся позиции заполняются нулями);
  • арифметический (сдвиг влево аналогичен логическому, а при сдвиге вправо — свободные позиции заполняются значениями крайнего левого бита, который еще называют знаковым);
  • циклический (потерянные с одной стороны биты перемещаются на освободившиеся позиции с другой, как замкнутое кольцо).

Операторы битового сдвига в языке программирования Си обозначаются как «>>» и «<<» и выполняют логический сдвиг битов в используемой переменной в указанном направлении и на указанное число элементов.

Важный нюанс: при сдвиге вправо («>>») битов в числе с отрицательным знаком (signed) выполняется арифметический сдвиг — освободившиеся позиции слева заполняются единичками (перенос знака). Это важно помнить!

Сокращенные обозначения:

  • Bin — от слова Binary, двичная система счисления;
  • Dec — от слова Decimal, десятичная система счисления;
  • Hex — от слова Hexadecimal, шестнадцатиричная система счисления.

Для примера выполним сдвиги битов в разных числах, предварительно представив их в двоичном виде.

Для числа 1 (Dec, в десятичной системе 1) — 00000001 (Bin, в двоичной системе 0b00000001):

  • 1 << 0 = 1 (00000001);
  • 1 << 1 = 2 (00000010);
  • 1 << 2 = 4 (00000100)
  • 1 << 5 = 32 (00100000);
  • 1 >> 2 = 0 (00000000).

Сдвиг влево на один разряд выполняет умножение числа на 2, а сдвиг вправо — деление числа на 2.

Для числа 209 (Dec, в десятичной системе 209) — 11010001 (Bin, в двоичной системе 0b11010001):

  • 209 = 11010001;
  • 209 << 3 = 136 (10001000);
  • 209 << 5 = 32 (00100000);
  • 209 >> 5 = 6 (00000110)

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

Битовые операторы в языке Си

То как двигать биты в байте мы теперь знаем, дальше разберемся с битовыми операторами в Си:

  • «&» (логическое И, AND) или умножение — бинарная операция, результат которой равен 1 только в том случае если оба операнда равны 1, в противном случае будем иметь 0;
  • «|» (логическое ИЛИ, OR) или сложение — бинарная операция, результат которой равен 1 в том случае если хотя бы один из операндов равен 1;
  • «~» (логическое НЕ) или инверсия — унарная операция, результат которой равен 0 если операнд равен 1, и наоборот — результат равен 1, если операнд равен 0;
  • «^» (исключающее ИЛИ, XOR) — бинарная операция, результат которой равен 1 в том случае если только один из двух операндов равен 1.

Рассмотрим примеры битовых операций над числами 209, 7 и их битовыми представлениями:

1101 0001 (209)
&

0000 0111 (7)
—————-
0000 0001 (1)

1101 0001 (209)
|
0000 0111 (7)
—————-
1101 0111 (215)
1101 0001(209)
~
—————-
0010 1110(46)
1101 0001 (209)
^
0000 0111 (7)
—————-
1101 0110 (214)

Как видите, битовые операции позволяют установить или сбросить отдельные биты числа.

Установка битов в регистре порта

А теперь немного практики, давайте сделаем установку 6-го бита в регистре порта PORTB что в свою очередь установит высокий уровень для канала PB5 (6-й бит в регистре). Допустим что сейчас в регистре PORTB содержится число 136, которое в битовом представлении выглядит как 10001000 (высокий уровень на каналах PB7 и PB3).

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

Что такое битовая маска? — по сути это специально подготовленное число, состоящее из требуемой конфигурации битов, которое в сочетании с некоторой операцией над битами другого числа позволяет установить или сбросить биты в последнем.

Для получения битовой маски, при помощи которой позже будет установлен один бит, мы выполним левосторонний сдвиг битов числа 1 (00000001) на 5 разрядов:

0000 0001 (1)
<< 5
—————-
0010 0000 (32)

В результате битовой операции получим число 32 (00100000), это и есть наша битовая маска. Хочу заметить что это число равняется числу 2 в 5-й степени, каждый сдвиг разряда влево умножает результат на 2.

Теперь нам останется выполнить битовую операцию ИЛИ над текущим числом в регистре и получившимся числом-маской:

1000 1000 (136)
|
0010 0000 (32)
—————-
1010 1000 (168)

А теперь сравните состояние регистра перед операцией и после — все состояния битов сохранены и дополнительно установлен 6-й бит.

Для установки 6-го бита и последующей записи числа в регистр порта PORTB (установка высокого уровня для канала PB5) в нашем примере можно использовать любую из следующих конструкций операторов, они все выполняют идентичную задачу:

  • PORTB = PORTB | 32;
  • PORTB = PORTB | (1 << 5);
  • PORTB = PORTB | (1 << PB5);
  • PORTB |= (1 << PB5);

Наиболее удобно использовать последнюю краткую запись, где используется комбинирования операция логического ИЛИ и присвоения, в данном случае PB5. К примеру константа PB5 (канал 5 порта B, 6-й бит регистра) определена в файле /usr/lib/avr/include/avr/iom8.h для микроконтроллера ATmega8 и она равна числу 5.

Как установить несколько бит в регистре? — можно вызвать поочередно две конструкции с операторами, а можно все выполнить одной командой. Допустим нужно установить 2-й и 6-й биты в регистре порта PORTD, что соответствуют каналам PD1 и PD5:

  • PORTD |= ( 1 << 1 ) | ( 1 << 5 );
  • PORTD |= ( 1 << PD1 ) | ( 1 << PD5 );

Сброс битов в регистре порта

Для сброса разрядов в регистре порта мы будем использовать битовую операцию «&» (логическое «И»), которая применяется к двум битам (бинарная операция) и даёт единицу только в том случае если оба исходных бита имеют единичное значение, также нам пригодится битовая операция «~» (логическое «НЕ», инверсия).

Давайте выполним сброс 5-го бита в регистре порта PORTD, что в свою очередь выполнит установку низкого уровня на канале PD4. Допустим что сейчас в регистре PORTD содержится число 157, которое в битовом представлении выглядит как 10011101.

Для того чтобы сбросить 5-й бит (10011101) в регистре порта PORTD мы подготовим маску (как при установке битов), инвертируем ее биты «~», а потом выполним битовую операцию «&» над текущим значением регистра и полученной инвертированной маской.

Для подготовки маски выполним сдвиг битов на 4 разрядов в числе 1 (00000001).

0000 0001 (1)
<< 4
—————-
0001 0000 (16)

Маска готова, получили число 16 (00010000), 2 в 4-й степени. Выполним инверсию битов:

0001 0000 (16)
~
—————-
1110 1111 (239)

Готово, осталось применить маску к содержимому регистра порта PORTB используя битовую операцию «&»:

1001 1101 (157)
&
1110 1111 (239)
—————-
1000 1101 (141)

Теперь в содержимом регистра PORTD значение 5-го бита установлено в 0 с сохранением положений остальных бит. В языке Си данные операции можно выполнить используя любую из приведенных ниже, идентичных по результату команд:

  • PORTD = PORTD & ~ 16;
  • PORTD = PORTD & 239;
  • PORTD = PORTD & ~( 1 << 4 );
  • PORTD = PORTD & ~( 1 << PD4 );
  • PORTD &= ~( 1 << PD4 );

В данном случае наиболее удобной и информативной формой команды будет последний укороченный вариант. Для одновременного сброса нескольких битов регистра можно использовать вот такие конструкции из операторов:

  • PORTD = PORTD & ~( ( 1 << PD4 ) | ( 1 << PD6 ) );
  • PORTD &= ~( ( 1 << PD4 ) | ( 1 << PD6 ) );

Проверка разрядов регистра

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

Как проверить значение установленного бита в регистре на Си? — для этого нужно подобрать специальное выражение с использованием битовых операций, результатом работы которого будет значение: правда (True) или ложь (False). Имея булево (bool) значение выражения мы можем использовать для работы условные операторы языка Си.

Например нам нужно проверить есть ли единица (1) в 3-м бите регистра PORTD, тем самым мы проверим есть ли высокий уровень на канале PD2 порта PORTD. Примем что текущее значение регистра — 10010101 (149).

Для проверки используем выражение, которое состоит из битовой маски с установленным битом для проверки и проверяемого регистра, к которым применен битовый оператор «&» (логическое И).

Готовым маску, в которой только 3-й бит установлен в 1. Для этого выполним сдвиг битов числа 1 на 2 разряда влево:

0000 0001 (1)
<< 2
—————-
0000 0100 (4)

Теперь применим битовую операцию «&» (логическое И) к содержимому регистра PORTD и получившейся маске:

1001 0101 (149)
&
0000 0100 (4)
—————-
0000 0100 (4)

В результате выражения получим число 4 (0000 0100).

В языке Си все числа которые НЕ равны «нулю» (-100, -5, 1, 500) являются логической истиной (True), а 0 — логической ложью (False).

Результат нашего выражения — число 4, которое является логической истиной (True), а это значит что 3-й разряд регистра PORTD содержит единицу.

Вот как будет выглядеть данное выражение из двух логических операций на языке Си:

PORTD & (1 << 2)
None

Такое выражение можно использовать в условных операторах (if) и операторах циклов (while), например:

while( PORTD & (1 << 2) ) { ... }
if( PORTD & (1 << PD2) ) { ... }
C

Для проверки состояния бита в регистре на ноль (0) используем такую же конструкцию, только к результату выражения применим логическую операцию инверсии «!» (логическое НЕ).

Логическая операция «!» переворачивает значение с правды (True) на ложь (False), и наоборот. В отличие от битовой операции инверсии, которая переворачивает биты с 1 на 0 и наоборот, логическая операция инверсии оперирует с логическими значениями: правда (True) на ложь (False).

1 = True 0 = False 122 = True (149 & (1 << 2)) = True
!1 = False !0 = True !(5-1) = False !(149 & (1 << 2)) = False

Пример выражения на языке Си для проверки на ноль (0) 3-го бита в регистре порта PORTD (канал PD2):

while( !(PORTD & (1 << 2)) ) { ... } 
if( !(PORTD & (1 << PD2)) ) { ... }
C

Здесь выражение «PORTD & (1 << 2)» берется в круглые дужки, что позволяет получить результат этого выражения, к которому потом и будет применен оператор логической инверсии.

Инверсия состояния бита в регистре

Иногда может понадобиться изменить состояние определенного бита в регистре на противоположное — выполнить инверсию состояния бита.

Для подобной операции отлично подходит битовый оператор «^« (исключающее ИЛИ, XOR). Чтобы выполнить инверсию определенного бита в регистре нужно создать маску, в которой этот бит установлен, а потом применить к содержимому регистра и полученной маске бинарный оператор «^«, потом останется записать полученный результат в регистр и готово.

Возьмем, к примеру, что нужно погасить светодиод, который подключен к каналу PD5 порта PORTD. Если светодиод светится то это значит что в на канале PD5 присутствует высокий уровень, соответственно это значит что в регистре порта PORTD бит под номером 6 (PD5 = 5, 6-й бит в байте регистра) установлен в 1. Допустим что содержимое регистра порта PORTD сейчас — 10111010 (число 186, 1 байт, 8 разрядов, 6-й разряд = 1).

Подготовим маску, для установки 6-го бита нам необходимо сдвинуть все биты числа 1 на 5 разрядов:

0000 0001 (1)
<< 5
—————-
0010 0000 (32)

Применим маску к содержимому регистра порта PORTD:

1011 1010 (186)
^
0010 0000 (32)
—————-
1001 1010 (154)

Как видите, 6-й бит в байте регистра, который раньше был 1, сейчас установлен в 0 (1001 1010). Теперь осталось записать число в регистр порта и задачу можно считать выполненной. Примеры использования такой конструкции на языке Си:

  • PORTD = PORTD ^ 32;
  • PORTD = PORTD ^ (1<< 5);
  • PORTD = PORTD ^ (1<< PD5);
  • PORTD ^=(1<< PD5);

Как и в предыдущих примерах по установке и сбросу битов, последняя краткая конструкция является наиболее простой и понятной для использования.

Заключение

С первого взгляда очень просто запутаться в операторах и значениях таких как «&», «!», «PD1», «>>» и других, но один раз хорошо разобравшись и попробовав на практике вы всегда будете иметь понятие что и как работает, откуда берется и что содержит.

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

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