Analytics

среда, 10 октября 2012 г.

Лекция №03: Жонглируем битами

Теперь, когда мы с вами знаем, что такое регистры и за что они отвечают, самое время изучить механизм управления “переключателями”(изменения значений битов). Изучим мы этот механизм, разбирая реальную задачу. У нас имеется светодиод, одна нога которого соединена с P1.4(четвертый пин первого порта), а вторая - с “землей”(Vss). Если на P1.4 не подано напряжение, то ничего не происходит, но стоит только его подать, как светодиод тут же загорается. Исходя из того, что светодиод подключен к P1.4, становится очевидно: чтобы достичь желаемого результата, нам необходимо что-то сделать с четвертыми битами регистров, которые относятся к P1(Порту 1). Следует понимать, что, когда мы говорим “четвертый бит”, на самом деле он не является четвертым, так как нумерация битов начинается с 0, так что технически этот бит - пятый.

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

Проще писать эти значения в двоичной системе счисления: есть 8 цифр, каждая из них может принимать лишь два значения - 1 или 0. Чтобы отличать двоичные числа от десятичных, мы можем использовать префикс 0b(b - binary(двоичный)) к примеру - 0b10.

Ещё одна частоиспользуемая система счисления - шестнадцатеричная(англ.: hexadecimal). Эта система довольно удобна, так как любое 8-битное значение может быть представлено в виде двух символов, каждый из которых может принимать значение от “0” до “9” или от “a” до “f”. В качестве префикса для записи значений в этой системе счисления принят - 0x(hexadecimal), таким образом, число 2 в шеснадцатеричной системе будет выглядеть как 0x02, число 12 - 0x0c, становится очевидно, что сиволы a-f соответствуют числам 10-15 соответственно. 16-битные же значение в этой системе счисления после префикса содержат 4 цифры - ровно в два раза больше, нежели 8-битные, к примеру - 0x14da. В микроконтроллере MSP430 довольно много различных 16-битных значений, поэтому шестнадцатеричная система будет очень удобна. (Я прошу прощения, если у вас возникла путаница из-за того, что в своей предыдущей статье я использовал символ h после числа, для обозначения шестандцатеричных данных. Дело в том, что в документации TI в качестве обозначения шестнадцатеричного числа используется именно эта нотация, но тем не менее, мы все равно будем писать 0x, так как в текстах наших программ необходимо писать именно так. )
Помимо этих двух систем, вы можете использовать восьмеричную(с префиксом 0o), но, честно говоря, я ещё не встречал людей, которые так делают.

Ну что-ж, самое время разбавить сухую теорию небольшими практическими упражненями.

