Меню Рубрики

Установка бита в числе

Как установить, сбросить, проверить нужный бит или битовые операции

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

Независимо от того какие микроконтроллеры Вы собираетесь программировать, первое что придётся освоить — это битовые операции.
Битовых операций в языке Си всего 6.

Начнем с того, что выводы микроконтроллера условно разделены на порты, у Atmega16 порт состоит из 8 выводов, у STM32f103 из 16 выводов.

Установить в 1 нулевой бит порта B можно следующим образом.

Таким образом, мы установили нулевой бит в 1, а все остальные в 0, то есть мы переопределили все биты порта. А что если мы хотим установить в 1 только нулевой бит и не задеть остальные? В таком случае нужно воспользоваться побитовым ИЛИ.

В результате мы изменили только нулевой бит порта.
Надо отметить, что в микроконтроллерах счёт начинается с нуля, то есть первый бит будет иметь нулевой порядковый номер, а порядковый номер восьмого бита будет 7.

Битовая операция НЕ — изменяет значение бита на противоположное.

Эта операция совместно с битовым НЕ может использоваться для сброса конкретного бита в ноль.

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

В итоге мы выставили в 0 только первый бит.

Также эту операцию можно использовать для проверки чему равен бит. Например, нам надо проверить чему равен нулевой бит порта B, это можно сделать с помощью следующей конструкции.

Если бит равен единице, выражение в скобках будет правда, иначе — ложь.

Побитовое исключающее ИЛИ — если сумма соответствующих битов число чётное, результирующий бит 0, иначе 1.

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

Также с помощью этой операции можно определить равенство регистров. Например, мы хотим сравнить в одинаковом ли состоянии находятся порты B и D.

Если результат равен нулю, то содержимое регистров равно.

Логический сдвиг влево — все разряды при этом сдвигаются на одну позицию влево, самый левый бит теряется, а в самый правый бит записывается 0.

Операция логического сдвига влево эквивалентна умножению на 2.
0b0000 1011 = 11
0b0001 0110 = 22

Логический сдвиг вправо — все разряды при этом сдвигаются на одну позицию вправо, самый правый бит теряется, а в самый левый бит записывается 0.

источник

Установка бита в числе

Операции с битами

Данный урок посвящён битовым операциям (операциям с битами, битовой математике, bitmath), из него вы узнаете, как оперировать с битами – элементарными ячейками памяти микроконтроллера. Мы уже сталкивались с битовыми операциями в уроке про регистры микроконтроллера, сейчас рассмотрим всё максимально подробно. Данная тема является одной из самых сложных для понимания в рамках данного курса уроков, так что давайте разберёмся, зачем вообще нужно уметь работать с битами:

  • Гибкая и быстрая работа напрямую с регистрами микроконтроллера (в том числе для написания библиотек)
  • Более эффективное хранение данных (упаковка нескольких значений в одну переменную и распаковка обратно)
  • Хранение символов и другой информации для матричных дисплеев (упаковка в один байт)
  • Максимально быстрые вычисления
  • Работа со сдвиговыми регистрами и другими подобными железками
  • Разбор чужого кода

Данный урок основан на оригинальном уроке по битовым операциям от Arduino, можете почитать его здесь – там всё описано чуть более подробно.

Двоичная система и хранение данных

В начале цикла уроков (в уроке о типах данных) мы уже разбирали тему систем исчисления и важность двоичной системы. Давайте коротко вспомним, как это всё работает.

Минимальная ячейка памяти, которую мы можем изменить – бит, он принимает всего два значения: 0 и 1. Минимальная ячейка памяти, к которой мы можем обратиться (которая имеет адрес в памяти) – байт, байт состоит из 8-ми бит, каждый занимает свою ячейку (примечание: в других архитектурах в байте может быть больше или меньше бит, в данном уроке речь идёт об AVR и 8-ми битном байте). Таким образом, байт – это элементарный блок памяти, к которому мы можем обратиться и читать/записывать данные, самый младший тип данных в Arduino так и называется – byte.

Читайте также:  Установка внешней видеокарты плату встроенной

