Если Вы хотите обучаться программированию микроконтроллеров, но попали, сюда не прочитав предыдущих уроков, то советую начать изучение материала с самого начала.
На прошлом занятии мы с вами закончили изучение циклов, а также их возможности и применение дополнительных операторов досрочного выхода и пропуска итераций. Также, надеюсь, вы уяснили почему не рекомендуют применять оператор «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.