Установка битов в
регистре порта
Сделаем установку 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).
В языке Си все числа что равны или больше 1 являются логической истиной
(True), а 0 - логической ложью (False).
Поэтому, результат нашего выражения - число 4 является логической истиной
(True), а это значит что 3-й разряд регистра PORTD содержит единицу. Вот как
будет выглядеть данное выражение на языке Си:
Установка битов в
регистре порта
PORTB = PORTB | 32;
PORTB = PORTB | (1 << 5);
PORTB = PORTB | (1 << PB5);
PORTB |= (1 << PB5);
PORTB |= ( 1 << 2 ) | ( 1 << 6 );
PORTB |= ( 1 << PD1 ) | ( 1 << PD5 );
Сброс битов в регистре
порта
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 ) );
Проверка разрядов
регистра
PORTD & (1 << 2)
|
Такое выражение можно использовать в условных операторах (if) и операторах
циклов (wgile), например:
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);
PORTD = PORTD ^ 32;
PORTD = PORTD ^ (1<< 5);
PORTD = PORTD ^ (1<< PD5);
PORTD ^=(1<< PD5);
Как и в предыдущих примерах по установке и сбросу битов, последняя краткая
конструкция является наиболее простой и понятной для использования.
Операции побитового сдвига
Операций сдвига две – битовый сдвиг влево (оператор <<) и битовый сдвиг вправо (оператор >>). Битовый сдвиг вправо сдвигает биты числа вправо, дописывая слева нули. Битовый сдвиг влево делает противоположное: сдвигает биты влево, дописывая справа нули. Вышедшие за пределы числа биты отбрасываются.
Например, сдвиг числа 5 влево на 2 позиции
00000101 << 2 == 00010100
Сдвиг числа 19 вправо на 3 позиции
00010011 >> 3 == 00000010
Независимо от архитектуры (big-endian, или little-endian, или middle-endian) числа в двоичном виде представляются слева направо, от более значащего бита к менее значащему. Побитовый сдвиг принимает два операнда – число, над которым необходимо произвести сдвиг, и число бит, на которое необходимо произвести сдвиг.
int a = 12; printf ( "%d << 1 == %d\n" , a, a << 1); printf ( "%d << 2 == %d\n" , a, a << 2); printf ( "%d >> 1 == %d\n" , a, a >> 1); printf ( "%d >> 2 == %d\n" , a, a >> 2); |
Так как сдвиг вправо (>>) дописывает слева нули, то для целых чисел операция равносильна целочисленному делению пополам, а сдвиг влево умножению на 2. Произвести битовый сдвиг для числа с плавающей точкой без явного приведения типа нельзя. Это вызвано тем, что для си не определено представление числа с плавающей точкой. Однако можно переместить число типа float в int, затем сдвинуть и вернуть обратно
float b = 10.0f; float c = ( float ) (*((unsigned int *)&b) >> 2); printf ( "%.3f >> 2 = %.3f" , b, c); |
Но мы, конечно же, получим не 5.0f, а совершенно другое число.
Особенностью операторов сдвига является то, что они могут по-разному вести себя с числами со знаком и без знака, в зависимости от компилятора. Действительно, отрицательное число обычно содержит один бит знака. Когда мы будем производить сдвиг влево, он может пропасть, число станет положительным. Однако, компилятор может сделать так, что сдвиг останется знакопостоянным и будет проходить по другим правилам. То же самое и для сдвига вправо.
unsigned int ua = 12; signed int sa = -11; printf ( "ua = %d, ua >> 2 = %d\n" , ua, ua >> 2); printf ( "sa = %d, sa >> 2 = %d\n" , sa, sa >> 2); printf ( "(unsigned) sa = %u, sa >> 2 = %u\n" , sa, sa >> 2); printf ( "sa = %d, ((unsigned) sa) >> 2 = %d" , sa, ((unsigned) sa) >> 2); |
В данном случае при первом сдвиге всё работает, как и задумано, потому что число без знака. Во втором случае компилятор VSE2013 оставляет знак. Однако если посмотреть на представление этого числа, как беззнакового, сдвиг происходит по другим правилам, с сохранением самого левого бита. В последней строчке, если привести число со знаком к числу без знака, то произойдёт обычный сдвиг, и мы получим в результате положительное число.
Побитовые операторы и операторы сдвига не изменяют значения числа, возвращая новое. Они также как и арифметические операторы, могут входить в состав сложного присваивания
int a = 10; int b = 1; a >>= 3; a ^= (b << 3); |
Примеры
1. Напишем функции, которые позволяют определять и изменять определённый бит числа
Для того, чтобы узнать, какой бит (1 или 0) стоит на позиции n, воспользуемся логическим умножением.
Пусть имеется число 9
00001001
Нужно узнать, выставлен ли бит на позиции 3 (начиная с нуля). Для этого умножим его на число, у которого все биты равны нулю, кроме третьего:
00001001 & 00001000 = 00001000
Теперь узнаем значение бита в позиции 6
00001001 & 01000000 = 00000000
Таким образом, если мы получаем ответ, равный нулю, то на искомой позиции находится ноль, иначе единица. Чтобы получить число, состоящее из нулей с одним битом на нужной позиции, сдвинем 1 на нужное число бит влево.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| #include <stdio.h> #include <conio.h> #include <limits.h> int checkbit( const int value, const int position) { int result; if ((value & (1 << position)) == 0) { result = 0; } else { result = 1; } return result; } void main() { int a = 3; size_t len = sizeof ( int ) * CHAR_BIT; size_t i; for (i = 0; i < len; i++) { printf ( "%d" , checkbit(a, i)); } _getch(); } |
Заметьте, что в функции условие записано так
(value & (1 << position)) == 0 |
Потому что без скобок сначала будет вычислено равенство нулю и только потом выполнено умножение.
value & (1 << position) == 0 |
Функцию можно упростить
int checkbit( const int value, const int position) { return ((value & (1 << position)) != 0); } |
Функция, которая выставляет бит на n-й позиции в единицу.
Известно, что логическое сложение любого бита с 1 будет равно 1. Так что для установки n-го бита нужно логически сложить число с таким, у которого все биты, кроме нужного, равны нулю. Как получить такое число, уже рассмотрено.
int setbit( const int value, const int position) { return (value | (1 << position)); } |
Функция, которая устанавливает бит на n-й позиции в ноль.
Для этого нужно, чтобы все биты числа, кроме n-го, не изменились. Умножим число на такое, у которого все биты равны единице, кроме бита под номером n. Например
0001011 & 1110111 = 0000011
Чтобы получить такую маску, сначала создадим число с нулями и одной единицей, а потом инвертируем его.
int unsetbit( const int value, const int position) { return (value & ~(1 << position)); } |
Функция, изменющая значение n-го бита на противоположное.
Для этого воспользуемся функцией исключающего или: применим операцию XOR к числу, которое состоит из одних нулей и одной единицы на месте нужного бита.
int switchbit( const int value, const int position) { return (value ^ (1 << position)); } |
Проверка
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| #include <stdio.h> #include <conio.h> #include <limits.h> int checkbit( const int value, const int position) { return ((value & (1 << position)) != 0); } int setbit( const int value, const int position) { return (value | (1 << position)); } int unsetbit( const int value, const int position) { return (value & ~(1 << position)); } int switchbit( const int value, const int position) { return (value ^ (1 << position)); } void printbits( int n) { //CHAR_BIT опеределён в библиотеке limits.h //и хранит число бит в байте для данной платформы size_t len = sizeof ( int )* CHAR_BIT; size_t i; for (i = 0; i < len; i++) { printf ( "%d" , checkbit(n, i)); } printf ( "\n" ); } void main() { int a = 3; size_t len = sizeof ( int ) * CHAR_BIT; size_t i; printbits(a); a = setbit(a, 5); printbits(a); a = unsetbit(a, 5); printbits(a); a = switchbit(a, 11); printbits(a); a = switchbit(a, 11); printbits(a); _getch(); } |
Комментариев нет:
Отправить комментарий
ваше мнение...