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

Отменить подписку
Популярные публикации
Интересный опрос
Что вас больше всего интересует?

Заработок в интернете
Радиоэлектроника
Программирование
Операционные системы
Информационная безопасность
Саморазвитие, лайфхак
Спорт
Другое
Поблагодарить автора
donate
1B4ZZ2PXUd9E7AqEB82AqFnkpk2bt5hQG7

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

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

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

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

Мы знаем что один байт представляет собою 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

В файле констант и определений в библиотеке avr-libc для каждого типа микроконтроллера указаны значения для констант PD0, PD1, PB5, PC4, и другие. Например для вывода значений всех констант из IO-файла для микроконтроллера ATmega8 и где встречается сочетание "PD" (ищем константы для порта D), достаточно выполнить команду:

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

Получим вот такой результат:

#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

Теперь при использовании константы PD0 вы знаете что в ней содержится число 0, а в PD1 - 1 и т.д.

 

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

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

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

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

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

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

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

Для числа 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), двойка в 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:

  • PORTB |= ( 1 << 2 ) | ( 1 << 6 );
  • PORTB |= ( 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-й степени. Выполним инверсию битов:

0010 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-й бит установлен в 0. Для этого выполним сдвиг числа 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)

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

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

Для проверки содержимого бита в регистре на ноль (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-го бита (канал PD2) в регистре порта PORTD:

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

Здесь мы выражение "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.

 (5/5) голосов: 2   просмотров: 864


Тематика:  AVR  Си  Бит

Комментарии к публикации (7)
папа 1 папа 
31 Октябрь 2016 20:28

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

PORTB |= ( 1 << 2 ) | ( 1 << 6 );

+1 
Дмитрий 2 Дмитрий 
16 Февраль 2017 10:57

В таблице раздела Битовые операторы в языке Си в последнем столбце ошибочка, или как?

0 
ph0en1x 3 ph0en1x 
16 Февраль 2017 16:02

Дмитрий, спасибо за внимательность. Ошибка исправлена.

0 
ph0en1x 4 ph0en1x 
27 Февраль 2017 14:26

Недавно получил такое письмо:

Прочёл вашу статью. Но на других сайтах встретил такие записи:
MCUCR=(0<<ISC00)|(1<<ISC01);
DDRB |= ((1<<PB2)|(1<<PB0));                  
TCCR0A=(1<<COM0A1) | (1<<WGM00);
 SREG|= (1<<7);
DDRD  = (0 << PD2);
Так всё таки ноль можно писать? И чем отличается выражение имеющий
знак "|" от выражения не имеющего знак "|"?

Знак "|" - это логическая операция ИЛИ (OR).
К примеру, записи ниже эквивалентны между собою:
"SREG |= (1<<7);" - то же самое что и "SREG = SREG | (1<<7);"
Допустим что SREG содержит значение 01110001, вот что получится:
1 = 00000001 (в двоичном представлении)
00000001 << 7 = 10000000 (число 128)
01110001 | 10000000 = 11110001
Таким образом, мы выполнили установку восьмого(старшего) бита в регистре SREG (регистр состояния микроконтроллера) чем разрешили использование перерываний в МК.

Теперь рассмотрим вот эту запись:
DDRD  = (0 << PD2);
Здесь выполняется логический сдвиг числа 0 влево на 2 позиции (PD2=2, смотрим значение для своего МК).
00000000 << 2 = 00000000
Приведенную выше запись можно использовать для наглядности, хотя по сути она эквивалентна:
DDRD  = 0

Следующая конструкция нужна для установки бит ISCxx в регистре MCUCR (настройка прерываний):
MCUCR = (0<<ISC00) | (1<<ISC01);
Она эквивалентна вот этим конструкциям:
MCUCR = 0 | (1<<ISC01);
MCUCR = (1<<ISC01);
В первом варианте выражение "0<<ISC00" используется для наглядности, так более понятно что и как установлено в регистре.

0 
VB99 5 VB99 
04 Март 2017 09:40

Хочу выразить Вам огромную благодарность!

Как для новичка, то все очень доступно и понятно изложено.

Спасибо за Ваш труд!

+1 
Ivan 6 Ivan 
14 Март 2017 19:06

«В языке Си все числа что равны или больше 1 являются логической истиной (True), а 0 - логической ложью (False).»
Исправьте , так как в Си, все числа что не равны 0 (-1,-5,1,5,150…) являются логической истиной (True), а 0 — логической ложью (False)

+1 
ph0en1x 7 ph0en1x 
14 Март 2017 22:28

Ivan, благодарю за хорошее замечание! Исправил.

+1 

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