Обратившись к байту, мы можем манипулировать битами, из которых он состоит, именно для этого и используются операции с битами. Если мы “включим” все биты в байте, то получится число 0b11111111 в двоичной системе, или 255 в десятичной. Здесь нужно вспомнить важность степени двойки – на ней в битовых операциях завязано абсолютно всё. Давайте посмотрим на первые 8 степеней двойки (начиная с 0):

2 в степени
DEC BIN
1 0b00000001
1 2 0b00000010
2 4 0b00000100
3 8 0b00001000
4 16 0b00010000
5 32 0b00100000
6 64 0b01000000
7 128 0b10000000

Таким образом, степень двойки явно “указывает” на номер бита в байте, считая справа налево (примечание: в других архитектурах может быть иначе). Напомню, что абсолютно неважно, в какой системе исчисления вы работаете – микроконтроллеру всё равно и он во всём видит единицы и нули. Если “сложить” полный байт в десятичном представлении битов, то мы получим как раз 255: 128+64+32+16+8+4+2+1 = 255. Нетрудно догадаться, что число 0b11000000 равно 128+64, то есть 192. Именно таким образом и получается весь диапазон от 0 до 255, который умещается в один байт. Если взять два байта – будет всё то же самое, просто ячеек будет 16, то же самое для 4 байт – 32 ячейки с единицами и нулями, каждая имеет свой номер согласно степени двойки.

Давайте начнём манипуляции с битами с самого простого – с макро-функций, которые идут “в комплекте” с ядром Arduino.

Макросы для манипуляций с битами

В “библиотеке” Arduino.h есть несколько удобных макросов, которые позволяют включать и выключать биты в байте:

Макросы Arduino.h Действие
bitRead(value, bit) Читает бит под номером bit в числе value
bitSet(value, bit) Включает (ставит 1) бит под номером bit в числе value
bitClear(value, bit) Выключает (ставит 0) бит под номером bit в числе value
bitWrite(value, bit, bitvalue) Ставит бит под номером bit в состояние bitvalue (0 или 1) в числе value
bit(bit) Возвращает 2 в степени bit
Другие встроенные макросы
_BV(bit) Возвращает 2 в степени bit
bit_is_set(value, bit) Проверка на включенность (1) бита bit в числе value
bit_is_clear(value, bit) Проверка на выключенность (0) бита bit в числе value

Есть ещё макрос _BV, который сидит в других файлах ядра и в целом является стандартным макросом для других платформ и компиляторов, он делает то же самое, что bit(b) – возвращает 2 в степени b

Также в ядре Arduino есть ещё два макроса для проверки состояния бита в байте, bit_is_set() и bit_is_clear(), их удобно использовать для условных конструкций с if.

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

Битовые операции

Переходим к более сложным вещам. На самом деле они максимально просты для микроконтроллера, настолько просты, что выполняются за один такт. При частоте 16 МГц (большинство плат Arduino) одна операция занимает 0.0625 микросекунды.

Битовое И

И (AND), оно же “логическое умножение”, выполняется оператором & и возвращает следующее:

Основное применение операции И – битовая маска. Позволяет “взять” из байта только указанные биты:

То есть при помощи & мы взяли из байта 0b11001100 только биты 10000111, а именно – 0b11001100, и получили 0b10000100

Также можно использовать составной оператор &=

Битовое ИЛИ

ИЛИ (OR), оно же “логическое сложение”, выполняется оператором | и возвращает следующее:

Читайте также:  Установка перемычки на блок питания

Основное применение операции ИЛИ – установка бита в байте:

Также можно использовать составной оператор |=

Вы уже поняли, что указывать на нужные биты можно любым удобным способом: в бинарном виде (0b00000001 – нулевой бит), в десятичном виде (16 – четвёртый бит) или при помощи макросов bit или _BV (bit(7) даёт 128 или 0b10000000, _BV(7) делает то же самое)

Битовое НЕ

Битовая операция НЕ (NOT) выполняется оператором

Также она может инвертировать байт:

Битовое исключающее ИЛИ

Битовая операция исключающее ИЛИ (XOR) выполняется оператором ^ и делает следующее:

Данная операция обычно используется для инвертирования состояния отдельного бита:

То есть мы взяли бит №7 в байте 0b11001100 и перевернули его в 0, получилось 0b1001100, остальные биты не трогали.

