adiw и sbiw, которые, по сути, делают то же самое, что пары команд add/adc (sub/sbc), только за одну операцию, и притом с константой, а не с регистром. Единственный их недостаток— работают они только с определенными парами регистров: с четырьмя парами, начиная с r25-r24. Три старших пары (r26-27, r28-29 и r30-31) носят еще название X, Y и Z, и мы их будем «проходить» далее в этой главе, они задействованы в операциях обмена данными с SRAM. Но, к счастью, точно так же работает и пара r24-r25, которая более нигде не употребляется в объединенном качестве, и это очень удобно. Независимо от используемой пары, старшим считается регистр с большим номером, а операцию нужно проводить с младшим, при этом перенос учтется автоматически. Например, в результате выполнения последовательности команд
clr r25
ldi r24,100
adiw r24,200
в регистрах r25:r24 окажется записано число 300 (в r25 будет записано $01, что эквивалентно 256 для 16-разрядного числа, а в r24 окажется $2С = 44). Аналогично работает и процедура вычитания константы sbiw.
С арифметикой многоразрядных чисел мы познакомимся в главе 15, там же попробуем освоить умножение и деление. Заметим, что все эти операции корректно работают с числами со знаком (см. главу 7), но вы удивитесь, узнав, насколько редко такая задача возникает на практике. А с дробными числами (с «плавающей запятой») AVR работать напрямую не «умеют», и тут приходится хитрить, о чем мы подробнее поговорим в главе 15.
Кроме собственно арифметических, к этой группе команд еще иногда относят некоторые логические операции, например логический сдвиг, хотя чаще их помещают в группу инструкций для операций с битами. Самая простая такая операция — сдвиг всех разрядов регистра влево (lsl) или вправо (lsr) на одну позицию. Сдвиги приходится применять довольно часто, потому что, как вы знаете из главы 7, такая операция равносильна умножению (соответственно, делению) на 2. Для того чтобы разряды не терялись при сдвиге, предусмотрены разновидности этих операций: rol и rоr. Они учитывают все тот же флаг переноса С, и его можно перенести в другой регистр. Например, в результате выполнения последовательности команд
lsl R1
rol R2
регистр R1 будет умножен на 2, а старший его разряд (неважно, ноль он или единица) окажется в младшем разряде R2. Причем обратите внимание, что R2 при этом также умножается на 2, и, следовательно, его младший разряд без учета переноса всегда окажется равным нулю (четные числа в двоичной системе оканчиваются на 0).
Здесь же имеет смысл рассмотреть инструкции установки отдельных битов. Команды, устанавливающие значения битов в регистрах общего назначения (sbr и cbr) иногда относят к группе арифметических операций, а команды, устанавливающие биты в регистрах ввода/вывода (sbi и cbi) — к группе битовых операций. Правда, в некоторых пособиях их относят к одной группе операций с битами, что, конечно, логичней. Но почему такой разнобой, если они, по сути, делают одно и то же?
Механизм работы этих команд существенно различается. Очевиднее всего устроены sbi и cbi: так, уже знакомая нам команда sbi PortB,5 установит в единичное состояние разряд номер 5 порта В. Если этот разряд порта сконфигурирован на выход, то единица появится непосредственно на соответствующем выводе микросхемы (например, для микросхемы ATmega8 это вывод 19, для 8515 или 8535 любого семейства это вывод 6 и т. п.), если на вход, то эта операция управляет подключением «подтягивающего» резистора.
Гораздо сложнее устроены команды установки бит в регистрах общего назначения. Взгляните на фрагмент ранее приведенного кода, где вы встретите команду sbr Flag, 0b00010000, устанавливающую бит номер 4 в единицу. Почему так сложно, да еще и в двоичной системе? Дело в том, что команды эти на самом деле не устанавливают никаких битов, а просто осуществляют некую логическую операцию между значением регистра и заданным байтом, который в этом случае называют битовой маской. Указанная команда расшифровывается как Flag OR ob00010000. Если вы вернетесь к главе 7, то сообразите, что при такой операции четвертый бит (нумерация начинается с нуля) установится в единицу, какое бы значение он ни имел ранее, а все остальные останутся в старом состоянии. Понятно также, почему я предпочел использовать двоичное представление маски: так легче отсчитывать биты. Так, разумеется, можно за один раз установить хоть все биты в регистре — в этом отличие команды sbr от sbi, которая устанавливает только по одному биту.
Аналогично работает команда сброса бита cbr Flag, 0b000010000. Только для того, чтобы ее можно было записывать в точности в том же виде, что и sbr, логическая операция, которую она осуществляет, сложнее: Flag AND (NOT 0b000100000). Здесь сначала в маске инвертируются все биты (реально это производится вычитанием ее из числа $FF, т. е. нахождением дополнения до 1), а затем полученное число и значение регистра комбинируются операцией логического умножения (а не сложения, как в предыдущем случае). В результате четвертый бит обнулится обязательно, а все остальные останутся в неприкосновенности. Кстати, обнулять все биты некоего регистра R удобнее и нагляднее не командой cbr R, $FF, а с помощью инструкций clr R или ldi R,0.
Призываю вас об этой разнице между регистрами общего назначения и регистрами ввода/вывода не забывать. Я и сам до сих пор «попадаюсь» на том, что в случае необходимости обнуления бита 1 в рабочем регистре temp записываю cbr temp, 1 (аналогично верной команде cbi portB,1), хотя такая операция обнулит не первый, а нулевой бит в temp. А операция cbr R, 0 (как и операция sbr R, 0) вообще ничего не делает, и такая запись бессмысленна.
Команды переноса данных
Последнее, что мы рассмотрим в этом разделе — команды, которые употребляются для переноса данных из одной области памяти в другую (здесь память рассматривается в широком смысле этого слова, и в нее включаются также и регистры). Некоторые команды из этой группы нам уже знакомы — это ldi и mov. Первая загружает в регистр непосредственное число (но действует только для регистров, начиная с r16), а вторая — значение другого регистра. Отметим еще, что для ldi очень часто применяется форма записи, которую можно пояснить следующим практическим примером:
ldi temp, (1<
Смысл этого выражения в том, что мы устанавливаем в единицы для регистра temp только биты с указанными именами (естественно, последние должны быть где-то определены, в данном случае это сделано в inc-файлах). Обратите внимание, что в отличие от команды sbi, остальные биты будут одновременно обнулены, а не останутся при своих значениях. Такая форма очень удобна для задания поименованных битов в процессе инициализации служебных регистров (при использовании «законной» команды sbi нужные биты пришлось бы указывать в числах, и предварительно обнулять регистр). В данном случае мы хотим инициализировать последовательный порт UART, и приведенная форма записи означает, что устанавливается разрешение приема (RXEN) и передачи (TXEN), причем и для того и для другого устанавливается 8-битный режим (RXB8 и TXB8). Подробнее мы в этом разберемся в главе 16, когда будем «проходить» программирование UART, а пока заметим, что еще. не все доделали: установили биты в регистре temp, но он-то какое отношение имеет к последовательному порту?
Потому следующей по тексту программы командой мы обязаны перенести установленное значение в соответствующий регистр ввода/вывода, для которого, собственно, поименованные биты и имеют значение, называющийся UCR. Это делается традиционной для всех ассемблеров командой out:
out UCR,temp
Замечу, что указанные команды по отношению к UART справедливы для семейства Classic, для Mega это делается несколько сложнее (a Tuny, кроме модели 2313, вообще UART не имеют), но сейчас это неважно. В паре к команде out идет симметричная команда in (чтения данных из I/О-регистра).
Заметки на полях
Здесь уместно обратить внимание читателей на то, что в момент подобного рода установок надо тщательно следить за тем, чтобы значение рабочего регистра temp не могло измениться между командами его установки и переносом значения в I/O-регистр. А как это может случиться, спросите вы? Да запросто, — если разрешены прерывания, в обработчиках которых наверняка также будет присутствовать temp, то они могут «вклиниться» между командами. Пусть вероятность такого события крайне мала, и оно может не происходить годами, но программист обязан об этом помнить, и грамотно составленная программа во время таких установок прерывания запрещает. В секции RESET это обеспечивается тем, что по умолчанию прерывания запрещены, а команда на их общее разрешение (sei) ставится после всех установок. Куда меньше проблем вызывают сами обработчики, т. к. по умолчанию во время обработки прерывания другое, произошедшее позднее, ожидает своей очереди (автоматически прерывания разрешатся только на выходе из текущего обработчика по команде reti). Но если вам зачем-то нужно разрешить критичным прерываниям «вклиниваться» во время обработки некритичных (для этого следует явно разрешить прерывания внутри обработчика данного прерывания командой sei), то такая проблема снова возникает. Именно поэтому, в частности, не рекомендуется употреблять синонимы для имен рабочих регистров: слишком легко забыть, что