Логотип

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

Программирование микроконтроллеров. Урок 10
Поразрядные операции

Урок 10 (Поразрядные операции)

На прошлом занятии мы с вами закончили изучение циклов, а также их возможности и применение дополнительных операторов досрочного выхода и пропуска итераций. Также, надеюсь, вы уяснили почему не рекомендуют применять оператор «goto». Ну и конечно подключили кнопку которая сегодня нам как раз пригодиться. К слову, до конца изучения основ «Си», осталось совсем чуть-чуть. В общем всего две темы. Первая - массивы, а вторую мы как раз сейчас разберем. На этом уроке мы посмотрим, как ведут себя переменные, когда к ним применяют битовые операции.

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

В этот раз не будем останавливаться на домашнем задании. Для тех кто не смог решить задачку ответ можно разобрать в приложенных файлах.

Разобрались с домашкой? Ну и хорошо. Теперь начнем играться битами.


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


Поразрядных операций всего шесть:
1.   >>    сдвиг вправо;
2.   <<    сдвиг влево;
3.   ~    (NOT) отрицание;
4.   &    (AND) конъюнкция;
5.   ^    (XOR) исключающее или;
6.   |    (OR) дизъюнкция.



Сдвиг вправо и влево сродни делению или умножению некого числа, которое находится в регистре на нужную степень числа 2. Например нам надо число 0b 0001 1100 сдвинуть вправо на два бита. Записываем.

	PORTD = 0b00011100;  //Тестовое значение
	PORTD = PORTD >> 2 ; //Сдвиг вправо на 2
	PORTD = 0b00000111;  //Полученное значение

А теперь прикинем. 0b 0001 1100 не что иное как 28 в десятичной системе счисления и если мы его разделим на 2 во второй степени то получим 7. А это в свою очередь, как раз 0b 0000 0111.

0b00011100 0b00000111

0b00011100

0b00000111

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


Теперь проверим, как работает сдвиг влево.

	PORTD = 0b00000110;  //Тестовое значение
	PORTD = PORTD << 4 ; //Сдвиг влево на 4
	PORTD = 0b01100000;  //Полученное значение

Проверим. 0b00000110 это у нас число 6 и мы его умножим на 2 в четвертой степени (16). Получим 96 которое в двоичной системе записывается как 0b 0110 0000. Все верно.

0b00000110 0b01100000

0b00000110

0b01100000

Безусловно, во всем есть нюансы. При сдвиге в лево необходимо понимать, что если тип данных не будет в себя вмещать результат, то часть данных «уйдя за левый край» прекратит свое существование. Т.е. если мы будем сдвигать число 0b 0110 0000 записанное в переменной типа char на три бита влево, то получим в итоге все нули в байте. Нажмите еще раз на кнопку и еще раз сделайте сдвиг влево на 4 позиции. Чтобы такого не случилось, приемник (переменная в которую будет записано итоговое значение) должен соответствовать принимаемому числу. А значит он должен быть как минимум типа int.



Со сдвигами разобрались. Теперь разберемся с логическими операциями. И начнем с самых часто используемых (И) (ИЛИ).

Про конъюнкцию читаем Википедию. Там очень много и заумно написано, но надо уяснить только одно. При применении конъюнкции единица будет лишь тогда, когда оба сравниваемых бита имеют единицу. Например,

	PORTD  = 0b00000110;  //Тестовое значение
	PORTD &= 0b01100010;  //Конъюнкция со вторым значением
	PORTD  = 0b00000010;  //Полученное значение

Эту операцию часто применяют в случаях, когда надо обнулить определенные биты в регистре. Так сказать, «наложить маску».

	PORTD  = 0b01110110;  //Тестовое значение
	PORTD &= 0b00001111;  //Маска для обнуления старшего ниббла.
	PORTD  = 0b00000110;  //Полученное значение

0b01110110 0b00000110

0b01110110

0b00000110

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

	PORTD &= (0<<3); //обнуляем бит 3 в порту «D».
	PORTD &= (0<<4)|(0<<2); //обнуляем биты 4 и 2 

Но будьте внимательны. Не везде эти записи работают! Тестируя все программы к этому уроку, я наткнулся на некоторую аномалию. Возможно это баг моей версии CV_AVR, а может и что-то большее, но записи обнуления одного или нескольких битов PORTD &= (0<<3); к сожалению, не работают и обнуляют весть регистр! Так, что если надо снять бит, то лучше наложить полную маску на регистр, как было показано выше.


