Работа с отдельными битами

В МК для задания режимов работы периферийных устройств и управления ими используются специальные регистры, каждый разряд которых привязан к конкретному устройству и задание соответствующих функций определяется содержимым этого разряда. В подобных случаях для манипуляций с битами применяют рассмотренные выше побитовые операции, при этом в качестве второго операнда чаще всего используется переменная, имеющая 1 на месте значимых бит. Остальные незначащие биты должны быть невидимыми (замаскированными) для операций установкой 0. Поэтому указанную переменную для операций с битами часто называют маской.

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

Запись единицы в заданный разряд с обнулением остальных разрядов

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

Unsigned char a, b;

a = 0x10; // a = 0b00010000

b = 0x30; // b = 0b00110000

Альтернативный и часто используемый программистами вариант основан на применении оператора сдвига влево:

Var = 1 << Num;

Пример:

Unsigned char a;

a = 1 << 4; // a = 0b00010000

Его легко расширить для записи в разряды переменной нескольких единиц с обнулением остальных разрядов:

Var = (1<<Num1)|(1<<Num2)|...|(1<<NumN);

Пример:

Unsigned char a;

a = (1 << 5)|(1 << 4); // a = 0b00110000

Запись единицы в заданный разряд без изменения остальных разрядов

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

Unsigned char a, mask;

mask = 0x30; // mask = 0b00110000

a = 0x03; // a = 0b00000011

a = a | mask; // a = 0b00110011

Альтернативный вариант − применение оператора сдвига

Var = Var | (1 << Num);

или в укороченной форме записи

Var |= (1 << Num);

Модификация этого способа для записи нескольких логических единиц без изменения остальных разрядов выглядит так:

Var |= (1<<Num1)|(1<<Num2)|...|(1<<NumN);

Пример:

Unsigned char a;

a = 0x03; // a = 0b00000011

a |= (1 << 4)|(1 << 5); // a = 0b00110011

Запись нуля в заданный разряд без изменения остальных разрядов

Для записи нуля в некоторый разряд без изменения ос­тальных разрядов можно воспользоваться оператором:

Var = Var & ~(1 << Num);

Эта запись равнозначна следующей:

Var &= ~(1 << Num);

Запись нескольких нулей без изменения остальных разрядов:

Var &= ~((1<<Num1)|(1<<Num2)|...|(1<<NumN));

Пример:

Unsigned char a;

a = 0x87; // a = 0b10000111

a &= ~((1 << 1)|(1 << 2)); // a = 0b10000001

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

Пример:

Unsigned char a, mask;

mask = 0xF9; // mask = 0b11111001

a = 0x87; // a = 0b10000111

a = a & mask; // a = 0b10000001

Инвертирование отдельных битов

Для инвертирования содержимого в заданном разряде целочисленной переменной используется операция поразрядного исключающего «ИЛИ» с помощью оператора:

Var ^= (1 << Num);

Инверсия нескольких разрядов без изменения остальных:

Var ^= (1<<Num1)|(1<<Num2)|...|(1<<NumN);

Пример:

Unsigned char a, b;

a = 0x87; // a = 0b10000111

a ^= (1 << 7); // a = 0b00000111

b = 0x87; // b = 0b10000111

b ^= (1 << 7)|(1 << 1); // b = 0b00000101

Проверка значения бита в переменной

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

if ((Var & (1<<Num)) != 0 ){

// блок выполнится, если бит Num переменной Var установлен

}

if ((Var & (1<<Num)) == 0 ){
// блок выполнится, если бит Num переменной Var сброшен

}

Обмен частей переменной

Одной из типовых операций преобразования данных является обмен тетрад в байте, когда четыре младших бита помещаются на место старших, а четыре старших бита – на место младших.

Обменять тетрады любой беззнаковой байтовой переменной можно при помощи двух операций сдвига:

Var = (Var << 4)|(Var >> 4);

