Данная статья со старого форума и была переведена мною когда-то, к сожалению источник оригинала уже утерян.
Простейшие вещи, как известно, создают большие проблемы
Когда я начинал работу со встраиваемыми системами мои знания о программировании были весьма элементарны, как в прочем и сейчас. Я часто использовал логические операторы такие как логическое НЕ (NOT), логическое ИЛИ (OR, ||), логическое И (AND, &&), но почти никогда не обращал внимание на битовые операторы.
В Embedded разработке, битовые операторы широко используются в элементарном взаимодействии программно-аппаратной части устройства при записи регистров. Обращение к регистрам настолько простое, что я потратил лишь пару дней на их изучение. Общение с моими друзьями, использующими Arduino, показало суть этой проблемы. А проблема эта заключается в отсутствии элементарных знаний информатики и электроники. Таким образом, в этой статье мы будем обсуждать нечто очевидное для специалистов и не совсем очевидное для начинающих.
Регистры
В цифровой электронике регистры это специальные структуры для хранения битов информации таким образом, чтобы система могла записать или считать весь регистр, т.е. все биты одновременно. Тем не менее регистры используются не только как буфер для хранения байт информации. Они могут быть использованы также как статусные регистры (изменение состояние регистра говорит о каком либо произошедшем событии), в качестве регистров ввода-вывода (типа GPIO) и для конфигурации определенных настроек.
Настройка периферийного устройства это всегда запись одного или нескольких бит в регистр. Тем не менее, различные библиотеки периферии зачастую инкапсулируют эти процедуры, скрывая их от программиста за уровнем абстракции. Практически всегда, если программисту необходим настроить периферию на какой-либо аппаратной платформе, он столкнется с записью конфигурационных бит в регистры. Длина регистров обычно равна длине слова (word) архитектуры процессора. В качестве примера рассмотрим регистр из микроконтроллера серии SAM:
По существу, конфигурирование данного регистра означает, что нам нужно собрать бит за битом целое значение в 32 бита, преобразовать это значение в десятичный или шестнадцатиричный вид для более короткой записи и записать его в регистр. Более того, если битам в регистре нужно поменять значение, то нам нужно считать данные из регистра, конвертировать их в бинарный вид, изменить нужные биты, снова конвертировать в короткую запись и записать значение в регистр. Рассмотрим следующий кусок кода:
addr = 0x40088030;
value = 147;
WriteRegister (addr, value);
Такая запись практически ни о чем не говорит, не ясно какая конфигурация была произведена. Более функционален следующий вид записи:
#define SPI_Chip_Select_REG 0x40088030
...
#define SPI_CSR_CPOL 0x01
#define SPI_CSR_ NCPHA 0x02
...
#define SPI_CSR_ BITS_0 0x10
...
#define SPI_CSR_ BITS_3 0x80
...
WriteRegister(SPI_Chip_Select_REG, SPI_CSR_BITS_3 | SPI_CSR_BITS_0 |
SPI_CSR_ NCPHA | SPI_CSR_CPOL);
Здесь мы записываем в биты СPOL и CPHA "1" и BITS в 1001 (или 9). Это одно из практических применений битовых операторов.
Побитовые и логические операторы
Языки без явно описанного логического типа данных, такие как С90 и Lisp, могут представлять значения истины в виде других данных. Например язык С использует целочисленный тип данных, где выражения, такие как i < j, логические выражения с операторами && и || всегда истинны, если результат равен 1 и ложны при 0. Если эти выражения часть операторов if, while, for, то истинно любое значение отличное от 0.
Любые логические операции и операторы с логическими типами данных всегда возвращают логическое значение 0 или 1, независимо от значений бит данных. Так 1001, 10000, 1, -5, -6 можно рассматривать как истинное значение и только 0 как ложное. !1001 и !(-2) возвращают 0, то есть являются ложными.
Учитывая предыдущий код, который мы написали:
SPI_CSR_BITS_3 | SPI_CSR_BITS_0 | SPI_CSR_ NCPHA | SPI_CSR_ CPOL
Равносильно этому:
0x80| 0x10| 0x02| 0x01
или
0b1000 0000 | 0b0001 0000 | 0b0000 0010 | 0b0000 0001
результатом будет 0b1001 0011
Используя побитовое ИЛИ (OR), мы можем устанавливать биты по одному в регистре. В следующем примере мы установим бит CPOL в 1, оставив другие без изменений:
Value = ReadRegister(SPI_Chip_Select_REG);
WriteRegister(SPI_Chip_Select_REG, Value | SPI_CSR_ CPOL);
Использую побитовое НЕ (NOT) и И (AND) мы можем устанавливать биты в 0. Рассмотрим пример:
0b1101 1111 & 0b1011 1111
Результатом операции станет значение 0b1001 1111. Кроме того второй оператор может быть получен в результате побитового НЕ (0b0100 0000). Это означает что с помощью этого оператора можно установить биты в 0, оставив без изменения другие.
Value = ReadRegister(SPI_Chip_Select_REG);
WriteRegister(SPI_Chip_Select_REG, Value & (~SPI_CSR_ CPOL));
В заключении можно сказать что битовые маски могут быть созданы с помощью операторов сдвига, так например 0х04 это результат операции 1<<3 или 0х0200 равносильно 1<<9.