Теперь об операции дизъюнкции (ИЛИ). Это вторая часто используемая побитовая операция. Как всегда, от прочитанного в Вики запомнить надо то, что результат этой операции будет единица если хотя бы один из сравниваемых битов является единица. Например,

	PORTD  = 0b00000110 ; //Тестовое значение
	PORTD |= 0b01100010;  //Дизъюнкция со вторым значением
	PORTD  = 0b01100110;  //Полученное значение

Эту операцию часто применяют в случаях, когда надо установить единицы в определенных битах. Например, нам надо установить младший ниббл регистра «D» в единицу.

	PORTD  = 0b01110110;  //Тестовое значение
	PORTD |= 0b00001111;  //Маска для устанвоки младшего ниббла.
	PORTD  = 0b01111111;  //Полученное значение

0b01110110 0b01111111

0b01110110

0b01111111

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

	PORTD |= (0<<3); //устанавливаем бит 3 в порту «D».
	PORTD |= (0<<4)|(0<<2); //устанавливаем биты 4 и 2 

При использовании оператора (ИЛИ) эти записи работают корректно.



Теперь поговорим об операции побитового отрицания (NOT) Здесь все просто. Применяя побитовое отрицание к переменной, мы просто инвертируем ее. Т.е. там, где были единицы будут нули и наоборот. Например,

	PORTD = 0b01010101;  //Тестовое значение
	PORTD = ~PORTD; //Инвертируем порт .
	PORTD = 0b10101010;  //Полученное значение

0b01010101 0b10101010

0b01010101

0b10101010

А если нажать кнопку повторно, то порт восстановит свое первоначальное состояние.



И вот мы дошли до самой своеобразной побитовой операции (XOR) исключающая ИЛИ. Применяя ее к двум битам, мы получим единицу в итоге тогда, когда только один из двух бит имеет единицу. Например:

	PORTD  = 0b00000110;  //Тестовое значение
	PORTD ^= 0b01100010;  //Применяем побитовое ИЛИ
	PORTD  = 0b01100100;  //Полученное значение

0b00000110 0b01100100

0b00000110

0b01100100

Так же можно применить эту операцию к одному биту. Например

	PORTD ^= (1<<3); //инвертируем тритий бит
	PORTD ^= (0<<3); //не делаем ничего 

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


Ну вот и всё о побитовых операциях.


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

	PORTD = (1<<7)|(0<<6)|(1<<5)|(0<<4)|(1<<3)|(0<<2)|(1<<1)|(1<<0);

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

	PORTD = (1<<6); 

То в этот бит установится 1, но если мы напишем

	PORTD = (0<<6); 

То обнулится весь порт. Вот такие пироги.

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



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

А чтобы выполнить домашку придется почитать о широтно-импульсной модуляции (ШИМ). В интернете много статей по этому поводу. Но все равно придётся подойти творчески к решению этой задачи чтобы сделать рабочий программный ШИМ. А для тех, кто думает, что домашнее задание слишком сложное, скажу, что моя версия этой программы использует всего четыре переменные и занимает около 15 строчек программного кода в основном цикле.


На следующим занятии мы разберём сегодняшнее домашнее задание, а также будем много паять. Для изучения массивов нам понадобится собрать небольшой светодиодный куб 3х3х3. Так, что к следующему занятию нам понадобятся 27 светодиодов, девять резисторов 300 Ом, три резистора на 1 кОм, три резистора на 10 кОм и три транзистора кт815 а также паяльные принадлежности.


А на сегодня всё. Удачи.


01.08.18



Небольшое дополнение к сказанному!

Мне подсказали, что при использовании поразрядного (И) для обнуления одного бита можно применить следующую конструкцию.

	PORTD &= ~(1<<3); //обнуляем бит 3 в порту «D».

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

Спасибо за подсказку Николаю Костину.

В приложенном файле программа для проверки работы.


26.09.18


Если вдруг найдете в статье неточности или заблуждения. Напишите мне об этом. Я подправлю.


Приложение:
Проект CV_AVR.
Проверка работы конструкции для обнуления одного бита.