Побитовые операции | Программирование микроконтроллеров AVR на C
Побитовые операции основаны на логических операциях, которые мы уже рассмотрели ранее. Они играют ключевую роль при программировании микроконтроллеров AVR и других типов. Практически ни одна программа не обходится без применения побитовых операций. До этого мы намеренно избегали их, чтобы облегчить процесс изучения программирования МК.
Во всех предыдущих статьях мы программировали только порты ввода-вывода а и не задействовали дополнительные встроенные узлы, например, такие как таймеры, аналогово-цифровые преобразователи, прерывания и другие внутренние устройства без которых МК теряет всю свою мощь.
Прежде, чем перейти к освоению встроенных устройств МК, необходимо научится управлять или проверять отдельные биты регистров МК AVR. Ранее же мы выполняли проверку или устанавливали разряды сразу всего регистра. Давайте разберемся, в чем состоит отличие, а затем продолжим далее.
Побитовые операции
Чаще всего при программировании микроконтроллеров AVR мы пользовались двоичной системой счисления, поскольку она имеет большую наглядность по сравнению с шестнадцатеричной и хорошо понятна для начинающих программистов МК. Например, нам нужно установить только 3-й бит порта D. Для этого, как мы уже знаем, можно воспользуемся следующим двоичным кодом:
PORTD = 0b00001000;
Однако этой командой мы устанавливаем 3-й разряд в единицу, а все остальные (0, 1, 2, 4, 5, 6 и 7-й) мы сбрасываем в ноль. А теперь давайте представим ситуацию, что 6-й и 7-й разряды задействованы как входы АЦП и в это время на соответствующие выводы МК поступает сигнал от какого-либо устройства, а мы, применяемой выше командой, обнуляем эти сигналы. В результате чего микроконтроллер их не видит и считает, что сигналы не приходили. Поэтому вместо такой команды нам следует применить другую, которая бы установила только 3-й бит в единицу, при этом не влияя на остальные биты. Для это обычно применяется следующая побитовая операция:
PORTD |= (1<<3);
Синтаксис ее мы подробно разберем далее. А сейчас еще один пример. Допустим нам нужно проверить состояние 3-го разряда регистра PIND, тем самым проверяя состояние кнопки. Если данный разряд сброшен в ноль, то мы знаем, что кнопка нажата и далее выполняется код команды, который соответствует состоянию нажатой кнопки. Ранее мы бы воспользовались следующей записью:
if (PIND == 0b00000000)
{ какой-либо код}
Однако с помощью нее мы проверяем не отдельный, – 3-й, а сразу все биты регистра PIND. Поэтому даже если кнопка нажат и нужный разряд сброшен, но в это время на какой-либо другой вывод порта D поступит сигнал, то соответствующий быт установится в единицу, и условие в круглых скобках будет ложным. В результате код, находящийся в фигурных скобках, не будет выполняться даже при нажатой кнопке. Поэтому для проверки состояния отдельного 3-го бита регистра PIND следует применять побитовую операцию:
if (~PIND & (1<<3))
{ какой-либо код}
Для работы с отдельными битами микроконтроллера в арсенале языка программирования C имеются шесть логических побитовых операций, с помощью которых можно изменять или проверять состояние одного или нескольких отдельных бит сразу.
Установка отдельного бита
Для установки отдельного бита, например порта D, применяется побитовая операция ИЛИ. Именно ее мы применяли в начале статьи.
PORTD = 0b00011100; // начальное значение
PORTD = PORTD | (1<<0); применяем побитовую ИЛИ
PORTD |= (1<<0); // сокращенная форма записи
PORTD == 0b00011101; // результат
Эта команда выполняет установку нулевого разряда, а остальные оставляет без изменений.
Для примера установим еще 6-й разряд порта D.
PORTD = 0b00011100; // начальное состояние порта
PORTD |= (1<<6); //
PORTD == 0b01011100; // результат
Чтобы записать единицу сразу в несколько отдельных бит, например нулевой, шестой и седьмой порта B применяется следующая запись.
PORTB = 0b00011100; // начальное значение
PORTB |= (1<<0) | (1<<6) | (1<<7); //
PORTB == 0b1011101; // результат
Сброс (обнуление) отдельных битов
Для сброса отдельного бита применяются сразу три ранее рассмотренные команды: <<; &; ~.
Давайте сбросим 3-й разряд регистра PORTC и оставим без изменений остальные.
PORTC = 0b00011100;
PORTC &= ~(1<<3);
PORTC == 0b00010100;
Выполним подобные действия для 2-го и 4-го разрядов:
PORTC = 0b00111110;
PORTC &= ~((1<<2) | (1<<4));
PORTC == 0b00101010;
Переключение бита
Кроме установки и сброса также применяется полезная команда, которая переключает отдельный бит на противоположное состояние: единицу в ноль и наоборот. Данная логическая операция находит широкое применение при построении различных световых эффектов, например, таких как новогодняя гирлянда. Рассмотрим на примере PORTA
PORTA = 0b00011111;
PORTA ^= (1<<2);
PORTA == 0b00011011;
Изменим состояние нулевого, второго и шестого битов:
PORTA = 0b00011111;
PORTA ^= (1<<0) | (1<<2) | (1<<6);
PORTA == 0b01011010;
Проверка состояния отдельного бита. Напомню, что проверка (в отличии от записи) порта ввода-вывода осуществляется с помощью чтения данных из регистра PIN.
Наиболее часто проверка выполняется одним из двух операторов цикла: if и while. С этими операторами мы уже знакомы ранее.
Проверка разряда на наличие логического нуля (сброса) с if
if (0==(PIND & (1<<3)))
{ Код1;}
else {Код2;}
Если третий разряд порта D сброшен, то выполняется Код1. В противном случае, выполняется Код2.
Аналогичные действия выполняются при и такой форме записи:
if (~PIND & (1<<3))
{ Код1;}
else {Код2;}
Проверка разряда на наличие логической единицы (установки) с if
if (0 != (PIND & (1<<3)))
{ Код1;}
else {Код2;}
Аналог:
if (PIND & (1<<3))
{ Код1;}
else {Код2;}
Приведенные выше два цикла работаю аналогично, но могут, благодаря гибкости языка программирования C, иметь разную форму записи. Операция != обозначает не равно. Если третий разряд порта ввода-вывода PD установлен (единица), то выполняется Код1, если нет ‑ Код2.
Ожидание сброса бита с while
while (PIND & (1<<5))
{
Код1;
}
Код2;
Код1 будет выполняться пока 5-й разряд регистра PIND установлен. При сбросе его начнет выполняться Код2.
Ожидание установки бита с while
Здесь синтаксис языка С позволяет записать код двумя наиболее распространёнными способами. На практике применяются оба типа записи.
while (~PIND & (1<<4))
while (! (PIND & (1<<4)))
Алгоритм работы данной функции противоположен предыдущей функции. Цикл будет выполняться до тех пор, пока 4-й разряд порта D сброшен.
Также существуют и другие, упрощающие формы записи рассмотренных действий, которые доступны при подключении заголовочного файла <avr/sfr_defs.h> либо переопределяются с помощью директивы препроцессора #define.
Но на первых этапах изучения программирования микроконтроллеров AVR я настоятельно рекомендую довести до автоматизма применение побитовых операций в классическом виде.
Огромная благодарность автору!
Спасибо! Когда следующие уроки?
Перелопатил кучу руководств по программированию мк. Ваше самое понятное и доходчивое. Наконец-то разобрался с побитовыми операциями — спасибо Вам огромное! Надеюсь будет продолжение.
Я, для понимания и запоминания , каждую операцию расписываю:
Проверка сброса бита:
PORTB = 0b00000001
if (~PINB & (1<<0))
&
1 11 1 11 10
00000001
—————
00000000
Тут всё понятно. Но с более длинной записью if (0 == PINB & (1<<0)) не понятно.
Получается, что:
if (0 == 00000001)
Объясните этот момент пожалуйста.
Чтобы записать единицу сразу в несколько отдельных бит, например нулевой, шестой и седьмой порта B применяется следующая запись.
»
PORTB = 0b00011100; // начальное значение
PORTB |= (1<<0) | (1<<6) | (1<<7); //
PORTB == 0b1011101; // результат
"
Тут седьмой бит потерялся в результате?
Тут, восьмой бит потерялся, ноль. Не
Очень жаль, что Ваши уроки закончились. Все, понятным языком объясняете, доходчиво и наглядно. Я, тоже думаю, что в нете, ваши уроки самые лучшие для начинающих.
Уроки не закончились. Просто затраченное время и силы на подготовку видео никак не соответствует количеству просмотров. Поэтому видео выходят не часто, надеюсь по понятным причинам. Время нынче стоит не дешево и тем более не бесплатно.
На самом деле все понятно и без видео. Лучше пишите статьи, со временем опубликуете книгу.
Отличная статья и урок на You Tube. Я только начал изучать Си для МК, и с ассемблера тяжеловато переходить на языки высокого уровня, но данный автор все по полочкам раскладывает. Респект тебе!!!)
while (~PIND & (1<<4)) это понятно. А как проверить сразу два и более сброшенных бита в ноль? Например чтобы цикл выполнялся, когда одновременно будут нажаты две и более кнопки (на двух и более выводах порта D будет ноль)?
А как проверить наличие единице или нуля отдельнвх битов (двух и более) в регистре PINx одновременно? То есть когда нажаты две и более кнопки сразу? Как в этом случае правильно код написать?
Это трагедия АТМЕЛА с проверкой состояния байта ? ) Я смотрю нет ответа даже как два PINа сразу проверить
PORTB = 0b00011100; // начальное значение
PORTB |= (1<<0) | (1<<6) | (1<<7); //
PORTB == 0b1011101; // результат
Квас здесь ошибка в последней строке.
Алексей
while (~PIND & (1<<4)) это понятно. А как проверить сразу два и более сброшенных бита в ноль? Например чтобы цикл выполнялся, когда одновременно будут нажаты две и более кнопки (на двух и более выводах порта D будет ноль)?
Ответ: в условии проверки добавляешь еще одно условие проверки + оператор логического сравнение который тебе нужен. Например если хочешь проверить что обе кнопки нажаты одновременно: while ((~PIND & (1 << PD1)) && (~PIND & (1 << PD2)))