Битовый сдвиг

Битовый сдвиг – очень мощный оператор, позволяет буквально “двигать” биты в байте вправо и влево при помощи операторов >> и >= и

Вспомним пример из пункта про битовое ИЛИ, про установку нужного бита. Вот эти варианты кода делают одно и то же:

Как насчёт установки нескольких бит сразу?

Или прицельного выключения бит? Тут чуть по-другому, используя &= и

Выключить несколько бит сразу? Пожалуйста!

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

Вернёмся к устройству Ардуиновских макросов:

Я думаю, комментарии излишни: макросы состоят из тех же элементарных битовых операций и сдвигов!

Быстрые вычисления

Как я уже говорил, битовые операции – самые быстрые операции, выполняются за один такт процессора. При помощи знания битовых операций можно делать некоторые вещи гораздо быстрее. Но так ли это? Можно разделить число на 2 в степени, например разделим число 3651 на 8:

Операция деления – очень медленная, но на степень двойки она выполняется максимально быстро для МК – за один такт. Особо усердствовать с такой оптимизацией не стоит – компилятор сам оптимизирует вычисления перед загрузкой кода в МК: если мы напишем 3651 / 8, оно само превратится в 3651 >>= 3 на этапе компиляции. Деление на другие числа компилятор тоже может оптимизировать – вычесть до степени двойки, разделить “сдвигом” и прибавить остаток. Но деление именно на степень двойки будет быстрее, так как нет лишних действий.

Экономия памяти

При помощи битовых операций можно экономить немного памяти, пакуя данные в блоки. Например, переменная типа boolean занимает в памяти 8 бит, хотя принимает только 0 и 1. В один байт можно запаковать 8 логических переменных, например вот так:

источник

_______________ сдвиг влево unsigned char tmp = 3; //0b00000011
tmp = tmp //теперь в переменной tmp число 6 или 0b00000110

tmp = tmp //теперь в переменной tmp число 48 или 0b00110000

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

tmp = 7; //0b00000111
tmp //сокращенный вариант записи
//теперь в переменной tmp число 28 или 0b00011100

Операция сдвига влево на n разрядов эквивалентна умножению переменной на 2 n .

_______________ сдвиг вправо >> _______________

Пример для беззнаковой переменной

unsigned char tmp = 255; //0b11111111
tmp = tmp >> 1;
//теперь в переменной tmp число 127 или 0b01111111

tmp >>= 3; //сокращенный вариант записи
//теперь в переменной tmp число 15 или 0b00001111

Пример для переменной знакового типа

int tmp = 3400; //0b0000110101001000
tmp >>= 2;
//теперь в переменной число 850 или 0b0000001101010010

tmp = -1200; //0b1111101101010000
tmp >>= 2;
//теперь в tmp число -300 или 0b1111111011010100
//видите — два старших разряда заполнились единицами

Операция сдвига вправо на n разрядов эквивалентна делению на 2 n . При этом есть некоторые нюансы. Если потерянные младшие разряды содержали единицы, то результат подобного “деления” получается грубоватым.

Например 9/4 = 2,5 а 9>>2 (1001>>2) равно 2
11/4 = 2,75 а 11>>2 (1011>>2) равно 2
28/4 = 7 а 28>>2 (11100>>2) равно 7

_______________поразрядная инверсия

tmp;
//теперь в переменной tmp число 161 или 0b10100001

tmp;
//теперь в tmp снова число 94 или 0b01011110

Читайте также:  Установка процессора на мат плату

_______________ поразрядное ИЛИ | ______________

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

tmp = 155
tmp = tmp | 4; //устанавливаем в единицу второй бит переменной tmp

155 0b10011 0 11
|
4 0b00000 1 00
159 0b10011 1 11

Использовать десятичные числа для установки битов довольно неудобно. Гораздо удобнее это делать с помощью операции сдвига влево //устанавливаем в единицу четвертый бит переменной tmp

Установить несколько битов в единицу можно так