В первой операции сдвига биты младшей тетрады перемещаются в старшую, а младшая тетрада при сдвиге обнуляется. Во второй операции сдвига старшая тетрада перемещается на место младшей. Затем операция поразрядного «ИЛИ» объединяет байты со сдвинутыми тетрадами.

Использование макросов

Директива #define служит для замены часто использующихся в программе констант, операторов или выражений некоторыми идентификаторами. Идентификаторы, заменяющие текстовые или числовые константы, называют именованными константами, а идентификаторы, заменяющие фрагменты программ, называют макроопределениями. Директива #define имеет две синтаксические формы:

#define идентификатор текст

#define идентификатор(список параметров) текст

Эта директива заменяет все последующие вхождения идентификатора в программе на текст.

Пример:

#define MIN 10

#define MAX (MIN+20)

Эти директивы заменят в тексте программы каждое слово MIN на число 10, а каждое слово MAX на выражение (10+20) вместе с окружающими его скобками. Скобки, содержащиеся в макроопределении, позволяют избежать ошибок, связанных с порядком вычисления операций. Например, при отсутствии скобок выражение a = MAX*5 будет преобразовано в выражение a = 10+20*7, а не в выражение a = (10+20)*7, как это получается при наличии скобок.

При разработке компиляторов данная директива чрезвычайно широко используется для привязки адресов аппаратных ресурсов МК к их символическим именам, заданным в технической документации на МК. Если заглянуть, например, в любой заголовочный файл конкретного МК, то можно увидеть, что большая его часть состоит их директив #define. Использование заданных таким образом символических имен портов, регистров и даже отдельных разрядов, позволяет резко упростить написание программ.

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

Например, если в проекте при подключении клавиатуры использовался порт PORTA, то можно задать привязку этого подключения так

#define P_KEY PORTA

Тогда в дальнейшем в программе можно использовать символическое имя P_KEY. Если по каким-то причинам возникнет необходимость изменить порт подключения клавиатуры, то сделать это в программе нужно будет только в одном месте – в директиве #define, а не искать места его использования по всей программе.

В директиве можно использовать и выражения, например

#define MASK ((1 << 1)|(1 << 2))

Макросы создаются с использованием второй синтаксической формы директивы #define. При идентификаторе макроса имеется список формальных параметров, заключенных в скобки и разделенных запятыми. Формальные параметры в тексте макроопределения отмечают позиции, в которые должны быть подставлены фактические аргументы макровызова.

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

Пример:

#define MAX(x,y) (((x)>(y))?(x):(y))

Эта директива заменит фрагмент

a = MAX(i,f);

на фрагмент

a = (((i)>(f)?(i):(f));

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

При программировании МК наиболее часто применяются макросы, основанные на использовании рассмотренных выше битовых операций. Рассмотрим самые распространенные из них.

Макрос установки заданного бита в переменной или регистре:

#define BitSet(var, bit) ((var) |= (1<<(bit)))

Пример:

BitSet(PORTA,5); // установить 5-й бит PORTА

Макрос очистки заданного бита в переменной или регистре:

#define BitClr(var, bit) ((var) &= (~(1<<(bit))))

Пример:

BitClr(PORTC,4); // очистить 4-й бит PORTC

Макрос инверсии заданного бита в переменной или регистре:

#define BitInv(var, bit) ((var) ^= (1<<(bit))

Пример:

BitInv(PORTB,1); // инвертировать 1-й бит PORTB

Макрос возвращает 1, если заданный бит в переменной или регистре равен 1:

#define BitIsSet(var, bit) ((var & (1<<bit)) != 0)

Пример:

if(BitIsSet(PORTB,1)){Блок оператора if};

Макрос возвращает 1, если заданный бит в переменной или регистре равен 0:

#define BitIsClr(var, bit) ((var & (1<<bit)) == 0)

Пример:

if(BitIsClr(PORTB,1)){Блок оператора if};

Данные макросы можно поместить в заголовочный файл (например, macros.h) и использовать его в своих программах. При появлении новых макросов можно легко расширять библиотеку, помещая их в этот файл.

Наши рекомендации