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

Программирование микроконтроллеров. Урок 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.

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


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

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

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

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


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

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

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

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

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

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

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

не везде работает. В CV_AVR при использовании такой записи обнуляется три младших бита. Чтобы все работало корректно и обнулялся только третий бит необходимо использовать другую структуру записи.

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

С точки зрения логического смысла обе записи эквивалентны, но CV_AVR корректно понимает только вторую.


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

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

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

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

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

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

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


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

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

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


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

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

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

	PORTD ^= (1<<3); //инвертируем тритий бит

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


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


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

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


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

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

01.08.18

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

Приложение: Проект CV_AVR.