tmp = tmp | (1 //устанавливаем в единицу седьмой, пятый и нулевой биты переменной tmp

С помощью составного оператора присваивания |= можно сделать запись компактней.

tmp |= (1 //обнуляем третий бит переменной tmp

155 0b1001 1 011
&
247 0b1111 0 111
147 0b1001 0 011

Видите, третий бит стал равен 0, а остальные биты не изменились.

Обнулять биты, используя десятичные цифры, неудобно. Но можно облегчить себе жизнь, воспользовавшись операторами //обнуляем третий бит

(1 1 011 & 0b1111 0 111
результат 0b1001 0 011

Обнулить несколько битов можно так

((1 //обнуляем третий, пятый и шестой биты

Используя составной оператор присваивания &= ,можно записать выражение более компактно

((1 if ((tmp & (1 // блок будет выполняться, только если установлен
// второй бит переменной tmp
>

if ((tmp & (1 // блок будет выполняться, только если не установлен
// второй бит переменной tmp
>

_______________побитовое исключающее ИЛИ ^ _______________

Оператор ^ осуществляет операцию логического исключающего ИЛИ между соответствующими битами двух операндов. Результатом операции логического исключающего ИЛИ будет 0 в случае равенства битов. Во всех остальных случаях результат будет 1. Это проиллюстрировано в табице истинности.

tmp = 155;
tmp = tmp ^ 8; // инвертируем четвертый бит переменой tmp

155 0b1001 1 011
^
8 0b0000 1 000
147 0b1001 0 011

Четвертый бит изменил свое значение на противоположное, а остальные биты остались без изменений.

tmp = tmp ^ 8; // опять инвертируем четвертый бит переменой tmp

147 0b1001 0 011
^
8 0b000 0 1 000
155 0b1001 1 011

Видите, четвертый бит снова изменил свое значение на противоположное.

Так записывать выражение намного удобнее

tmp = tmp ^ (1 / инвертируем третий бит переменой tmp

А так и удобно и компактно

tmp ^= (1 //инвертируем четверый бит

Можно инвертировать несколько битов одновременно

tmp ^= ((1 //инвертируем 4,2 и 1 биты

tmp = var1;
var1 = var2;
var2 = tmp;

Но используя оператор ^ переставить значения можно так:

var1 ^= var 2;
var 2 ^= var 1;
var 1 ^= var 2;

Чистая магия, хотя, честно говоря, я ни разу не пользовался таким приемом.

________________Директива #define__________________

//порт, к которому подключены кнопки
#define PORT_BUTTON PORTA
#define PIN_BUTTON PINA
#define DDRX_BUTTON DDRA

//выводы, к которым подключены кнопки
#define DOWN 3
#define CANCEL 4
#define UP 5
#define ENTER 6

int main()
<
//конфигурируем порт на вход,
//и включаем подтягивающие резисторы
DDRX_BUTTON = 0;
PORT_BUTTON = 0xff;

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

#define MASK_BUTTONS ((1 #define не жалейте скобок чтобы четко задать последовательность вычисления выражений!

Некоторые выражения можно замаскировать под «функции».

Можно использовать многострочные определения, используя в конце каждой строки символ \

#define INIT_Timer() TIMSK = (1 #define – это задание макроопределений (или просто макросов). Вот как с помощью #define можно задать макросы для рассмотренных ранее операций с битами

#define SetBit(reg, bit) reg |= (1 #define ClearBit(reg, bit) reg &= (

(1 #define InvBit(reg, bit) reg ^= (1 #define BitIsSet(reg, bit) ((reg & (1 #define BitIsClear(reg, bit) ((reg & (1

пример использования:

SetBit(PORTB, 0); //установить нулевой бит порта B
InvBit(tmp,6); //инвертировать шестой бит переменной tmp

Определим макрос, вычисляющий квадрат числа:

выражение
tmp = SQUARE(my_var);
даст корректный результат.

А что будет если в качестве аргумента макроопределения использовать выражение my_var+1

Препроцессор заменит эту строчку на

а это вовсе не тот результат, который мы ожидаем.

Чтобы избежать таких ошибок не скупитесь на скобки при объявлении макросов!

выражение
tmp = SQUARE(my_var +1);
даст корректный результат, потому что препроцессор заменит эту строчку на
tmp = ((my_var + 1) * (my_var +1));

источник

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