Использование двоичной системы счисления, как нельзя лучше помогает увидеть и понять, какие конкретно биты вы используете, какие из них «включены», какие «выключены», в общем ничего лучше и придумать нельзя. Однако, тут есть и подводные камни — при написании программного кода, вам пришлось бы вручную прописывать все значения этих регистров, а это ой как много писанины. Почему фраза «пришлось бы» в сослагательном наклонении? Потому что программисты из «Texas Instruments», изрядно нам в этом помогли написав для нас специальные заголовочные файлы, в которых всем нужным нам для работы адресам в памяти, уже назначены имена. Таким образом, при написании программы на языке C, нам достаточно подключить этот заголовочный файл, написав в самом начале программы соответствующую строчку(к примеру - #include <msp430g2001.h>). Эти заголовочные файлы, включают в себя короткие имена двоичных значений в которых все биты кроме значащих обнулены. К примеру, вместо того чтобы писать 0b00000100, мы можем написать BIT2, что, согласитесь, значительно быстрее и наглядней. Короткое имя BIT2 показывает нам что это набор из 8 бит, в котором все биты кроме значащего, в нашем случае — второго, равны нулю. Кстати говоря, нумерация битов не только начинается с нуля, но и считать и начинать считать их нужно с конца. Подобного рода короткие имена, есть в любом заголовочном файле и существуют для любого битового набора, начиная с BIT0(0b00000001) и заканчивая BIT7(0b10000000). А теперь, приступим!

Первое, что вам надо сделать для решения задачи со светодиодом - обозначить P1.4 как output, так мы сможем подавать напряжение через эту ножку к нашему светодиоду. Чтобы это сделать, вам необходимо установить значение 1 в четвертый бит регистра P1DIR. Существует три способа это сделать.

1. Явное присваивание


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

P1DIR = 0b00010000;

установит 1 в четвертый бит регистра P1DIR, а все остальные биты примут значение 0. Этот метод довольно неплох, для того, чтобы задать начальные значения регистров, с которыми по ходу программы будет работать наш процессор. Но если вы продолжите подобным образом изменять значения в последующем программном коде, у вас могут возникнуть проблемы, так как не всегда знаешь, какие биты вам нужно менять, а какие нет, какие уже изменили свое значение, что нужно сохранить, а что можно отбросить. Использование данной конструкции подобно лому - просто и наверняка. Что может быть проще, чем явное указание значения каждого бита? Но с такой же легкостью этот метод просто напросто “отключит” все остальные биты регистра, установив в них 0, так как вы сами ему это сказали. Только есть проблема: далеко не факт, что для корректной работы программы вам нужно их отключать. Существуют методы для более гибкого контроля этих значений. Их мы и рассмотрим чуть ниже. Кстати говоря, напоминаю об использовании коротких имен. Вышеуказанная строчка кода эквивалентна следующей:

P1DIR = BIT4;

Так как BIT4 является “именем” 8-битного значения 0b00010000

2. Сложение и вычитание


Если же вы точно не знаете, какое значение нам необходимо установить или вам необходимо просто поменять значение лишь одного бита, вы можете использовать обычное сложение. Да-да, просто сложение. Вам необходимо к регистру P1DIR прибавить этот бит(в следующем примере, символом “:” я обозначу те биты, значение которых нам неизвестно): 0b:::0:::: + 0b:::1:::: = 0b:::1::::
Таким же точно образом, мы можем изменить значение нашего регистра P1DIR просто написав следующее:

P1DIR += 0b00010000;

*(Смущает конструкция “+=”? Ничего страшного, в примечаниях к статье описано, что это за конструкция и как она работает)
Проблемы при использовании данного метода могут возникнуть, если вы попытаетесь установить значение 1 в бит, который был “включен” и до вас, тогда этот бит не только обернется в ноль, так ещё и следующий за ним бит изменит свое значение на 1(если конечно он уже не “включен”, тогда с ним произойдет то же самое, что и с предыдущим. Попробуйте поскладывать столбиком двоичные числа, вы поймете почему так происходит). То есть, ошибившись в данной операции, вы измените “направление” отнюдь не той ножки, которой хотели, но это приведет ошибкам в работе вашей программы. Так что использовать этот метод стоит очень внимательно. Кстати, эти операции так же применимы и к коротким именам. Т.е. конструкция

P1DIR += BIT4;
или
P1DIR -= BIT4;

будет работать без каких-либо проблем.

3. Логические операторы & и |


Во избежание проблем, связанных со сложением и вычитанием, мы можем воспользоваться логическими операциями. Предположим, у нас есть битовое значение “x”(x может быть как 1, так и 0, на данный момент это не так важно, так как мы рассматриваем общий случай). Логические операции И(and - &) и ИЛИ(or - |) позволяют нам указать точное значение для данного бита, независимо от того, в каком он сейчас состоянии. Таким образом, операция & возвращает значение 1 только в том случае, если ОБА её операнда имеют значение 1. Получается, что x & 0 = 0, и абсолютно не важно, какое в данный момент имеет значение бит “x”. Логическая операция | вернет нам единицу, если хотя бы один из операндов уже равен единице - x | 1 = 1 в независимости от состояния бита “x”. Отсюда же следует, что - x | 0 = x, ведь если бит “x” имеет значение 1, то логическая операция ИЛИ вернет вам туже самую единицу, ну а если “x” равен 0, то в числе операндов единица не значится, а из этого следует, что оператор возвращает тот же ноль, коим и является бит “x”. Каков же тогда будет результат операции P1DIR | 0b00010000? Биты 0-3 и 5-7 останутся неизменными(помните? | 0 никак не влияет на второй операнд), чего не скажешь про 4-й бит, он как раз таки однозначно примет значение 1(он, конечно же, может и так был единицей, но после этой операции, он точно станет равен 1). Теперь небольшой пример с операцией &: P1DIR &= 0b11101111. После выполнения данной операции четвертый бит в данном регистре обнулится, так как один из операндов равен нулю, а все остальные биты останутся неизменными. Почему? Потому что, как уже говорилось ранее, чтобы операция вернула 1, нужно чтобы оба операнда были 1, таким образом, если биты регистра изначально были равны единице, то они и останутся единицей, в противном случае, они как нулями были, так нулями и останутся. Да, и в этих операциях короткие имена наших битов будут работать. Чтобы больше не повторяться, запомните, что они вообще везде будут работать.

Домашнее задание №1: Вы можете встретить программный код, в котором для выполнения одной и той же задачи, используются разные методы - иногда это a + b, а иногда а | b. Подумайте, почему в некоторых случаях эти операции эквивалентны и в каких ситуациях вы бы НЕ стали использовать тот или иной способ.

4.  Логический оператор ^


Да-да, знаю. Я говорил что их три, а сам описываю уже четвертый. Но их на самом деле три. Эту логическую операцию, более правильно было бы описать в третьем блоке, просто я для удобства выделил её “особняком” так как она все-таки, пусть немного, но отличается от двух вышеописанных. Итак, операция эта называется ИСКЛЮЧАЮЩЕЕ ИЛИ(XOR - ^). Если вы волею судеб оказались не знакомы с этим оператором, то, скорее всего, понять его суть будет несколько сложнее, чем все предыдущее, вместе взятые. Попробуем оттолкнуться от примеров из формальной логики, вероятно, так будет немного проще. Исключающее ИЛИ ведет себя идентично обычному ИЛИ за исключением одного лишь случая: когда оба бита являются единичными, эта функция возвращает нам ноль. Как же нам в этом разобраться? Представим, что “поднятый” бит символизирует о том, что наступило какое-то событие. Теперь представим, что оба операнда имеют значение 1. В случае обычного ИЛИ вам предоставляется выбор между двумя событиями, которые без всяких проблем могут существовать одновременно - вы можете выбрать или первое событие, или второе, суть одна - вы выбираете единицу. С исключающим же ИЛИ, все несколько сложнее: вам предоставляется выбор между двумя событиями, которые ну никак не могут произойти одновременно. Т.е. сложившаяся жизненная ситуация попросту неразрешима и вы лишаетесь выбора. Так как выбрав одно, вы исключаете возможность второго. То есть, выбирая единицу, вы исключаете из вариантов выбора единицу. Таким образом, единицу вы выбрать просто не можете. Тогда вам не остается ничего другого, кроме как ничего не делать - 0. Если вы не поняли о чем речь, то вам следует сесть и глубоко поразмыслить. Понимание таких вещей довольно важно. Если у вас все-таки совсем никак не получается осмыслить это, могу посоветовать лишь одно - поискать более доступное разъяснение исключающего ИЛИ в интернете.
Приведу табличку иллюстрирующую работу с оператором ^:


a b ^
0 0 0
0 1 1
1 0 1
1 1 0


Так зачем же нужен этот оператор и почему я не описал его вместе с его собратьями & и |? А потому что, если два его брата нужны для того, чтобы напрямую устанавливать в конкретный бит конкретное значение, то этот нужен для того, чтобы это значение изменить на противоположное. Как щёлкнуть выключателем: щёлк - x = 1, щёлк ещё раз x = 0. Все, как видите, довольно просто и, что не менее важно, достаточно удобно.
Ну и, следуя традиции, приведу конкретный пример:

P1DIR ^= 0b00010000;

Щёлк - и четвертый бит нашего регистра изменил свое значение на противоположное. Всё просто!

Теперь в нашей программе надо инициализировать нашу ножку(P1.4) как output. Для этого мы пишем:

P1DIR = BIT4;

(Того же самого мы бы достигли используя логический оператор вместо присваивания: P1DIR |= BIT4;)
Значение, содержащееся в бите регистра P1OUT, может быть как единицей, так и нулём, но мы на всякий случай обнулим это значение, написав следующую строку:

P1OUT = 0;

(Вообще это не очень хороший тон, было бы куда лучше, если бы вы проинициализировали начальное состояние этого регистра ДО того как назначаете какую-либо ножку в режим “вывода”)
Теперь, когда все биты, включая бит нашей ножки, обнулены, светодиод, разумеется, выключен. Чтобы его включить необходимо ввести следующую строку:

P1OUT |= BIT4;

Чтобы снова выключить:

P1OUT &= ~BIT4;

(префикс ~ перед значением инвертирует все биты в этом значении. Все единицы становятся нулями, а все нули единицами)
Чтобы “пощёлкать выключателем”:

P1OUT ^= BIT4;

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

Для начала назначим первоначальные состояния наших ножек как “выключены” и укажем им output-направление:

P1OUT = 0;
P1DIR = BIT4 + BIT6;

(Сложение двух битовых значений 0b00010000 и 0b01000000 в сумме дает 0b01010000. Как видите, все до безобразия просто)
Теперь мы можем включать и выключать их по отдельности:

P1OUT |= BIT4;    // P1.4 включен
P1OUT &= ~BIT4;    // P1.4 выключен
P1OUT |= BIT6;    // P1.6 включен
P1OUT &= ~BIT6;   // P1.6 выключен

Или вместе:

P1OUT |= BIT4 + BIT6;     // включить оба
P1OUT &= ~(BIT4 + BIT6);  // выключить оба

И, наконец, “пощёлкать” ими одновременно:

P1OUT ^= BIT4 + BIT6;     // вкл/выкл оба

Домашнее задание №2:  Напишите программу, которая бы зажигала два светодиода подключенные к P1.3 и P1.7. Начальное состояние светодиода на P1.3 должно быть “Выкл”, а на P1.7 - “Вкл”, после этого, напишите строчку “переключатель”, которая бы одновременно меняла состояние обоих светодиодов.


* += это сокращенный оператор языка С. Запись x += 1 эквивалентна следующей записи:  x = x + 1; её можно трактовать как: “С этого момента значением x является текущее значение x, увеличенное на единицу”.


Оригинал статьи: Tutorial 03: Flipping Bits
Перевод: Александр Мошкин
Коррекция: Алёна Ступникова
Следующая лекция: Лекция №04. Крутимся в цикле
Предыдущая лекция: Лекция №02. MSP430 Города и регистры.