Главная страница
Библиотека (скачать книги)
Скачать софт
Введение в программирование
Стандарты для C++
Уроки по C#
Уроки по Python
HTML
Веб-дизайн
Ассемблер в среде Windows
ActiveX
Javascript
Общее о Линукс
Линукс - подробно
Линукс - новое
Delphi
Паскаль для начинающих
Турбопаскаль
Новости
Партнеры
Наши предложения
Архив новостей





Призываю вас об этой разнице между регистрами общего назначения и регистрами ввода-вывода не забывать — я и сам до сих пор попадаюсь на том, что в случае необходимости обнуления бита 1 в рабочем регистре temp записываю cbr temp, 1 (аналогично верной команде cbi PortB, l), хотя такая операция обнулит не первый, а нулевой бит в temp. А операция cbr R,O (как и sbr R, о) вообще ничего не делает, и такая запись бессмысленна. Разумеется, этими командами можно за один раз установить хоть все биты в регистре— в этом отличие команд sbr/cbr от sbi/cbi, которые устанавливают только по одному биту. Совместно с указанными командами можно употреблять и выражения, так же, как с командой ldi в примере из главы 5:

sbr temp, (1«SE) | (1«SM1)
out MSUR,temp /разрешение "спящего" режима Power Down

Кроме перечисленных, к группе операций с битами также относят команды установки разрядов регистра SREG, которые обсуждались ранее. Более правильным, на мой взгляд, было бы добавить к группе битовых операций также и команды сдвига (иногда их почему-то относят к арифметическим операциям). Самая простая такая операция — сдвиг всех разрядов регистра влево (lsi) или вправо (isr) на одну позицию. Их приходится применять довольно часто, потому что это равносильно умножению (соответственно, делению) на 2. Для того чтобы крайние разряды не терялись при сдвиге, используют разновидности этих операций, "сдвиг через перенос" — roi и гог. Они учитывают флаг переноса с, и через него можно перенести в другой регистр значения крайних разрядов. Например, в результате выполнения последовательности команд
lsl rl rol r2
регистр ri будет умножен на 2, а старший его разряд (неважно, ноль он или единица) окажется в младшем разряде г2, Причем обратите внимание, что г2
при этом также умножается на 2, и, следовательно, умножение 16-битового числа будет выполнено полностью корректным способом. Если последовательно применять команду rol к одному регистру, то мы получим т. н. циклический (кольцевой) сдвиг, когда биты некоего девятибитового (с учетом бита переноса) числа будут двигаться по кругу. Кроме упомянутых, есть еще команда "арифметического" сдвига as г, которая осуществляет деление на два, но не трогает старшего (седьмого) бита — она применяется в случаях, когда этот бит несет знак числа. Интересно также, что команда сложения регистра самого с собой (add х, х) не только осуществляет ту же самую операцию, что команда lsi х, но даже совпадает с ней по коду операции — типичная ситуация для AVR, где более трети команд представляют собой синонимы.

Команды арифметических операций
Арифметические операции в 8-разрядном контроллере— развлечение для настоящих любителей трудностей. Контроллеры, тяготеющие к CISC-архитектуре (вроде семейства л;51), изначально предоставляют программисту встроенные команды всех операций, вплоть до аппаратного деления, но на самом деле это мало помогает: кому требуется делить и перемножать числа, если и операнды и результаты ограничены, скудным диапазоном в пределах одного байта, а с учетом знака— в пределах всего семи двоичных разрядов? Требуется расширить диапазон хотя бы до 16 разрядов (хотя для операций с "плавающей точкой" и этого недостаточно).

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

Не вызывают никаких возражений только очевидные операции: add RI,R2 (сложить два регистра, записать результат в первый) и sub RI,R2 (вычесть второй из первого, записать результат в первый). Но если вдуматься, то и тут вопросов возникает множество: а что будет, если сумма превышает 255? Или разность меньше нуля? Куда девать остатки? Оказывается, все продумано — для учета переноса есть специальные команды — adc и sbc. Корректная операция сложения двух 16-разрядных чисел будет занимать две команды:
add RL1,RL2 adc RH1,RH2
5 Зак. 400

Здесь переменные RLI И RL2 содержат младшие байты слагаемых, a RHI И RH2 — старшие. Если при первой операции результат превысит 255, то перенос запишется во все тот же флаг переноса с и будет учтен при второй операции. Общий результат окажется в паре RHI : RLI. Совершенно аналогично выглядит операция вычитания.
Постойте, но мы же вовсе не стремились складывать 16-разрядные числа! Мы хотели всего лишь сделать так, чтобы в результате сложения 8-разрядных чисел получился правильный результат, пусть он займет два байта. На что нам тогда старший байт второго слагаемого, если его вообще в природе не существует, и для представления результата он также не требуется? Конечно, можно сделать его фиктивным, загрузив в некий регистр нулевое значение, но это только кажется, что регистров у AVR много — аж 32 штуки, на самом деле они довольно быстро расходуются под переменные и разные другие надобности, и занимать целый регистр под фиктивную операцию, пусть даже на один раз— как-то некрасиво. Потому "экономная" операция сложения 8-разрядных чисел будет выглядеть так:
add RL1,R2 brcc add_8 inc RHI add 8:

Исходные слагаемые находятся в RLI и R2, а результат будет, как и ранее, в RH1:RL1. Отметим, что в старшем байте (RH1) в результате может оказаться только либо 0, либо 1 — сумма двух восьмиразрядных чисел не может превысить число 510 (255 + 255), именно потому флаг переноса С представляет собой один-единственный бит в регистре флагов. Команда brcc является операцией перехода по условию, что флаг переноса равен нулю (BRanch if Carry Cleared). Таким образом, если флаг не равен нулю, выполнится операция inc RHI — увеличение значения регистра RH1 на единицу, в противном случае это действие будет пропущено. Можно придумать и другие способы программирования этой операции.

Внимательный читатель, несомненно, уже заметил неточность в программе— а чему равно значение RH1 до выполнения нашей процедуры? Вдруг оно совсем не ноль, и тогда нельзя говорить о корректном результате. Поэтому правильней было бы либо дополнить нашу процедуру еще одним оператором, который расположить раньше всех остальных: clrRHl, либо вместо команды inc применить какой-то из способов загрузки значения 1 в переменную RH1 (например, ldi RH1,1).

Заметим, что во всех случаях процедура разрастается во времени — было две команды, стало четыре, причем тут имеется замедление еще и неявного порядка — если все представленные арифметические команды выполняются за один такт, то команда ветвления brcc — может за такт (если с = l), а может и за два (если с = о). Итого мы выиграли один регистр, а потеряли два или три такта. И так всегда— есть процедуры, оптимизированные по времени, есть — по числу команд, а могут быть — и по числу занимаемых регистров. Идеала, как и везде в жизни, тут не добиться, приходится идти на компромисс.



     
 

Библиотека программиста. 2009.
Администратор: admin@programmer-lib.ru