Немного о портах и битовых операциях

Многие из вас видели и наверно даже понимают, что такое

DDRB |= (1 << 5);

PORTB |= (1 << 5);

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


Основная опасность и в то же время преимущество управления портами напрямую без прослоек типа digitalWrite() - это то, что вы можете управлять сразу 8 портами всего за 1 строчку кода, которая в свою очередь выполняется достаточно быстро, намного быстрее стандартной функции.

Но если необходимо подергать частью портов так, чтобы не сбить какие-либо связи на других портах - надо изолировать эти обращения. Тут на помощь и придет битовая логика.


Например надо настроить на вывод 11, 12, 13 порт уны, а 8, 9, 10 не трогать. Для этого нужен оператор "ИЛИ", который обозначается '|':

DDRB |= B00111000;

или

DDRB |= (B111 << 3);

Оба варианта делают абсолютно одно и то же. Сдвиг влево (<<) задается количеством бит, на которое сдвигается значение. Сдвигая B111 на три бита, получаем B111000. Можно сказать, что мы добавляем определенное количество значащих нулей.


Теперь разберем, почему оператор | так важен. Для этого надо понять, что делает этот оператор:

0101 (decl 5)  

0011 (deс 3)

OR=

0111 (dec 7)

Если по-простому - при установке единицы меняет 0 на 1, а при установке 0 в бит, где есть единица - не меняется ничего, так же и при установке ноля в 0. То есть мы установили на вывод три бита, не затронув остальные, которые так же могли быть уже назначены выводами.


Но если надо выставить 0 так, чтобы не прибить остальные биты в 0, нужен оператор "И", обозначающийся &. Так же к нему понадобится оператор "НЕ" - обозначается ~.


Итак, выключаем светодиод на 13 пине:
PORTB &= ~(1 << 5);

или

PORTB &= ~B00100000;

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

Итак, ~ переворачивает все с ног на голову:

~B00100000 = B11011111

А оператор & как раз нолем меняет любую единицу на ноль, а единицей не меняет ничего. В итоге - 1 выключенный бит без беспокойства остальных. "И" в двоичной математике - это умножение: умножаем 1 на 0 - и получим 0, а 1*1=1.


Так же есть исключающее "ИЛИ" или XOR в английском варианте - ^. С помощью него можно поменять значение определенного бита. Например:
B11011111 ^= B00110000;
будет иметь в итоге

B11101111.

Вкратце: с помощью единицы меняем бит на противоположный, ноль ничего не делает.


Теперь можно поговорить о цифровом чтении портов. Перед чтением необходимо установить 0 в нужные биты:
DDRB &= ~B00000001;

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


И благодаря тому, что мы оперируем сразу 8 портами одновременно, можно не городить конструкции типа
if(digitalRead(8) && digitalRead(9) && digitalRead(11) && digitalRead(13)){....}

А сделать небольшую маску сравнения:

if(PINB & B00101011){....}

Работает точно так же, но экономит место в памяти контроллера, плюс выигрыш в быстродействии. Главное от наводок защититься подтягивающими резисторами, иначе даже digitalRead не спасет.



Напоследок расскажу один чит, который подглядел в оригинальных библиотеках для shiftOut, и не сразу смог понять, что же он делает:
!!(val & (1 << i))

Разгадка оказалась на поверхности, но о ней по порядку: '!' - это НЕ в логических операндах, работает так:
!1=0

!255=0

!0=1


В данном случае двойной восклицательный знак позволяет привести 8, 16, 32... битное число с помощью маски в один лишь бит, без всяких нолей, значащих и нет:

0. !!(val & (1 << i))

1. !!(B1110 & (1<<2))

2. !!(B0100)

Поскольку B0100 равно или больше единицы, однократное НЕ приведет к нолю, а двухкратное к единице, переход через 0 позволяет оставить лишь суть:

3. !(0)

4. (1)


Таким нехитрым образом можно разобрать любое число на отдельные биты.

Arduino & Pi

1.4K постов20.6K подписчиков

Добавить пост

Правила сообщества

В нашем сообществе запрещается:

• Добавлять посты не относящиеся к тематике сообщества, либо не несущие какой-либо полезной нагрузки (флуд)

• Задавать очевидные вопросы в виде постов, не воспользовавшись перед этим поиском

• Выкладывать код прямо в посте - используйте для этого сервисы ideone.com, gist.github.com или схожие ресурсы (pastebin запрещен)

• Рассуждать на темы политики

• Нарушать установленные правила Пикабу

Автор поста оценил этот комментарий

Просьба разжевать по конструкции if(PINB & B00101011){....}

Как я ее понял- в условии висит побитовое И между состоянием порта и маска. Что получится после побитового сложения? И каков критерий выполнения "if"? Насколько я помню проверяется истина-ложь относительно выражения в скобках, но истиной оно будет во всем диапазоне от 1 до 255, или я чего-то не понимаю в побитовых операциях...

Благодарю.

раскрыть ветку
Автор поста оценил этот комментарий

а можно с начала?

что делает DDRB, что делает команда DDRB |= (1 << 5);

PORTB |= (1 << 5);

разжевать для тех, кто в танке.

раскрыть ветку
Автор поста оценил этот комментарий

интересно конечно, но далеко не для начинающих

а у вас есть свой модуль для созданию многоуровневого меню? можете поделиться?

раскрыть ветку