Использование битовых масок в ESP32
Битовые маски используются, для того, чтобы можно было получить/передать множество параметров в одной переменной.
Общий принцип следующий: в целочисленной переменной, сохраняется значение параметров, которые могут принимать два значения, 0 или 1. Т.е. бит, отсюда и название.
Для сохранения и чтения параметров используют значения разряда в числе (еще его называют "словом).
Например 1001 - тут мы видим, что в первом (считается справа налево) и четвертом разряде хранится единица, а во втором и третьем - ноль.
В данном примере 1001 в двоичном счислении это цифра 9 в десятично счислении. Но хранится число в шестнадцатиричном коде.
1001(двоичный код) = 9 (десятичный код) = 0x9 (шестнадцатеричный код, где 0х -указывает, что число хранится в шестнадцатеричном коде).
Для работы с битами в C++ используются побитовые операции:
1001 >> 2 = 0010 (все в двоичном счислении, бывшие справа 01, просто отбрасываются, слева замещаются 0).
Сдвиг влево сдвигает влево на заданное количество разрядов.
1001 << 2 = 0100 (бывшие слева разряды 10 сдвигаются и теряются навсегда, справа замещаются нулями)
Примеры
Определение значения единичного бита
Для определения значения бита в определенном разряде применяются операции сдвига вправо на количество, чтобы нужный бит стал в первый разряд и логически умножаем полученное число на 1.
Например мы желаем узнать значение 4 разряда в слове: 1001001
1001001 >> 3 & 1 итог = 1. Как это получилось? Разберем пошагово:
1 шаг - побитовый сдвиг вправо.
1001001 >> 3, итог 0001001 - все биты сдвинулись на три вправо.
2 шаг - умножение на 1. Распишем в столбик:
0001001
&
0000001
Итог:
0000001 Это и есть единица.
Если бы значение нужного бита было бы 0, на выходе был бы 0.
Работа с битовой маской
Битовые маски используют, чтобы сравнивать значение одного или нескольких бит.
И в этом деле есть свой нюанс.
Дело в том, что для того, чтобы задать битовую маску в основном используют шестнадцатеричное представление числа.
Вы будете встречать в коде программ числа вида 0x3000. Где "0х" означает, что далее идет шестнадцатеричное число.
Это вносит определенные сложности.
Разберем на примере работы с выводами контроллера. Из примера "мигающего светодиода".
К примеру мы желаем определять значения на 12 и 13 выводах котроллера EMS32 и если на обеих ногах 0, то устанавливается высокая скорость моргания светодиода, иначе низкая.
Битовая маска для задания контрольного значения будет иметь еще один нюанс. Дело в том, что первый бит будет контролировать 0 вывод контроллера. Значит, для того, чтобы определять 12 и 13 вывод, нам нужно будет использовать 13 и 14 разряды в полученном от контроллера слове, содержащим состояния выводов.
Итак, как задать маску.
Берем в двоичном коде число, содержащее в 13 и 14 разряде единицы:
11000000000000 - вот что получилось.
Сколько разрядов слева роли для нас не играет. Они заполнятся нолями.
Воспользуемся онлайн конвертером и конвертируем данное двоичное число в шестнадцатеричное.
Получим следующее:
11000000000000 = 0х3000 - это и будет значение для нашей маски.
А код будет выглядеть примерно так (инициализация и прочее пропущено):
#define BUTTON_MASK_12_13 0x3000 //бит маска для 12 и 13 выхода контроллера (13 и 14 разряда в слове)
while(1) {
input_GPIO = gpio_input_get(); // Считываем значения входов с 0 по 31
gpio_output_set(0, LED_GPIO_SET, 0, 0); // светодиод погашен
printf("Светодиод погас\n");
if((input_GPIO & BUTTON_MASK_12_13) == 1) vTaskDelay(250 / portTICK_PERIOD_MS); //задержка 0,25 сек
else vTaskDelay(500 / portTICK_PERIOD_MS); //задержка 0,5 сек
gpio_output_set(LED_GPIO_SET, 0, 0, 0);// светодиод горит
printf("Светодиод горит\n");
input_GPIO = gpio_input_get();
if((input_GPIO & BUTTON_MASK_12_13) == 1) vTaskDelay(250 / portTICK_PERIOD_MS); //задержка 0,25 сек
else vTaskDelay(500 / portTICK_PERIOD_MS); //задержка 0,5 сек
}
input_GPIO & BUTTON_MASK_12_13 - вот эта операция сравнивает нашу маску со значением 13 и 14 бита в переданном слове, содержащем состояние выводов.
И если на обеих выводах 0, результатом операции будет 0, иначе значение, отличное от 0.
Почему может использоваться работа с переданным словом, а не опрос каждого вывода контроллера? Во первых это быстрее работает, а во вторых может понадобится считывать состояние шины данных в момент времени.
На этом вводную статью о работе с битами и битовыми масками завершим.
Если будут вопросы, пишите в комментариях. Постараемся разобрать и ответить.
Общий принцип следующий: в целочисленной переменной, сохраняется значение параметров, которые могут принимать два значения, 0 или 1. Т.е. бит, отсюда и название.
Для сохранения и чтения параметров используют значения разряда в числе (еще его называют "словом).
Например 1001 - тут мы видим, что в первом (считается справа налево) и четвертом разряде хранится единица, а во втором и третьем - ноль.
В данном примере 1001 в двоичном счислении это цифра 9 в десятично счислении. Но хранится число в шестнадцатиричном коде.
1001(двоичный код) = 9 (десятичный код) = 0x9 (шестнадцатеричный код, где 0х -указывает, что число хранится в шестнадцатеричном коде).
Для работы с битами в C++ используются побитовые операции:
- И - and (&) - побитовое умножение
- ИЛИ - or (|) - побитовое сложение
- ИСКЛЮЧАЮЩЕЕ ИЛИ - xor (^) - логическое сложение по модулю
- ИНВЕРСИЯ - NOT(~) - побитовое отрицание или инверсия
- Сдвиг вправо - (>>)
- Сдвиг влево - (<<)
И - and (&) 0 & 0 = 0 1 & 0 = 0 0 & 1 = 0 1 & 1 = 1 | ИЛИ - or (|) 0 | 0 = 0 1 | 0 = 1 0 | 1 = 1 1 | 1 = 1 | ИСКЛЮЧАЮЩЕЕ ИЛИ - xor (^) 0 ^ 0 = 01 ^ 0 = 1 0 ^ 1 = 1 1 ^ 1 = 0 | ИНВЕРСИЯ - NOT(~) ~1 = 0 ~0 = 1 |
Сдвиг вправо просто сдвигает вправо всё биты на заданное количество разрядов.
1001 >> 2 = 0010 (все в двоичном счислении, бывшие справа 01, просто отбрасываются, слева замещаются 0).
Сдвиг влево сдвигает влево на заданное количество разрядов.
1001 << 2 = 0100 (бывшие слева разряды 10 сдвигаются и теряются навсегда, справа замещаются нулями)
Вышеописанные операции применяют далее для работы с битовыми масками или параметрами переданными битовым словом.
Определение значения единичного бита
Для определения значения бита в определенном разряде применяются операции сдвига вправо на количество, чтобы нужный бит стал в первый разряд и логически умножаем полученное число на 1.
Например мы желаем узнать значение 4 разряда в слове: 1001001
1001001 >> 3 & 1 итог = 1. Как это получилось? Разберем пошагово:
1 шаг - побитовый сдвиг вправо.
1001001 >> 3, итог 0001001 - все биты сдвинулись на три вправо.
2 шаг - умножение на 1. Распишем в столбик:
0001001
&
0000001
Итог:
0000001 Это и есть единица.
Если бы значение нужного бита было бы 0, на выходе был бы 0.
Работа с битовой маской
Битовые маски используют, чтобы сравнивать значение одного или нескольких бит.
И в этом деле есть свой нюанс.
Дело в том, что для того, чтобы задать битовую маску в основном используют шестнадцатеричное представление числа.
Вы будете встречать в коде программ числа вида 0x3000. Где "0х" означает, что далее идет шестнадцатеричное число.
Это вносит определенные сложности.
Разберем на примере работы с выводами контроллера. Из примера "мигающего светодиода".
К примеру мы желаем определять значения на 12 и 13 выводах котроллера EMS32 и если на обеих ногах 0, то устанавливается высокая скорость моргания светодиода, иначе низкая.
Битовая маска для задания контрольного значения будет иметь еще один нюанс. Дело в том, что первый бит будет контролировать 0 вывод контроллера. Значит, для того, чтобы определять 12 и 13 вывод, нам нужно будет использовать 13 и 14 разряды в полученном от контроллера слове, содержащим состояния выводов.
Итак, как задать маску.
Берем в двоичном коде число, содержащее в 13 и 14 разряде единицы:
11000000000000 - вот что получилось.
Сколько разрядов слева роли для нас не играет. Они заполнятся нолями.
Воспользуемся онлайн конвертером и конвертируем данное двоичное число в шестнадцатеричное.
Получим следующее:
11000000000000 = 0х3000 - это и будет значение для нашей маски.
А код будет выглядеть примерно так (инициализация и прочее пропущено):
#define BUTTON_MASK_12_13 0x3000 //бит маска для 12 и 13 выхода контроллера (13 и 14 разряда в слове)
while(1) {
input_GPIO = gpio_input_get(); // Считываем значения входов с 0 по 31
gpio_output_set(0, LED_GPIO_SET, 0, 0); // светодиод погашен
printf("Светодиод погас\n");
if((input_GPIO & BUTTON_MASK_12_13) == 1) vTaskDelay(250 / portTICK_PERIOD_MS); //задержка 0,25 сек
else vTaskDelay(500 / portTICK_PERIOD_MS); //задержка 0,5 сек
gpio_output_set(LED_GPIO_SET, 0, 0, 0);// светодиод горит
printf("Светодиод горит\n");
input_GPIO = gpio_input_get();
if((input_GPIO & BUTTON_MASK_12_13) == 1) vTaskDelay(250 / portTICK_PERIOD_MS); //задержка 0,25 сек
else vTaskDelay(500 / portTICK_PERIOD_MS); //задержка 0,5 сек
}
input_GPIO & BUTTON_MASK_12_13 - вот эта операция сравнивает нашу маску со значением 13 и 14 бита в переданном слове, содержащем состояние выводов.
И если на обеих выводах 0, результатом операции будет 0, иначе значение, отличное от 0.
Почему может использоваться работа с переданным словом, а не опрос каждого вывода контроллера? Во первых это быстрее работает, а во вторых может понадобится считывать состояние шины данных в момент времени.
На этом вводную статью о работе с битами и битовыми масками завершим.
Если будут вопросы, пишите в комментариях. Постараемся разобрать и ответить.