Глава 5 Набор команд
Написание программы в чем-то сродни постройке дома. Имея в наличии определенные стройматериалы, строитель просто укладывает их вместе в нужном порядке. Разумеется, для этого он должен иметь соответствующие знания и навыки, ведь нарушение технологии может привести к тому, что крыша дома будет протекать, а сам дом будет продуваться всеми ветрами и даже может развалиться!
Можно проектировать дом одновременно с его постройкой. В принципе, если вы строите простую хижину, это вполне допустимо. Однако очевидно, что при таком подходе к строительству полученный в результате дом не сможет очень долго защищать владельца от дождя, а также не будет экономичным, ремонтопригодным, эргономичным, да и просто красивым. Гораздо лучше нанять архитектора, чтобы он спроектировал здание до того, как начнется строительство. Разумеется, этот проект будет в достаточной степени абстрактным, хотя лучше, если проектировщик имеет представление о технических характеристиках и стоимости имеющихся строительных материалов.
К сожалению, очень многие программируют «на бегу», практически не задумываясь о составлении сколько-нибудь подробного проекта. В области программного обеспечения термин «проектирование» означает написание алгоритма и разработку необходимых структур данных. И опять же лучше, если разработчик алгоритма будет учитывать «кирпичики», из которых будет построена программа. В нашем случае такими кирпичиками являются машинные команды.
В примерах, приведенных в данной главе, большей частью затрагиваются именно вопросы кодирования (строительства). В последующих главах мы познакомимся с более развитыми структурами, которые облегчат нам жизнь на этом этапе разработки программы. Кроме того, мы сможем попрактиковаться в разработке алгоритмов и структур данных.
Можно провести параллель между написанием программы и приготовлением кулинарного блюда. Для любой плиты, будь то микроволновая печь или электроплита, имеется свой набор операций. Эти операции — к примеру, выпаривание, обжаривание, кипячение — по своей сути аналогичны набору команд, которые могут быть выполнены центральным процессором. Различные ингредиенты, которые могут использоваться во время этих операций, являются данными команд. Эти данные могут находиться как во внутренних регистрах процессора, так и во внешней памяти. Причем исполнительный адрес операнда может задаваться несколькими различными способами. Эти способы называются режимами адресации.
В соответствии с RISC-подобной философией микроконтроллеров PIC ядро среднего уровня имеет всего 33 команды плюс две устаревшие команды, доставшиеся в наследство от младшего семейства, которые мы не будем рассматривать. Каждая команда представляет собой 14-битное слово, в котором содержится собственно код операции (КОП), адрес или значение операнда, а также бит адресата результата операции. Некоторые из этих команд и режимов адресации мы уже рассмотрели в главе 3 при обсуждении нашего компьютера BASIC. Теперь же пришла пора полностью разобраться с этим материалом. Так что в этой главе мы подробно рассмотрим различные режимы адресации и все имеющиеся команды.
Прочитав эту главу, вы:
• Узнаете, что режим адресации предназначен для точного указания местонахождения данных команды.
• Поймете, каким образом адресуется операнд команды при различных режимах адресации.
• Поймете, каким образом формат слова команды влияет на использование этих команд.
• Разберетесь, каким образом бит IRP регистра STATUS позволяет процессору обращаться ко всей памяти данных с использованием косвенной адресации.
• Узнаете, что наиболее часто используемыми командами являются команды пересылки данных, предназначенные для копирования данных между рабочим регистром и памятью данных.
• Узнаете, что процессор может выполнять базовые арифметические операции, такие как сложение, вычитание, инкрементирование, декрементирование и изменение битов.
• Научитесь выполнять сравнение данных и их проверку на определенное значение с выполнением требуемых действий в зависимости от результата.
• Узнаете, что данные в памяти данных можно циклически сдвигать через флаг переноса С.
• Научитесь использовать четыре базовых логических операции для того, чтобы выполнять инвертирование, установку, сброс, смену значения, проверку состояния битов и сличение данных.
• Поймете, как можно управлять ходом программы в зависимости от состояния любого бита или нулевого значения в регистре данных.
Подавляющее большинство команд оперирует данными, находящимися либо во внутренних регистрах ЦПУ, либо вне его (в памяти данных или памяти программ). Так что в 14-битном слове команды должно быть поле, информирующее дешифратор команд о том, где расположены эти данные. Исключение из указанного правила составляют несколько команд с адресацией кодом самой команды, такие как nop (нет операции) и return (возврат из подпрограммы). Прежде чем перейти к изучению собственно команд, мы рассмотрим различные методы, используемые для указания местоположения любых операндов.
В общем случае все команды записываются следующим образом:
мнемоника <операнд А>,<операнд В>
где операнд-А — исходные данные или их местоположение, а операнд В — адресат команды. Например, команда movf h’20’,w (копировать регистр данных) копирует содержимое источника (регистр h’20’) в приемник (рабочий регистр).
Существует несколько вариантов такой записи. Наиболее часто встречаются команды с 2.5 операндами. Например, команда addwf [регистр], d прибавляет содержимое рабочего регистра W к содержимому указанного регистра данных и помещает результат либо в W, либо обратно в регистр данных. Так, команда addwf h’20’, w означает «прибавить содержимое W к содержимому регистра h’20’ и записать результат в регистр h’20’». Коротко это можно записать как [f20] <- W + [f20], где квадратные скобки означают «содержимое», а стрелка — «становится». Такой тип нотации называется языком регистровых передач (Register Transfer Language — RTL).
Разумеется, эта команда не является трехоперандной в полном смысле этого слова, поскольку в качестве адресата может использоваться только один из источников, т. е. либо рабочий регистр, либо регистр данных. В некоторых командах указывается только один операнд-адресат, например clrf h’20’, a y команд с самоадресацией вообще нет явных операндов.
Все команды можно разделить по используемому способу адресации.
Адресация кодом команды
Такие команды, как clrwdt (сброс сторожевого таймера), retfie (возврат из прерывания), nop (нет операции), return (возврат из подпрограммы) и sleep (переход в «спящий» режим), не используют операнды из памяти. У всех этих команд в старших семи битах слова команды присутствуют нули. Например, команда clrwdt имеет машинный код Ь’00000000000100’.
Адресация константы
В командах, работающих с константами, младшие 8 бит кода команды используются для указания операнда-источника, являющегося в данном случае константой, а не байтом в регистре данных. Например, команда addlw 06 кодируется как b’11111000000110’. Операндом-адресатом в командах такого типа всегда является рабочий регистр, что и отражено в мнемоническом обозначении. Так, в нашем примере сумма W + 6 копируется обратно в рабочий регистр W. На языке регистровых передач эта операция выражается как W <- W + #6, где символ «#» (диез, или решетка) указывает, что стоящее после него число является константой, а не адресом регистра данных.
В некоторых процессорах такая разновидность адресации называется непосредственной, поскольку значение данных доступно непосредственно, без обращения к памяти.
Абсолютная адресация памяти программ
В микроконтроллерах PIC предусмотрены две команды, которые позволяют программе перейти к другой команде, находящейся в любом месте памяти программ. Этими командами являются команды goto и call (вызов подпрограммы, см. главу 6). В микроконтроллерах с 14-битным ядром под этот адрес[80] в коде команды выделяется 11 бит. Так что машинный код команды goto h’400’ будет равен Ь’10110000000000’. Аналогично call h’530’ — b’10010100110000’.
Используя этот 11-битный адрес, можно непосредственно адресовать любую команду в памяти программ объемом до 211 = 2 Кбайт. Однако в микроконтроллерах среднего уровня реализован 13-битный счетчик, который может адресовать память данных объемом до 8 Кбайт (память такого объема имеется, например, в модели PIC16F877). Для разрешения этой ситуации при выполнении команд goto и call абсолютный 11-битный адрес объединяется с битами 4:3 регистра защелки PCLATH, формируя таким образом полный 13-битный адрес, загружаемый в счетчик команд. Этот процесс показан на Рис. 5.1 (см. также Рис. 4.8 на стр. 103).
Рис. 5.1. Формирование 13-битного адреса памяти программ из 11-битного абсолютного адреса, передаваемого при вызове команд goto и call
При сбросе по включению питания регистр PCLATH сбрасывается, так что непосредственная область действия команды goto составляет h’000’…h’7FF’. Это соответствует диапазону адресов памяти программ объемом 2 Кбайт, имеющейся, например, в микроконтроллере PIC16F628. В моделях с большим объемом памяти программ необходимо использовать дальние переходы и вызовы (т. е. за пределы h’7FF’), используя биты PCLATH[4:3]. Например, в микроконтроллере PIC16F877 переход к адресу h’F00’ должен быть реализован следующим образом:
bsf PCALTH,3; Запишем в PCALTH[4:3] =11
bsf PCLATH,4;
goto h’F00’; Перейдем к требуемому адресу!
Обратите внимание, что после изменения этих битов они остаются в данном состоянии до следующего изменения, поэтому необходимо быть внимательным при переносе такого кода на процессор с памятью программ больше 2 Кбайт[81].
Прямая адресация памяти данных
Большинство данных, используемых программой, размещаются в памяти данных. Соответственно, этот режим адресации используют команды, в которых источник и/или адресат находятся в регистрах памяти данных. Адрес регистра содержится в семи младших битах кода команды (обозначенных как fffffff). Например, код команды addwf h’26’,f (сложить содержимое рабочего регистра с регистром данных h’26’ и поместить результат обратно в регистр данных, или, коротко, [f] <- [f] + [W]) выглядит как Ь’00011110100110’.
Большинство команд, использующих прямую адресацию, могут пересылать результат либо в рабочий регистр, либо обратно в регистр данных. Бит 7 кода команды, помеченный как d (см. также Рис. 3.5 на стр. 68), используется для указания адресата, как в следующем примере:
addwf h’26’,w; Код команды — 00 0111 0 0100110
addwf h’26’,f ; Код команды — 00 0111 1 0100110
В обоих случаях содержимое регистра с адресом h’26’ прибавляется к содержимому рабочего регистра. В первом случае, приведенном на Рис. 5.2, а, результат помещается в рабочий регистр, оставляя содержимое регистра данных неизменным (d = 0), тогда как во втором случае, показанном на Рис. 5.2, б, исходное содержимое регистра данных замещается (d = 1) итоговой суммой.
Как мы увидим далее (см., например, Табл. 5.2), большинство команд используют непосредственную адресацию. Однако у этого метода адресации есть два ограничения, которые программист должен иметь в виду.
Всего 7 бит
Под адрес регистра данных в коде команды среднего семейства отведено всего семь битов, соответственно, используя прямую адресацию, можно обращаться только к регистрам из диапазона h’00’…h’7F’. Из Рис. 4.7 (стр. 97), а также Рис. 5.3 видно, что для обхода этого ограничения в микроконтроллере PIC16F84 в качестве суррогатного старшего бита адреса используется 5-й бит (RP0) регистра STATUS. В результате память разбивается на два банка регистров, каждый объемом до 27 = 128 регистров. Для переключения между 0-м (RP0 = 0) и 1-м (RPO = 1) банками этот бит управления страницами, расположенный в 5-м бите регистра STATUS (Рис. 4.6 на стр. 95), можно менять точно так же, как и любой другой бит регистра данных.
Рис. 5.2. Выбор операнда-результата в команде addwf h’26’
Особенность модели PIC16F84 заключается в том, что в ней имеется всего два банка памяти. Большинство микроконтроллеров среднего уровня имеют 4 банка памяти. В качестве примера можно назвать микроконтроллер PIC16F627/8 (усовершенствованный PIC16F84), структура памяти данных которого приведена на Рис. 5.4. Чтобы иметь возможность переключаться на любой банк памяти, требуется уже два бита управления страницами, показанных на Рис. 5.5. Эти два бита RP1:RP0, выделенные на рисунке серым цветом, обнуляются при сбросе любого типа, т. е. после сброса мы всегда работаем с 0-м банком памяти. Поэтому программист должен соответствующим образом изменить эти биты, если он хочет обратиться к регистру, находящемуся в другом банке. Например, если необходимо скопировать содержимое регистра h’120’, расположенного во втором банке, в рабочий регистр и переключиться обратно на 0-й банк, мы должны написать:
bcf STATUS,6; Устанавливаем RP1 (6-й бит) в 1
bcf STATUS,5; Сбрасываем RP0 (5-й бит) в 0
movf h’120’,w; Копируем содержимое регистра h’120’ в W
bcf STATUS,6; Сбрасываем RP1 (возвращаемся к 0-му банку)
Примером такой интенсивной работы с банками памяти может служить Программа 15.4, приведенная на стр. 552.
Если программист забудет изменить биты RP1:0 перед выполнением команды movf h’120’, то в рабочий регистр будет скопировано содержимое из регистра данных h’020’ (полагая, что процессор находится в нулевом банке), поскольку в коде команды будет записано только семь младших битов адреса Ь’(01)0100000’ (h’120’)! Ассемблер, однако, выдаст предупреждение, вид которого показан на стр. 99.
Чтобы избежать слишком частого переключения банков памяти, все регистры общего назначения (РОН) микроконтроллера PIC16F84 отображены на оба банка, как показано на Рис. 5.3.
Рис. 5.3. Память данных микроконтроллера PIC16F84
Подобное зеркалирование всех регистров встречается достаточно редко — чаще отображают небольшую группу регистров. Например, в моделях PIC16F627/8 предусмотрена общая область из 16 РОН, отображенных на все четыре банка (Рис. 5.4). Например, регистры данных с адресами h’070’, h’0F0’, h’170’ и h’1F0’ являются одним и тем же регистром. Переменные, которые могут потребоваться при работе с различными банками, по возможности следует размещать в этом общем пуле регистров. В общей же сложности в данных моделях имеется 224 уникальных РОН.
Рис. 5.4. Память данных микроконтроллера PIC16F627/8
Некоторые из наиболее часто используемых регистров специального назначения (РСН) тоже отображены на все банки, например регистр STATUS. Поэтому в приведенном выше примере мы могли изменять биты RP1:0 и возвращаться в 0-й банк, даже находясь во 2-м банке.
Рис. 5.5. Обобщенный формат регистра STATUS микроконтроллеров с 14-битным ядром
Фиксированные адреса
Будучи составной частью кода команды, 7-битный адрес операнда является фиксированным и поэтому не может быть изменен во время выполнения программы. Хотя явное задание этих адресов может показаться очевидным способом для указания местоположения объекта в памяти данных, существует ряд ситуаций, в которых такое ограничение слишком неудобно.
В качестве примера, иллюстрирующего эту недостаточную гибкость, предположим, что мы хотим очистить содержимое всех регистров данных 0-го банка модели PIC16F627/8, т. е. регистров h’20’…h’7F’. Очевидным решением этой задачи будет многократное (96 раз) использование команды clrf (очистка регистра), как показано в Программе 5.1.
Программа 5.1. Очистка группы регистров с использованием прямой адресации
CLEAR_ARRAY
clrf h’20’; Очищаем регистр 32
clrf h’21’; и 33
clrf h’21’; Каждая команда clrf
clrf h’23’; занимает одну ячейку
clrf h’24’; в памяти программ
clrf h’25’; Очищаем регистр 37
clrf h’26’; и так далее
... ...
clrf h’7E’; Очищаем регистр 126; еще чуть
clrf h’7F’; Очищаем регистр 127; уф-ф!
Несмотря на то что этот код вполне работоспособен, он чрезвычайно неэффективен. Каждая из 96 команд выполняет одну и ту же операцию, хотя и для другого адреса. Если нам потребуется очистить все 244 РОН, то придется выполнить 224 команды clrf, и все для того, чтобы выполнить эту простейшую задачу. Поскольку в памяти программ микроконтроллера PIC16F627 имеется всего 1024 ячейки, такое решение займет более 20 % памяти.
Должен быть лучший способ!
Косвенная адресация памяти данных
В любом процессоре имеется одна из разновидностей косвенной адресации, при которой один или более внутренних регистров используются для хранения адреса операнда в памяти данных. Такие адресные или индексные регистры используются в качестве указателя на данные. Основное отличие от прямой адресации заключается в том, что содержимое регистра-указателя может изменяться в процессе выполнения программы. То есть искомый адрес уже не зафиксирован в виде двоичного кода в памяти программ (обычно ПЗУ), а является переменной величиной. Например, для очистки массива данных из Программы 5.1 можно использовать цикл, инкрементируя в каждом проходе цикла регистр, указывающий на очищаемый регистр.
В микроконтроллерах PIC реализован достаточно простой вариант такого типа адресации — в полном соответствии с их философией. В младшем и среднем семействах[82] имеется отдельный элемент ИЛИ-HE, который детектирует обращение по прямому 7-битному адресу Ь’0000000’ и, как показано на Рис. 5.6, просто выставляет на шину адреса памяти данных содержимое регистра h’04’, называемого индексным регистром (FSR). Это происходит, если в качестве адресата команды используется нулевой адрес, по которому располагается регистр косвенной адресации INDF. Этот регистр является виртуальным, т. е. физически не существует. Он используется исключительно для выставления содержимого регистра FSR в качестве адреса операнда. Хотя такая реализация косвенной адресации может показаться довольно ущербной, она использует очень простую дополнительную логику и не требует для работы дополнительных тактов, в отличие от альтернативных способов, реализованных в других процессорах и микроконтроллерах.
Рис. 5.6. Механизм косвенной адресации
В качестве простого примера предположим, что содержимое регистра FSR равно h’86’.Тогда команда clrf 0 (или clrf INDF) очистит регистр, расположенный по адресу h’86’, а не по адресу h’00’! Разумеется, содержимое регистра FSR можно изменить в любой момент времени, например, его можно инкрементировать в каждом проходе цикла, как в Программе 5.2.
Давайте в качестве примера перепишем Программу 5.1, заменив линейную структуру циклом, как показано на Рис. 5.7.
Рис. 5.7. Использование цикла для очистки массива данных
Теперь наша программа будет работать по следующему алгоритму, представляющему собой перечень задач:
1. Установить указатель FSR на начало массива.
2. Очистить адресуемый регистр данных, указав в качестве адресата регистр данных h’00’.
3. Инкрементировать указатель FSR.
4. Проверить, не достигли указатель конца массива, в нашем случае — адреса h’80’. Если нет, то перейти к пункту 2.
5. Продолжить выполнение программы.
Визуально этот процесс представлен на Рис. 5.8.
Рис. 5.8. Проход массива
Код, соответствующий этому алгоритму, приведен в Программе 5.2. Линейная структура предыдущей программы была преобразована в цикл, тело которого выделено серым цветом. Очистку регистров по-прежнему выполняет команда clrf, которая «проходит» по массиву, начинающемуся с адреса h’20’. При каждом проходе цикла указатель в регистре данных h’04’ инкрементируется. В конце концов содержимое регистра FSR выйдет за границу заданного диапазона, в результате чего программа выйдет из цикла и продолжит выполнение следующей секции кода.
Программа 5.2. Очистка группы регистров с использованием косвенной адресации
В Программе 5.2 имеется много других особенностей, так что нам еще придется вернуться к рассмотрению набора команд.
Задача 1
Регистр FSR инициализируется адресом первого очищаемого регистра данных путем записи константы h’20’ в рабочий регистр W (movlw h’20’) с последующим копированием W в регистр h’04’ (movwf FSR). Как видно, в наборе команд отсутствует отдельная команда непосредственного копирования константы в регистр данных. Практически все циклы требуют инициализации перед входом в них.
Задача 2
Основная команда очистки регистра использует косвенную адресацию, указывая в качестве адресата фантомный регистр h’00’ (INDF) — clrf INDF. Эта строка помечена меткой СLOOP. Ассемблер понимает, что это именно метка, а не команда, поскольку она начинается с самой левой позиции строки исходного файла. Строки без меток должны начинаться с отступа хотя бы в один пробел.
Задача 3
При каждом проходе цикла указатель увеличивается на единицу. Эта операция осуществляется командой incf FSR,f. Обратите внимание, что в качестве адресата указан сам регистр памяти данных, а не рабочий регистр W.
Задача 4
Если вы не собираетесь крутиться в этом цикле бесконечно, то вам потребуется механизм для выхода из него. В нашем случае для этого используется сравнение содержимого регистра FSR с константой h’80’, т. е. адресом первого регистра, находящегося вне заданного диапазона. Сравнение осуществляется копированием содержимого регистра FSR в W (movf FSR,w) и последующим вычитанием рабочего регистра из константы h’80’ с использованием команды addlw — h'80' (прибавление отрицательного числа). Если эти числа равны, то флаг Z будет установлен, в результате чего команда btfss STATUS, Z (см. стр. 133) пропустит следующую за ней команду goto CLOOP. До наступления этого события команда goto будет передавать управление на начало цикла, и процесс будет повторяться с FSR, указывающим наследующий сбрасываемый регистр данных.
В итоге вариант программы с циклом состоит из 8 команд против 96 в линейном варианте, т. е. размер программы уменьшился в 12 раз. Однако наша новая программа выполняется в 7 раз дольше из-за наличия различных команд, необходимых для организации цикла и выполняющихся 96 раз! Обычно затраты на накладные расходы не так велики, как в данном примере.
* * *
Наличие регистра FSR, хранящего адрес операнда, означает, что у нас теперь есть 8-битный изменяемый адрес для обращения к памяти данных вместо фиксированного 7-битного. В свою очередь, из этого следует, что при работе с памятью данных, имеющей два банка (аналогичной приведенной на Рис. 5.3), к любому регистру можно обратиться откуда угодно. Например, если мы хотим записать число b’01111111 в регистр данных h’86’ (регистр специального назначения TRISB, расположенный в 1-м банке), то вместо кода, приведенного на стр. 105, мы можем написать:
movlw h’86’; Настроим FSR для работы
movwf FSR; с регистром h’86’(TRISB)
movlw b01111111; Маска
movwf 0; Записываем ее в указываемый регистр
При этом нам не придется возиться с битом переключения страниц RP0. Если необходимо часто обращаться к какому-либо из регистров первого банка, то можно записать в FSR адрес этого регистра и больше регистр FSR не трогать. Разумеется, предполагается, что он не требуется для других целей[83].
В моделях с четырьмя банками памяти требуется дополнительный бит для образования 9-битного адреса. Бит IRP регистра STATUS, формат которого показан на Рис. 5.5, позволяет косвенно адресовать банки 0/1 (IRP = 0, состояние по умолчанию) и банки 2/3 (IRP = 1). Например, ранее написанный код для копирования содержимого регистра h’120’ банка 2 (PIC16F627/8) в рабочий регистр W, приведенный на стр. 119, можно переписать следующим образом:
bsf STATUS,7; Установим бит IRP (банки 2/3)
movlw h'120'; Инициализируем указатель в FSR
movwf FSR;
rnovf 0,w; Копируем содержимое регистра, указываемого FSR, в W
bcf STATUS,7; Сбрасываем IRP (банки 0/1)
Поскольку в регистре W могут находиться только 8-битные значения, старший бит адреса при выполнении команды movlw h’120’ будет отброшен, т. е. в регистр W будет записано число h’20’. Роль отсутствующего девятого бита выполняет бит IRP, установленный в 1, поэтому обращение произойдет к регистру h’120’, что и требовалось. Ассемблер, возможно, выдаст предупреждение, что в регистр W записывается слишком большое значение. Это предупреждение можно игнорировать.
Битовая адресация
Четыре команды (на что указывают два бита, помеченные знаками «??») предназначены либо для изменения, либо для проверки состояния отдельных битов в регистре данных. В этом случае в коде команды имеется 3-битное поле NNN, предназначенное для хранения позиции бита (0…7), тогда как адрес регистра кодируется обычным образом. Так, машинный код команды bcf h’20’,7 (сбросить бит 7 в регистре h’20’) выглядит как Ь’0100111010000’. Остальными командами этой группы являются команда bsf (установить бит регистра данных, код 01), btfsc (проверить состояние бита и пропустить следующую команду, если он сброшен, код 10) и btfss (проверить состояние бита и пропустить следующую команду, если он установлен, код 11). Последнюю из перечисленных команд мы уже использовали в Программе 5.2 для проверки 2-го бита регистра h’03’ (т. е. флага Z регистра STATUS) и выходили из цикла, если условие было истинно.
Пока что мы классифицировали команды по способу, которым они определяют местоположение своих операндов. Однако чаще используется деление команд по выполняемым функциям. С этой точки зрения все 33 команды микроконтроллеров PIC с 14-битным ядром можно разбить на 6 групп, четыре из которых будут рассмотрены в этой главе. Команды, относящиеся к подпрограммам и прерываниям, будут описаны в 6-й и 7-й главах, а управляющим командам, связанным с функционированием микроконтроллера, посвящена глава 10.
В таблицах команд, приводимых далее, в левом столбце приводятся мнемонические обозначения команд. Затем указывается влияние данной команды на три флага регистра STATUS, причем символ «» соответствует отсутствию какого-либо изменения, а символ «√» — нормальному воздействию. В последнем столбце приводится краткое описание операций, выполняемых командой. Полностью набор команд приведен в Приложении Г. Если вам потребуется более подробное описание, его можно найти в документации на любой микроконтроллер PIC соответствующего семейства (см. сайт, посвященный оригинальному изданию данной книги). Однако, в связи с тем что микроконтроллеры PIC имеют RISC-архитектуру, команд достаточно мало, и они простые.
Команды пересылки данных
Почти треть всех команд в любой компьютерной программе, независимо от оборудования, на котором она выполняется, используются для простой пересылки данных между памятью и внутренними регистрами. С учетом этого в Табл. 5.1 приведены наиболее часто используемые команды PIC.
Все три команды пересылки используются либо для простого копирования однобайтного значения между рабочим регистром и указанным регистром данных, либо для загрузки константы в рабочий регистр. При этом исходные данные не изменяются, они просто копируются в регистр-адресат. Команда swap тоже копирует содержимое регистра данных в W, однако при этом меняет местами младший и старший полубайты.
∙ movlw
Эта команда заносит указанную 8-битную константу в рабочий регистр W. Например, команда movlw h’80’ инициализирует W значением b’10000000’.
Заметьте, адресатом в этой команде всегда является рабочий регистр, поэтому для инициализации регистра данных какой-либо константой требуется дополнительная операция — см. ниже.
∙ movwf
Эта команда предназначена для копирования (сохранения) содержимого рабочего регистра в регистре данных. Например, команда movwf h’23’ скопирует байт из W в регистр h’23’.
Таким образом, для инициализации регистра h’23’, скажем, числом Ь’10000000’, необходимо выполнить следующие операции:
movlw h’80’; Заносим в W число Ь’10000000’
movwf h’23’; и копируем его в регистр h’23’
∙ movf
Эта команда предназначена для копирования (загрузки) содержимого любого регистра данных в рабочий регистр W. Например, команда movf h’22’, w загрузит в W содержимое регистра h’22’.
Вообще говоря, в качестве адресата данной команды можно указать сам регистр данных, в результате чего мы выполним, как может показаться, бессмысленную операцию. В нашем случае это будет команда movf h’22’,f, которая скопирует содержимое регистра h’22’ в него же! Однако команда movf воздействует на флаг нуля Z (это единственная команда из Табл. 5.1, воздействующая хоть на какой-то флаг регистра состояния), который установится, если содержимое регистра равно нулю. Команда movf [File],f не изменяет содержимое указанного регистра, поэтому ее можно использовать для проверки регистра на нулевое значение, т. е. в качестве отсутствующей команды tstf [File],f, имеющейся во многих других микроконтроллерах и микропроцессорах. Таким образом, мы можем проверить содержимое любого регистра данных с помощью одной-единственной команды. Вариант проверки рабочего регистра на нулевое значение приведен на стр. 141.
Принимая во внимание тот факт, что большинство команд, оперирующих регистрами в памяти данных, позволяют задавать в качестве адресата либо тот же регистр, либо рабочий регистр W, операцию пересылки можно рассматривать как неявного члена этой группы команд. Например, в зависимости от ситуации операция инкрементирования регистра данных с последующим копированием нового значения в рабочий регистр W может быть записана следующим образом:
incf h’22’,f; Инкрементируем содержимое регистра h’22’
movf h’22’,w; и копируем его в W
либо так:
incf h’22’,w; Копируем инкрементированное содержимое регистра h’22’ в W
Разумеется, в последнем случае содержимое регистра данных не изменяется.
∙ swapf
Команда swapf переставляет местами старший и младший полубайты содержимого регистра данных и помешает результат либо в тот же регистр данных, либо в рабочий регистр. Например, команда swapf h’22’,w выполнит операцию:
Команда swapf полезна в тех случаях, когда полубайты регистра используются для хранения BCD-чисел, однако может использоваться и для копирования содержимого регистра данных в W. В отличие от более понятной команды movf [File],w состояние флага Z при этом не изменяется. Недостатком, конечно же, будет перестановка полубайтов местами при копировании.
В Программе 7.2 на стр. 226 команда swapf используется именно с этой целью.
Команды арифметических операций
Процессоры микроконтроллеров PIC младшего и среднего уровня помимо сложения и вычитания могут выполнять и другие арифметические операции. В качестве примера можно отметить операции сброса, инкрементирования и декрементирования. В Табл. 5.2 также перечислены команды, устанавливающие или сбрасывающие заданный бит в указанном регистре данных.
Сложение и вычитание
В микроконтроллерах PIC имеется две команды сложения.
∙ addlw
Эта команда позволяет прибавить 8-битную константу к рабочему регистру W. Например, команда addlw b’10101010’:
∙ addwf
Эта команда прибавляет переменную из памяти данных к содержимому рабочего регистра W. В отличие от команды addlw, в качестве адресата может использоваться как W, так и исходный регистр данных. Например, addwf h’26’,f:
Очистка
Как рабочий регистр, так и любой регистр данных могут быть сброшены. В обоих случаях при выполнении этой операции будет установлен флаг Z, индицирующий нулевой результат операции.
∙ clrw
Эта команда очищает рабочий регистр. По своему действию она эквивалентна команде movlw 0.
∙ clrf
С помощью этой команды можно очистить содержимое любого регистра данных. Например, clrf h’26’:
Обе команды сложения, да и вообще все команды, оперируют 8-битными операндами. Тем не менее можно обрабатывать операнды любой длины, если делать это побайтно. В случае сложения, например, нам потребуется складывать попарно соответствующие байты операндов (от младшего байта до старшего) с добавлением бита переноса, полученного при сложении n-х байтов, к (n + 1) — й сумме. Входной перенос при сложении младшего байта равен нулю, а перенос из старшего байта становится старшим битом результата. Например, h’FFFF’ + h’FF = h’100FE’ (65 535 + 255 = 65 790).
Чтобы проиллюстрировать этот процесс, напишем программу, складывающую 8-битное число с 16-битным и получающую в результате 17-битную сумму. Первое слагаемое, как показано на Рис. 5.9, размещается в двух ячейках памяти данных с адресами h’20’ (старший байт) и h’21’ (младший байт). Сумма сохраняется в трех ячейках с адресами h’30’ (старший байт), h’31’ (средний байт) и h’32’ (младший).
Pис. 5.9. Сложение 16-битного числа с 8-битным
Составим перечень задач (алгоритм), учитывая, что нам нужно реализовать этот процесс в виде последовательности операций, выполняемых 1-байтными командами:
1. Прибавить младший байт первого слагаемого к младшему байту второго слагаемого — получим младший байт суммы и бит переноса С1 (Рис. 5.10, а).
2. Прибавить бит переноса С1 к старшему как байту первого слагаемого — получим средний байт суммы и новый бит переноса С2 (Рис. 5.10, б).
3. Старшим байтом суммы является последний бит переноса С2 — 0 или 1 (Рис. 5.10, в).
Поскольку это будет первая программа главы, все операции подробно показаны на Рис. 5.10. Во многих случаях подобная детализация совершенно бесполезна, а алгоритм в виде списка задач может быть дополнен более абстрактной блок-схемой.
Рис. 5.10. Визуализация процесса сложения
Прежде чем перейти к написанию программы, нам необходимо познакомиться с двумя командами (подробно мы их рассмотрим чуть позже). Команда incf позволяет нам непосредственно прибавлять единицу к содержимому любого регистра данных, а команда btfsc проверяет состояние конкретного бита заданного регистра данных и, если этот бит сброшен, выполняет пропуск следующей команды (см. Табл. 5.4). В нашем случае таким регистром является регистр h’03’ (регистр STATUS), а проверяемым битом — бит 0 (флаг переноса), т. е. команда будет записана как btfsc 5,0 или, более понятно, как btfsc STATUS,С. Мы уже использовали аналогичную команду btfss для проверки флага Z в Программе 5.2.
Все три указанные задачи помечены в листинге соответствующими комментариями.
Вводная часть
Всем регистрам с данными присвоены символические имена с помощью директивы equ. Как уже говорилось на стр. 105, использование осмысленных имен вместо голых адресов регистров дает в итоге более удобочитаемую программу. При этом уменьшается вероятность возникновения ошибок и облегчается отладка программы.
Задача 1
Младший байт 1-го слагаемого загружается в W, складывается со 2-м слагаемым, и результат сохраняется в памяти в качестве младшего байта суммы. При этом команда addwf изменяет соответствующим образом состояние флага С. К счастью, на его состояние не влияют последующие команды пересылки.
Задача 2
Старший байт 1-го слагаемого загружается в W. Если бит переноса С1 из предыдущей задачи равен 0, то команда прибавления единицы (addlw 1) пропускается, в противном случае производится инкрементирование содержимого W. Затем результат копируется в средний байт суммы.
Задача 3
Если бит переноса С2 из предыдущей задачи равен 1, то предварительно сброшенный старший байт суммы увеличивается до h’01’. Обратите внимание, что команда clrf SUM_U не воздействует на флаг переноса. Если С2 равен 0, то команда incf SUM_1,f пропускается и старший байт суммы остается нулевым.
Программа 5.3. Выполнение сложения с двойной точностью
AUGEND_H equ h’20’; Два регистра 1-го слагаемого
AUGEND_L equ h’21’
ADDEND_L equ h’22’; Второе слагаемое
SUM_U equ h’30’; Три регистра суммы
SUM_H equ h’31’
SUM_L equ h’32’
STATUS equ 3 ; Регистр STATUS расположен по адресу h’03’
С equ 0; Флаг переноса — 0-й бит регистра STATUS
; Задача 1 ---------------
DP_ADD
movf AUGEND_L,W; Берем младший байт 1-го слагаемого
addwf ADDEND_L,w; Прибавляем 2-е слагаемое, результат — в W
movwf SUM_L; Помечаем результат в младший байт суммы
; Задача 2 ---------------
movf AUGEiCD_H,w; Берем старший байт 1-го слагаемого
btfsc STATUS, С; Был ли перенос при предыдущем сложении?
addlw 1; ЕСЛИ да, ТО прибавляем единицу
movwf SUM_H; Помечаем в средний байт суммы
; Задача 3 ---------------
clrf SUM_U; Обнуляем старший байт суммы {не влияя ка флаг С)
btfsc STATUS, С; Был ли перенос при предыдущем сложении?
incf SUM_U,f; ЕСЛИ да, ТО старший байт суммы равен 01
... ...
В Программе 5.3 следует обратить внимание на два момента:
1. Ни одна из команд программы, за исключением команд сложения, не влияет на состояние флага С. Благодаря этому флаг С можно проверить с помощью команды btfsc даже через две команды после выполнения операции сложения.
2. Команды, следующие после каждой команды btfsc, имеют отступ на один пробел больше, чем остальные. Увеличенный отступ просто подчеркивает, что выполнение этого блока необязательно, т. е. он может быть пропущен. Ассемблер все эти украшательства игнорирует!
Команды инкрементирования и декрементирования
Содержимое любого регистра данных можно увеличить или уменьшить на единицу.
∙ incf
Эта команда увеличивает на единицу содержимое заданного регистра данных, помещая результат либо обратно в исходный регистр, либо в рабочий регистр W.
∙ decf
Эта команда уменьшает на единицу содержимое заданного регистра данных, помещая результат либо обратно в исходный регистр, либо в рабочий регистр W. Например, если в регистре h’26’ было записано число h’64’, то после выполнения команды decf h’26’,f в нем окажется число h’63’.
Если в качестве адресата указать рабочий регистр (decf h’26’, w), то содержимое регистра h’26’ останется равным h’64’, а содержимое рабочего регистра станет равным h’63’.
* * *
Хочу обратить ваше внимание на то, что обе эти команды не влияют на флаг переноса С в отличие от эквивалентных команд прибавления или вычитания единицы[84]. В частности, это означает, что если вы собираетесь инкрементировать 3-байтное число, хранящееся в формате , то просто инкрементировать младший байт и проконтролировать переносы в старшие байты не получится. В следующем фрагменте программы используется команда btfss, которая пропускает команду, если 2-й бит регистра STATUS (флаг нуля Z) установлен в 1.
incf LOWER,f; Прибавим единицу
btfss STATUS,Z; Результат равен нулю?
goto NEXT; ЕСЛИ нет (Z == 0), TO выходим
incf MIDDLE,f; ИНАЧЕ инкрементируем следующий байт
goto NEXT; ЕСЛИ нет (Z == 0), ТО выходим
incf UPPER,f; ИНАЧЕ инкрементируем следующий байт
NEXT
... ...; Прочий код
В приведенном фрагменте инкрементируется самый младший байт, и если он становится равным нулю (h’FF’ —> h’00’), то инкрементируется следующий байт и так далее для всех байтов. Эта последовательность прерывается, если при инкрементировании регистра получается ненулевое значение, например
h’06 FF FE’ h’06 FF FF —> h’07 00 00’.
Бит-ориентированные команды
Возможность сброса либо установки отдельного бита в любом регистре памяти данных очень важна, особенно при изменении различных РСН, управляющих процессором и его периферийными устройствами. Изменяемый регистр можно указать с помощью как прямой, так и косвенной адресации.
∙ bcf
Эта команда позволяет программисту сбросить в 0 любой из восьми битов указанного регистра памяти данных.
∙ bsf
Эта команда аналогична bcf, только указанный бит не сбрасывается, а устанавливается в 1. Например, при установке 5-го бита регистра h’26’ мы имеем
Одним из назначений данных команд является управление различными флагами и переключателями регистра STATUS. В коде, приведенном на стр. 119, мы уже использовали эти две команды для изменения состояния битов RPx (для переключения банков памяти данных). Ни одна из этих команд не воздействует на биты регистра STATUS. Однако важно понимать, что все команды, непосредственно изменяющие содержимое регистров данных, на самом деле считывают этот байт во временный регистр, выполняют соответствующую операцию (incf, bcf и т. д.), используя АЛУ, после чего помещают результат обратно в память данных. Такое поведение называется принципом чтение — модификация — запись и выполняется за один машинный цикл. Иногда такое функционирование может привести к неожиданным побочным эффектам (см., например, стр. 335).
Вычитание
В системе команд имеется две команды вычитания, операнды которых аналогичны командам сложения.
∙ subwf
Эта команда вычитает содержимое рабочего регистра из переменной, хранящейся в памяти данных. Как обычно, результат операции может быть помещен либо в рабочий регистр, либо обратно в исходный регистр данных. Например, при выполнении команды subwf h’26’,f происходит следующее:
Как мы уже обсуждали на стр. 95 и в Примере 4.2, приведенном на стр. 109, состояние флага переноса С равно дополнению к биту заема, возникающего при выполнении команд вычитания. Упущение из виду этого факта является одним из основных источников ошибок при написании программы!
∙ sublw
Команда sublw представляет собой еще один из источников ошибок, поскольку она вычитает содержимое рабочего регистра W из константы, а не наоборот, как можно подумать. Например, если в регистре W было, скажем, h’64’ (d’100’), то в результате выполнения команды sublw 1 вместо вычитания единицы получим 1 — h’64’ = h’9D’, что равно десятичному 157 (вообще говоря, это число — h’63’ в дополнительном коде). Лично я считаю, что из-за такой перевернутой реализации использование этой команды является неоправданным риском. В качестве альтернативы давайте посмотрим на команду addlw h’FF’. В нашем случае мы получим h’64’ + h’FF’ = h’(1)63’ (десятичное 99). Если игнорировать перенос, то получается, что 8-битный результат в W на единицу меньше исходного значения. Конечно же, зная о том, что h’FF’ является числом —1 в дополнительном коде, можно записать команду как addlw - 1, что будет более понятно. Более того, флаг С в данном случае равен 1 и, интерпретируя его как дополнение к биту заема, получаем, что заема не было.
В дальнейшем мы будем игнорировать команду sublw и использовать вместо нее addlw. Вообще-то мы уже так делали в Программе 5.2, где нам было нужно вычесть константу h’80’ из W. Ассемблер просто преобразует отрицательное число в его эквивалент в дополнительном коде, например, вместо addlw — 6 будет addlw h’FA’.
* * *
Одной из наиболее важных операций является операция сравнения двух чисел. С математической точки зрения это можно сделать при помощи вычитания байта (обозначаемого ниже как [f] и для регистра данных, и для константы) из содержимого рабочего регистра [W]. Результат [W] — [f] представляет реальную разность величин операндов. Однако в большинстве случаев достаточно определить отношение между величинами, т. е. узнать, не больше ли W, чем байт данных? Для этого необходимо контролировать состояния флагов С и Z регистра STATUS.
Рабочий регистр больше, чем байт данных… нет заема, не ноль
Рабочий регистр равен байту данных… ноль
Рабочий регистр меньше, чем байт данных… заем, не ноль
В нашем процессоре флаг С является дополнением к биту переноса, а флаг Z устанавливается при нулевом результате. Таким образом:
[W] больше, чем или равно [f]: [W] — [f] дает отсутствие заема (С = 1).
[W] равно [f]: [W] — [f] дает ноль (Z = 1).
[W] меньше, чем [f]: [W] — [f] дает заем (С = 0).
Эти варианты приведены на Рис. 5.11, где показано сравнение значения, находящегося в W, с содержимым регистра h’36’. Команда subwf h’36’,w формирует разность и изменяет флаги Z и С, как показано на рисунке. Собственно, разность двух чисел, находящаяся в W, нас не интересует, однако она перезаписывает исходное содержимое, которое может потребоваться сохранить перед сравнением.
Рис. 5.11. Сравнение содержимого W и регистра данных командой subwf h’26’
Рассмотрим следующий пример. Имеется топливная цистерна объемом 255 л, на дне которой установлен датчик, показывающий оставшееся количество топлива как линейную функцию от давления. Предположим, что значение выходного сигнала датчика представляется в виде байта, считываемого с порта В (см. стр. 105), который мы назовем FUEL. Нам нужно написать процедуру, которая будет включать световой сигнал «Пусто» (бит 0 порта А), если в цистерне осталось меньше 20 л, и включать звуковой излучатель (бит 1 порта А), если осталось меньше 5 л (см. Рис. 5.12). Активный уровень на обоих выходах — ВЫСОКИЙ. Эта задача может быть реализована следующим образом:
STATUS ecu 3; Регистр STATUS расположен по адресу h’03’
С equ 0; Флаг переноса — 0-й бит
Z equ 2; Флаг нуля — 2-й бит
FUEL equ 6; Уровень топлива можно считать из регистра h’06’ (порт В)
DISPLAY equ 5; Порт А — регистр h’05’
LAMP equ 0; Сигнальная лампочка управляется 0-м битом
BUZZ equ 1; Звуковой излучатель управляется 1-м битом
ALARM
bcf DISPLAY,BUZZ; Выключим пищалку
bcf DISPLAY,LAMP; Выключим лампочку
movf FUEL, w; Считываем значение уровня топлива в W
addlw -5; FUEL — 5. ЕСЛИ БОЛЬШЕ ИЛИ РАВНО,
btfss STATUS,С ; ТО заема не будет (С == 1), так что пропускаем
bsf DISPLAY,BUZZ; ИНАЧЕ включаем пищалку
movf FUEL,W; Снова считываем значение уровня топлива в W
addlw — d’20’; FUEL — 20. ЕСЛИ БОЛЬШЕ ИЛИ РАВНО,
btfss STATUS,С; ТО заема не будет <С == 1), так что пропускаем
bsf DISPLAY.LAMP; ИНАЧЕ включаем лампочку
NEXT:
... ...;
Рис. 5.12. Операции сравнения в системе контроля уровня топлива
После каждого вычитания флаг переноса будет равен 1 (т. е. нет заема), если число в рабочем регистре (количество топлива) больше или равно значению константы, с которым оно сравнивается посредством вычитания. Обратите внимание на использование команды bsf для установки соответствующих битов порта А (предполагается, что эти линии уже сконфигурированы как выходы). Точно так же команда bcf используется для выключения светового сигнала и звукового излучателя в самом начале процедуры.
К операциям сравнения можно отнести и операцию проверки, во время которой байт данных проверяется на равенство нулю. Мы уже видели (см. стр. 67), что содержимое любого регистра данных можно проверить на нулевое значение простым копированием его в себя самого, например movf h’36’,f. Если в регистре находится нулевое значение, то флаг Z установится в 1[85]. Аналогичная проверка рабочего регистра может быть выполнена прибавлением к нему нуля, т. е. addlw 0. Эта команда установит флаг Z при нулевом значении в рабочем регистре, не изменяя его содержимого.
Команды логических операций и операций сдвига
Микроконтроллеры PIC могут выполнять все четыре базовые логические операции — НЕ, И, ИЛИ и Исключающее ИЛИ, как показано в Табл. 5.3.
Операция НЕ
Логическая функция НЕ, показанная на Рис. 1.1 (стр. 26), инвертирует (формирует обратный код) логическое состояние входа.
∙ comf
С помошью этой команды можно инвертировать содержимое любого заданного регистра данных. Так, команда comf h’26’,f вычисляет обратный код содержимого регистра h’26’:
Как обычно, результат может быть помещен либо в исходный регистр данных, либо в W (в последнем случае исходное содержимое остается неизменным), например:
В микроконтроллерах PIC отсутствует команда типа comw для инвертирования содержимого рабочего регистра, однако эту операцию можно выполнить за один машинный цикл посредством вычитания W из числа b’11111111’, что дает в результате тот самый обратный код, т. е. sublw h’FF’. Например (см. также стр. 147):
Операция И
Из Рис. 1.2 (стр. 27) можно увидеть следующие соотношения:
• Логическое И любого бита и 0 всегда дает в результате 0.
• Логическое И любого бита и 1 дает в результате исходный бит.
Используя эти свойства, мы можем обнулять группы битов в байте данных посредством логического умножения этого байта на соответствующую битовую маску.
Операция И между байтом данных и тестовым шаблоном, используемая для сброса всех ненужных битов, может также применяться для проверки на ноль заданной группы битов. Если эти биты равны нулю, то общий результат тоже будет равен нулю, и флаг Z установится в 1.
∙ andwf
Команда andwf выполняет операцию побитового И между содержимым рабочего регистра W и любого регистра данных, помещая результат либо в исходный регистр данных, либо в W. Например, при логическом умножении каждого бита W на соответствующий бит регистра h’26’ и помещении результата обратно в h’26’, имеем
К примеру, если нам нужно сбросить шесть старших битов регистра h’26’, то мы можем написать следующее:
movlw b’00000011’; Маска
andwf h’26’,f; Логически умножается на содержимое регистра h’26’
Эту же операцию можно было бы выполнить, повторив шесть раз команду bcf.
Чтобы разобраться, как можно использовать функцию И для проверки на ноль группы битов, представим себе контроллер стиральной машины, который считывает состояние восьми переключателей передней панели через порт В, т. е. через регистр h’06’. Нам нужно, чтобы при нулевом значении битов 7 и 6 (одновременно нажаты кнопки «СТАРТ» и «БЫСТРО») включалась программа быстрой стирки. Вот как можно это сделать:
movlw b’11000000’; Маска
andwf h’06’,w; Операция И с регистром PORTB
btfss STATUS,Z; Пропуск, если Z == 0 (т. е. результат не равен 0)
goto FAST_WASH; ИНАЧЕ перейти к процедуре FAST_WASH
... ...; Следующая проверка
В результате операции логическое И между содержимым регистра h’06’ и константой h’11000000’ младшие 6 битов сбрасываются. Результат будет равен нулю, если оба бита 6 и 7 порта В были сброшены перед выполнением команды. При этом будет установлен флаг Z, в результате чего программа перейдет к команде, помеченной меткой FAST_WASH. Не забудьте, что для проверки нулевого значения одного бита регистра данных можно использовать команду btfsc.
∙ andlw
Эта команда выполняет операцию побитового И между содержимым рабочего регистра и однобайтной константой. Например:
В результате операции, показанной на рисунке, старший полубайт содержимого W обнуляется, а младший — остается неизменным.
Операция ИЛИ
Из Рис. 1.3 (стр. 28) можно увидеть следующие соотношения:
• Логическое ИЛИ любого бита с 0 всегда дает в результате исходный бит.
• Логическое ИЛИ любого бита с 1 всегда дает в результате 1.
Используя эти свойства, мы можем устанавливать группы битов в байте данных, выполняя логическое сложение с соответствующей битовой маской.
∙ iorwf
По аналогии с командой andwf, эта команда выполняет операцию побитового ИЛИ между любым регистром данных и содержимым рабочего регистра W. Так, при логическом сложении каждого бита W с соответствующим битом регистра h’26’ и помещении результата обратно в h’26’ имеем
Например, для установки в 1 старших семи битов регистра данных h’36’ мы можем написать:
movlw b’11111110’; Маска
iorwf h’36’,f; Устанавливаем старшие 7 битов, младший бит не изменяется
∙ iorlw
Эта команда выполняет операцию побитового ИЛИ содержимого W с однобайтной константой. Например, для установки младших двух битов рабочего регистра в 1:
Операция Исключающее ИЛИ
Из Рис. 1.4 на стр. 28 можно увидеть следующее:
• В результате операции Исключающее ИЛИ между битом и нулем возвращается исходный бит.
• В результате операции Исключающее ИЛИ между битом и 1 возвращается инвертированное значение исходного бита.
Другим полезным свойством оператора XOR является его использование в качестве логического дифференциатора. При более внимательном рассмотрении таблицы истинности можно заметить, что на выходе элемента Исключающее ИЛИ будет 1, если на его входах присутствуют различные логические уровни, и 0, если эти уровни одинаковы. Соответственно, в результате побитовой операции Исключающее ИЛИ между двумя байтами мы получим байт с 0 в тех позициях, где биты входных переменных были одинаковыми, и с 1 в тех позициях, где они были различными.
∙ xorwf
Эта команда выполняет побитовую операцию Исключающее ИЛИ между любым регистром данных и содержимым рабочего регистра W. Так, при выполнении операции Исключающее ИЛИ между каждым битом W и соответствующим битом регистра h’26’ и записи результата обратно в h’26’ имеем
Например, для переключения состояния старшего бита регистра h’36’ мы можем написать:
movlw Ь'10000000’; Маска
xorwf h'36',f; Переключаем только старший бит регистра
В качестве примера продемонстрируем использование операции Исключающее ИЛИ для определения отличий между двумя группами битов. Рассмотрим процедуру, непрерывно опрашивающую состояние порта В микроконтроллера, к которому подключены восемь переключателей с передней панели стиральной машины. Процедура ожидает изменения состояния переключателей:
START
movf PORTB,w; Считываем начальное состояние переключателей
movwf h’20’; Сохраняем его в регистре h’20’
S_LOOP
movf PORTB,w; Считываем текущее состояние переключателей
xorwf h’20’,w; Ищем отличия от исходного состояния
btfsc STATUS,Z; Пропускаем, если результат проверки не равен нулю
goto S_LOOP; ИНАЧЕ проверяем снова
При этом возможны два варианта:
Результат, получаемый в рабочем регистре, отражает любые изменения состояния передней панели. В первом случае между исходным состоянием переключателей, сохраненным в регистре h’20’, и текущим нет никаких отличий. Во втором случае 4-й переключатель был переключен из 1 в 0. Чтобы определить, какой именно бит изменился, можно сдвигать результат вправо с подсчетом количества сдвигов до тех пор, пока оставшееся значение не будет равно 0 (см. Рис. 5.14). А характер изменения (0 —> 1 или 1 —> 0) можно определить посредством логического умножения итогового байта на байт исходного состояния переключателей, находящийся в регистре h’20’, т. е. с помощью команды andwf h’20’,w. Если 4-й бит результата равен нулю, то исходное значение тоже было равным 0 и, соответственно, состояние бита изменилось с 0 на 1 и наоборот.
∙ xorlw
Эта команда выполняет побитовую операцию Исключающее ИЛИ содержимого W с однобайтной константой. Например, для инвертирования всех битов в регистре W, т. е. для вычисления обратного кода:
Операции сдвига
Сдвиг данных влево или вправо является базовой операцией, реализованной во всех цифровых системах. Мы уже видели на Рис. 2.22 (стр. 51), как это можно сделать аппаратно. АЛУ всех без исключения микроконтроллеров и микропроцессоров позволяют реализовать различные комбинации команд сдвига вправо и влево.
Во всех микроконтроллерах PIC имеется две команды для циклического сдвига содержимого любого регистра данных, по одной команде для каждого направления[86].
∙ rrf
Эта команда сдвигает содержимое указанного регистра данных на один бит вправо, при этом вдвигаемый бит считывается из флага С, значение которого затем устанавливается в соответствии с выдвинутым битом. Эта операция показана на Рис. 5.13.
Рис. 5.13. Циклический сдвиг содержимого регистра данных на один бит вправо
Учитывая эту особенность команды, программист может выполнить нормальный сдвиг вправо с загрузкой в старший бит нуля (как на Рис. 2.22), если он сбросит бит С перед выполнением команды сдвига.
bcf STATUS,С; Обнуляем бит переноса в регистре STATUS
rrf h’30’,f; Сдвигаем регистр вправо
Одним из использований операций сдвига является побитовая проверка данных. Предположим, для примера, что состояние 8 кнопок мобильного телефона было сохранено в регистре данных h’26’. Вам требуется определить самую левую разомкнутую кнопку, при этом считаем, что разомкнутой кнопке соответствует 1, а замкнутой — 0. Так, если были считаны следующие состояния:
то в W должно получиться число 6 (Ь’00000110’).
Рабочий регистр в Программе 5.4 используется в качестве счетчика. Поскольку флаг переноса сбрасывается перед каждым сдвигом, вдвигается всегда лог. 0[87]. В какой-то момент остаток становится равным нулю, и процесс завершается. Так, 00010111 (1) —> 00001011 (2) —> 00000101 (3) —> 00000010 (4) —> 00000001 (5) —> 00000000 (6).
Список действий, необходимых для решения поставленной задачи, показанный также в виде блок-схемы на Рис. 5.14, будет следующим:
1, Обнулить KEY_COUNT.
2. ПОКА SWITCH_PATTERN не равно нулю, ВЫПОЛНЯТЬ:
а) ЕСЛИ остаток равен нулю, ТО выйти из цикла.
б) Сдвинуть SHIFT_PATTERN на один бит влево.
в) Инкрементировать KEY_COUNT.
3. Значение в KEY_COUNT равно позиции самой левой разомкнутой
Рис. 5.14. Процедура определения позиции самого левого установленного бита
При сдвиге вправо во флаг переноса выдвигается самый правый (младший) бит. Заменив команду btfsc STATUS,Z командой btfsc STATUS,С, мы сможем определить позицию самого правого бита. Во многих случаях циклическое выдвигание бита во флаг переноса может использоваться для побитовой проверки данных. Например, мы можем модифицировать свою программу таким образом, чтобы она подсчитывала число установленных битов в байте (см. Программу 5.6).
Программа 5.4. Поиск самого старшего единичного бита в регистре
SWITCH_PATTERN equ h’26’; Состояние кнопок в регистре h’26’
STATUS equ 3; Регистр STATUS расположен по адресу h’03’
С equ 0; Бит 0 — флаг переноса
Z equ 2; Бит 2 — флаг нуля
; Задача 1 -------------------
HIGH_BIT
clrw ; Обнуляем счетчик
; Задача 2: Сдвигаем вправо и инкрементируем счетчик до тех пор, пока проверяемый байт не равен нулю
; Задача 2а -------------------
LOOP
movf h’26’,f; Остаток равен нулю?
btfsc STATUS,Z; ЕСЛИ нет, TO пропускаем команду
goto FINI; ИНАЧЕ выходим из цикла
; Задача 2б -------------------
bcf STATUS,С; Сбрасываем флаг перекоса
rrf SWITCH_PATTERN,f; Сдвигаем регистр вправо
; Задача 2в -------------------
addlw 1; Увеличиваем счетчик на 1
goto LOOP; и выполняем следующий сдвиг
; Задача 3 ---------------------
FINI
... ...; KEYJTOUNT в W
Заметьте, если все кнопки были замкнуты, то в Программе 5.4 возвращается ноль. Поскольку после сдвига производится проверка на ноль, то нельзя будет различить ситуацию «нет разомкнутых кнопок» и «разомкнута только 1-я кнопка». При разработке программ необходимо уделять особое внимание вопросу их «живучести» при возникновении ограничивающих условий, подобных указанным.
∙ rlf
Команда rlf похожа на команду rrf, только она, как показано на Рис. 5.15, выполняет сдвиг влево.
Рис. 5.15. Циклический сдвиг содержимого регистра данных на один бит влево
В качестве примера использования команды rlf вспомним (см. стр. 25), что сдвиг влево можно использовать для умножения числа на степень двойки. Например:
00000110 (6) <<
00001100 (12) <<
00011000 (24) <<
00110000 (48) <<
и т. д.
где оператор языка Си «<<» используется для обозначения сдвига влево.
Чтобы проиллюстрировать этот процесс, предположим, что у нас имеется 16-битное число b’00000111 11010000’ (равное десятичному 1024 + 512 + 256 + 128 + + 64 + 16 = 2000), хранящееся в двух регистрах данных, например:
После сдвига на один бит влево получим число-:
равное десятичному числу 4000 (2048 + 1024 + 512 + 256 + 128 + 32 = 4000).
Проблема в том, что команда rlf может сдвигать только один бит. Поэтому нам необходимо разбить эту операцию на три этапа, как показано на Рис. 5.16:
1. Сбросить флаг переноса, чтобы при сдвиге вдвигался 0.
2. Сдвинуть влево младший байт, в результате чего значение флага переноса станет равным значению бита by.
3. Сдвинуть влево старший байт, при этом на месте младшего бита окажется бит, загруженный во флаг переноса при предыдущей операции.
Рис. 5.16. Сдвиг 2-байтного числа на один бит влево для умножения на 2
Из рисунка видно, что этот процесс совершенно прозрачен — выходной бит переноса первого регистра данных становится входным для второго. Фрагмент кода, выполняющий эту операцию, выглядит следующим образом:
bcf STATUS,С; Сбрасываем флаг С, в котором содержится вдвигаемый бит
rlf h’31’,f; Сдвигаем младший байт, MSB оказывается во флаге С
rlf h’30’,f; Сдвигаем старший бит
Команды передачи управления
Все команды, перечисленные в Табл. 5.4, тем или иным образом модифицируют состояние счетчика команд PC.
∙ nop
Команда «нет операции» не изменяет состояние системы, однако при ее выполнении инкрементируется PC, поскольку при этом производится выборка следующей команды из памяти программ. Таким образом, единственным результатом выполнения команды пор будет изменение значения счетчика команд.
Эта команда выполняется за один машинный цикл, так что ее основное назначение — реализация коротких задержек (с дискретностью 1 мкс при частоте тактового сигнала 4 МГц). Например, чтобы выдать на 0-й вывод порта А отрицательный импульс длительностью 2 мкс, мы можем написать:
bcf PORTA,0; Выставляем на RA0 НИЗКИЙ уровень
nop; Ждем 2 мкс
nop;
bsf PORTA,0; Выставляем на RA0 ВЫСОКИЙ уровень
предполагая, что 0-й бит порта А сконфигурирован как выход (см. стр. 105) и перед выполнением указанных команд на этом выходе был ВЫСОКИЙ уровень.
∙ goto
Эта команда позволяет выполнить переход к любой требуемой команде в пределах всей памяти программ.
В примере, показанном на Рис. 5.17, команда goto h’3F9’ размещена в памяти программ по адресу h’005’. В процессе выполнения программы счетчик команд инкрементируется до h’006’, а команда, расположенная по этому адресу, извлекается в конвейер для исполнения в следующем цикле. Однако при выполнении команды goto h’3F9’ в счетчик команд помещается адрес h’3F9’. То есть следующей исполняемой командой будет команда, расположенная по указанному адресу. Для этого команда № 1018 должна быть загружена в конвейер, поверх ненужного уже кода 7-й команды. Этот процесс называется сбросом конвейера, и для него требуется дополнительный машинный цикл. Поэтому команда goto выполняется за два машинных цикла.
На рисунке ячейка с адресом h’3F9’ помечена меткой FRED (завершающее двоеточие необязательно). Настоятельно рекомендуется использовать метки, а не абсолютные адреса (см. также стр. 106), поскольку программисту не так-то легко узнать, по какому адресу будет размещаться та или иная команда, и в любом случае ее положение может измениться в процессе разработки программы.
∙ btfsc
Команды btfsc и btfss играют очень важную роль при программировании микроконтроллеров PIC — это видно хотя бы из того, что они встречались практически в каждой программе данной главы. Эти команды использовались для организации операции выбора на основе состояний различных флагов регистра STATUS, обозначаемого фразами «ЕСЛИ…ТО» в текстовых описаниях алгоритмов или символом на блок-схемах. В частности, в Программе 5.4 команда btfsc STATUS,z (или, что менее понятно, команда btfsc 3,2) позволяет реализовать цикл, выполняющийся до тех пор, пока байт данных не станет равным нулю, пропуская команду выхода из цикла goto при Z = 0.
На самом деле, команда btfsc может использоваться не только для проверки флагов регистра STATUS. Проверить можно любой бит в любом регистре данных и пропустить следующую команду, если этот бит сброшен (см. стр. 128). На Рис. 5.18 шестой командой является команда btfsc h’20’,7. Она проверяет состояние 7-го бита регистра h’20’ и, в зависимости от его состояния, выполняет одно из двух действий:
1. ЕСЛИ 7-й бит равен 0, ТО команда 7 пропускается и выполняется команда 8.
2. ЕСЛИ 7-й бит равен 1, то выполняется команда 7.
Рис. 5.18. «Перескакивание» через команду при сброшенном бите регистра h’20’
Часто в качестве такой 7-й команды используется команда goto, что позволяет программе реагировать на изменение состояния любого бита в памяти данных переходом к соответствующему блоку
При пропуске команды необходимо очищать конвейер, так же как и в случае команды goto, поскольку нарушается линейный характер выполнения программы. Это означает, что команда btfsc выполняется за один машинный цикл, если пропуск не осуществляется, и за два цикла — в противном случае.
∙ btfss
Данная команда выполняет пропуск следующей команды, если указанный бит равен 1. За исключением этого ее функционирование полностью аналогично команде btfsc.
∙ decfsz
Команда decfsz представляет альтернативный вариант реализации операции выбора. Подобно комбинации команды decf и следующей за ней команды btfss STATUS,Z, эта команда позволяет декрементировать содержимое любого регистра данных и в случае равенства его нулю пропускать следующую команду.
Типичным примером использования этой команды является подсчет числа проходов цикла. Например, предположим, что нам требуется сформировать на выводе RA0 20 импульсов, длительность каждого из которых будет не менее 2 мкс. Ниже приведен фрагмент программы, выполняющий указанные действия, а его блок-схема — на Рис. 5.19 (предполагается, что частота используемого резонатора равна 4 МГц).
movlw d’20’; Запишем 20 в W
movwf h’3F’; и скопируем его в регистр h’3F’ — счетчик цикла
; ----------------
LOOP
bcf PORTA,0; Выставим на RAO НИЗКИЙ уровень
nop ; Ждем один машинный цикл
bcf PORTA,0; Выставим на RA0 ВЫСОКИЙ уровень
; -----------------
decfsz h’3F’; Считаем в обратном направлении
goto LOOP; Повторить тело цикла, если не ноль
... ...; ИНАЧЕ выйти из цикла
Первоначальный код, обрамленный комментариями в виде пунктирной линии, завершается командой декрементирования с проверкой, которая обеспечивает выход из цикла при достижении регистром h’3F’ нулевого значения. Обратите внимание на запись d’20’ — так в ассемблере явно указывается десятичное число (см. стр. 267). Эта запись эквивалентна записи h’14’, однако гораздо понятнее программисту. В теле цикла используется только одна команда nop, поскольку дополнительная задержка длительностью в один машинный цикл формируется в результате выполнения команд bcf и bsf.
∙ incfsz
Команда инкрементирования регистра данных и пропуска следующей команды при нулевом результате инкрементирует, а не декрементирует содержимое указанного регистра данных. При переходе содержимого через ноль, т. е. в ситуации h’FC’ —> h’FD’ —> h’FE’ —> h’FF’ —> h’00’, будет пропущена следующая команда. Вернемся к нашему примеру, блок-схема которого показана на Рис. 5.19. Если мы предварительно загрузим в регистр h’3F’ число -20 (h’FC’) и заменим команду decfsz h’3F’,f командой incfsz h’3F’,f, то получим тот же самый результат. Только в этом случае счет будет осуществляться в прямом направлении, а не в обратном.
Рис. 5.19. Формирование 20 импульсов на выводе RA0
Примеры
Пример 5.1
Напишите программу для декрементирования 2-байтной переменной, расположенной в памяти данных по адресам h’26’ (старший байт) и h’27’ (младший байт). Помните, что команда decf не влияет на состояние флага переноса/заема.
Решение
Сначала напишем алгоритм:
1. ЕСЛИ младший байт в регистре h’27’ равен нулю, то декрементировать старший байт.
2. ВСЕГДА декрементировать младший байт.
Одна из возможных реализаций этого алгоритма приведена в Программе 5.5. При увеличении разрядности исходного значения до n байт оно будет обрабатываться точно так же — от младшего байта к старшему, с декрементированием как (n + 1) — го, так и n-го байта при равенстве нулю n-го байта.
Программа 5.5. Декрементирование 2-байтного числа
STATUS ecu 3; Регистр STATUS
Z equ 2; Бит 2 — флаг нуля
MSB equ h’26’; Старший байт
LSB equ h’27’; Младший байт
movf LSB,f; Младший байт равен кулю?
btfcs STATUS,Z; ЕСЛИ нет, ТО пропускаем декрементирование старшего байта
decf MSB,f; ИНАЧЕ декрементируем старший байт
decf LSB,f; Всегда декрементируем младший байт
Пример 5.2
В некоторых ранних моделях компьютеров для представления двоично-десятичных чисел использовался сдвоенный пятизначный код (bi-quinary). Этот код представляет собой 7-битный код, в котором при любой комбинации битов будут установлены только два из них:
Хотя такое представление чрезвычайно неэффективно (используется только 10 из 128 возможных комбинаций), его преимуществом является чрезвычайная простота обнаружения ошибок. Напишите программу для проверки корректности числа, представленного в сдвоенном пятизначном коде и находящегося в регистре h’20’ (полагаем, что старший бит равен нулю). В случае ошибки в рабочий регистр необходимо записать h’FF’, иначе — h’00’.
Решение
Все, что нам нужно сделать, — это распознать ситуацию, когда число установленных битов будет больше или меньше двух. Исходя из этого, составим перечень задач:
1. Подсчитать количество единичных битов в числе.
2. Обнулить W.
3. Если полученное число не равно двум, загрузить h’FF’ в W для индицирования ошибки.
Одна из возможных реализаций этого алгоритма приведена в Программе 5.6. В программе исходный байт сдвигается влево до тех пор, пока остаток не станет равным нулю. Если в результате сдвига устанавливается флаг переноса, то инкрементируется счетчик единичных битов. При выходе из счетчика битов вычитается двойка. Если результат вычитания равен нулю, процедура завершается с нулевым значением в W, индицирующим корректность числа. В противном случае в W загружается число h’FF’ для индикации ошибки. Это значение соответствует числу — 1 и традиционно используется для сообщения об ошибочных ситуациях. Существует всего 20 комбинаций с двумя установленными битами, из которых только 10 являются корректными. Можете ли вы доработать программу таким образом, чтобы исключить из рассмотрения эти дополнительные комбинации?
Программа 5.6. Обнаружение ошибок в сдвоенном пятизначном коде
STATUS equ 3; Регистр STATUS расположен по адресу h’03’
С equ 0; Бит 0 — флаг переноса
Z equ 2; Бит 2 — флаг нуля
BI_QUIN equ 20h; Проверяемый байт
COUNT equ 21h; Счетчик битов
BI_QUINARY clrf COUNT; Обнуляем счетчик битов
; Задача 1
LOOP bcf STATUS,С; Сбрасываем флаг переноса
rlf BI_QUIN,f; Сдвигаем байт влево
btfsc STATUS,С; ЕСЛИ нет переноса, ТО пропускаем команду
incf COUNT,f; Инкрементируем счетчик
movf BI_QUIN,f; Проверяем остаток
btfss STATUS,Z; ЕСЛИ ноль, ТО выходим из цикла
goto LOOP; ИНАЧЕ повторяем цикл
; Задачи 2 и 3
movf COUNT,w; Берем подсчитанное значение
sublw 2; Сравниваем его с двумя
btfss STATUS,Z; ЕСЛИ ноль, завершаем программу (W = 0)
movlw h’FF’; ИНАЧЕ помещаем h’FF’ (-1) в W
... ...; и выходим
Пример 5.3
Микроконтроллеры PIC младшего и среднего уровней не имеют команд для непосредственного умножения или деления[88]. Однако для реализации этих важных арифметических операций можно использовать сложение и вычитание.
Например, для деления числа на 10 можно подсчитать, сколько раз можно вычесть из исходного числа десять без формирования бита заема. Подсчитанное таким образом значение будет частным, а оставшееся после вычитаний значение — остатком отделения. Используя этот способ, напишите программу для преобразования двоичного числа, меньшего или равного h’63’ (десятичное 99), находящегося в регистре h’20’, в два BCD-числа, помещаемые в регистры Ь’21’ (десятки) и h’22’ (единицы); см. стр. 20.
Решение
При делении числа на 10 формируется частное от 0 до 9 (напоминаю, что максимальное значение по условиям задачи равно 99) и остаток. Частное представляет собой число десятков, а остаток — число единиц.
Самым простым решением этой задачи, блок-схема которого изображена на Рис. 5.20, является циклическое вычитание десяти (addlw — d’10’ или addlw — h’0А’). В регистре TENS будет подсчитываться количество операций вычитания, выполненных до момента генерации заема, — искомое число десятков на единицу меньше подсчитанного значения. Прибавив к оставшемуся значению число 10, получим остаток отделения, т. е. число единиц.
Рис. 5.20. Преобразование десятичного числа (0…99) в BCD-число
Программа 5.7. Преобразование двоичного числа в двоично-десятичное
STATUS equ 3; Регистр STATUS расположен по адресу h’03’
С equ 0; Флаг переноса — бит 0
BINARY equ h’20’; Исходное число
TENS equ h’21’; Частное (число десятков)
UNITS equ h’22’; Остаток (число единиц)
; Сначала делим на 10
BIN_2_BCD clrf TENS; Обнуляем счетчик цикла
movf BINARY,w; Копируем исходный байт в W
; Вычитаем 10 и считаем кол-во вычитаний до генерации заема
LOOP incf TENS,f; Запомнили очередную операцию
addlw — d’10’; Вычли десять
btfsc STATUS,С; ЕСЛИ заем (С == 0), ТО выходим из цикла
goto LOOP; ИНАЧЕ вычитаем еще раз
; Корректируем лишнее вычитание и определяем число единиц
decf TENS,f; Последняя операция вычитания — лишняя
addlw d’10’; Прибавляем 10 к оставшемуся значению
movwf UNITS; Получаем остаток от деления (число единиц)
... ...; Следующая процедура
Пример 5.4
Другим подходом к делению является представление делителя в виде суммы чисел, являющихся дробными степенями двойки. К примеру, дробь 1/3 можно приближенно выразить следующим образом:
На основе этого ряда напишите программу, которая будет делить число N, находящееся в рабочем регистре, на три, помещая частное в тот же регистр.
В качестве временных переменных для хранения частного и количества сдвигов можно использовать соответственно регистры h’20’ и h’21’.
Решение
Сначала в Программе 5.8 обнуляется байт частного, а число из W копируется в регистр Ь’21’. После этого исходное число сдвигается вправо для получения различных дробей, которые либо прибавляются, либо вычитаются из регистра h’20’, постепенно формируя искомое частное.
При последнем члене ряда, равном 1/129, результат равен 0.3359375, т. е. отклонение от точного значения составляет 0.78 %. При работе с 8-битными числами включать в ряд остальные члены не имеет смысла.
Если же необходима большая точность, то исходное значение следует расширить до 16 бит, добавив младший нулевой байт. Используя при этом 2-байтные арифметические операции и операции сдвига, можно будет увеличить число членов ряда и получить точность вплоть до 1/32768.
Программа 5.8. Процедура деления на три
QUOTIENT equ h’20’; Временная переменная для хранения частного
TEMP equ h’21’; Временная переменная для операций сдвига
STATUS equ 3; Регистр STATUS
С equ 0; Бит 0 — флаг перекоса
DIV_3 clrf QUOTIENT; Обнуляем результат
movwf TEMP; Помещаем N во временный регистр
bcf STATUS,С; Сбрасываем флаг переноса
rrf TEMP,f; Сдвигаем вправо, получаем N/2
irtovf TEMP,w; Копируем в W
movwf QUOTIENT; и в QUOTIENT, получаем Q = N/2
bcf STATUS,С; Сбрасываем флаг переноса
rrf TEMP,f; Сдвигаем вправо, получаем N/4
roovf TEMP,w; Копируем в W
subwf QUOTIENT,f; Вычитаем, получаем Q = N*(1/2 — 1/4)
bcf STATUS,С; Сбрасываем флаг переноса
rrf TEMP,f; Сдвигаем вправо, получаем N/8
roovf TEMP,w; Копируем в W
addwf QUOTIENT,f; Складываем, получаем Q = N*(1/2 — 1/4 + 1/8)
bcf STATUS,С; Сбрасываем флаг переноса
rrf TEMP,f; Сдвигаем вправо, получаем N/16
movf TEMP,w; Копируем в W
subwf QUOTIENT,f; Вычитаем, получаем Q = N*(1/2 — 1/4 + 1/8 — 1/16)
bcf STATUS,С; Сбрасываем флаг переноса
rrf TEMP,f; Сдвигаем вправо, получаем N/32
movf TEMP,w; Копируем в W
addwf QUOTIENT,f; Складываем, получаем Q = N*(1/2 — 1/4 + 1/8 — 1/16 + 1/32)
bcf STATUS,С; Сбрасываем флаг переноса
rrf TEMP,f; Сдвигаем вправо, получаем N/64
movf TEMP,w; Копируем в W
subwf QUOTIENT,f; Вычитаем, получаем Q = N*(1/2 — 1/4 + 1/8 — 1/16 + 1/32 — 1/64)
bcf STATUS,С; Сбрасываем флаг переноса
rrf TEMP,f; Сдвигаем вправо, получаем N/128
movf TEMP,w; Копируем в W
addwf QUOTIENT,w; Складываем, получаем N*(1/2 — 1/4 + 1/8 — 1/16 + 1/32 — 1/64 + 1/128)
Пример 5.5
Одной из операций, выполняемой процедурой перевода температуры из шкалы Цельсия в шкалу Фаренгейта, является умножение числа, находящегося в регистре h’22’, на девять. Итоговое 16-битное произведение должно находиться в регистрах h’21’ (старший байт) и h’22’ (младший байт).
Решение
Задачу умножения числа на девять можно разбить на две подзадачи: умножение исходного числа на восемь и прибавление к полученному произведению исходного числа. Соответственно, в Программе 5.9 реализован следующий алгоритм:
1. Умножить число на восемь (сдвинуть 3 раза влево).
2. Добавить исходное число к частичному 16-битному произведению.
Однобайтный множитель копируется в младший байт будущего произведения. Расширение до 16 бит производится обнулением старшего байта произведения. Сбросив флаг переноса и выполнив 3 раза операцию сдвига, получаем частичное произведение исходного числа на 8. И наконец, прибавив однобайтный множитель к двухбайтному частичному произведению, получаем окончательный результат.
Принцип «сдвиг и сложение» (см. стр. 25) может использоваться для реализации умножения любых чисел. Например, умножение на 10 можно реализовать как х8 + х2. Эту операцию запрограммировать немного сложнее, поскольку необходимо оперировать 2-байтными временными переменными. Однако это все равно гораздо быстрее, нежели простое сложение в цикле.
Программа 5.9. Процедура умножения на девять
STATUS equ 3; Регистр STATUS расположен по адресу h'03'
MULTIPLICAND equ h’22’; Множимое
PRODUCT_H equ h’23’; Старший байт произведения
PRODUCT_L equ h’24’; Младший байт произведения
С equ 0; Флаг переноса — 0-й бит регистра STATUS
; Задача 1: Умножить множимое на восемь
MUL_9 movf MULTIPLICAND,w; Берем множимое, которое
movwf PRODUCT_L; становится младшим байтом произведения,
clrf PRODUCT_H; расширенным до 16 бит
bcf STATUS,С; Сбрасываем флаг переноса
rlf PRODUCT_L,f; Теперь сдвигаем 16-битное значение на три разряда влево
rlf PRODUCT_H,f
rlf PRODUCT_L,f
rlf PRODUCT_H,f
rlf PRODUCT_L,f
rlf PRODUCT_H,f
; Задача 2: Сложить Х8 и X1
addwf PRODUCT_L,f;Прибавим множимое (еще в W!) к младшему байту произведения
btfsc STATUS,С;ЕСЛИ нет переноса, ТО пропускаем команду
incf PRODUCT_H,f; ИНАЧЕ увеличиваем старший байт произведения на 1
... ...; Следующая процедура
Пример 5.6
Некий температурный регистратор считывает значение температуры каждый час, и к концу дня в памяти данных накапливается 24 значения, расположенные по адресам h’30’…h’47’. Напишите программу, просматривающую этот массив и вычисляющую среднесуточную температуру.
Решение
Для вычисления среднего значения необходимо просмотреть весь массив, аналогично тому, как это было показано на Рис. 5.8, добавляя каждый его элемент к 2-байтной сумме. После прохода массива эта сумма делится на 24 для вычисления среднего значения:
Исходя из сказанного, составим перечень задач:
1. Обнулить среднее.
2. Установить указатель на Temp[0] (i = 0).
3. ВЫПОЛНЯТЬ:
а) Прибавить Temp[i] к общей 2-байтной сумме.
б) Инкрементировать i.
в) Повторять, ПОКА i < 24.
4. Разделить на 24.
Этот алгоритм реализован в Программе 5.10. Сумма элементов массива накапливается в регистрах h’48’:h’47’, которые перед входом в цикл сбрасываются. Деление реализовано циклическим вычитанием числа 24 из общей суммы. Это похоже на процедуру деления на 10, реализованную в Программе 5.7, только в данном случае однобайтная константа вычитается из двухбайтного значения. Число успешных вычитаний представляет собой частное, т. е. в нашем случае усеченное среднее значение. Разумеется, более правильно было бы округлять результат до ближайшего большего целого, если остаток больше половины делителя.
Программа 5.10. Вычисление среднесуточной температуры
INDF equ 0; Регистр косвенной адресации
STATUS equ 3; Регистр STATUS
FSR equ 4; Индексный регистр
TEMP_0 equ h’30’; Начальный элемент массива
SUM equ h’48’ ; Общая сумма накапливается в регистрах h’48’:h’49’
AVERAGE equ h’4A’; Среднее
Z equ 2; Флаг нуля — 2-й бит регистра STATUS
С equ 0 ; Флаг переноса — 0-й бит регистра STATUS
; Задача 1: Обнулить общую сумму и среднее
AV_DAILY clrf SUM; Обнуляем старший байт суммы
clrf SUM+1; Обнуляем младший байт суммы
; Задача 2: Установить указатель на Temp[0]
movlw ТЕМР_0; Помещаем адрес первого элемента массива
movwf FSR; в регистр указателя
; Задача 3: Основной цикл
; Задача 3,а: Прибавить Temp[i] к 2-байтной сумме
LOOP1 movf INDF,w; Считываем Temp[i]
addwf SUM+1,f; Добавляем к младшему байту суммы
btfsc STATUS,С; ЕСЛИ нет переноса, ТО не инкрементируем старший байт
incf SUM,f; ИНАЧЕ учитываем перенос
; Задача 3,б: Инкрементирование i
NEXT incf FSR,f; i++
;Задача 3,в: Повторять вычисления, пока i < 24
movf FSR,w; Считываем значение указателя
sublw TEMP_0+h’18’; Вычитаем адрес конечного элемента массива (Теmр[24])
btfss STATUS,Z; ЕСЛИ равно, то выходим из цикла
goto LOOP1; ИНАЧЕ повторяем
; Задача 4: Разделить на 24 для получения среднего
clrf AVERAGE; Обнуляем регистр среднего
; Вычитаем 24 и накапливаем количество вычитаний до формирования бита заема
LOOP2 movlw d’24’; Заносим константу 24 в W
incf AVERAGE,f; Запоминаем очередную операцию вычитания
subwf SUM+1,f;Вычитаем 24 из младшего байта суммы
btfsc STATUS,C; ЕСЛИ заем, ТО переходим к старшему байту
goto LOOP2; ИНАЧЕ повторяем вычитание
movlw 1; Вычитаем единицу из старшего байта
subwf SUM,f
btfsc STATUS,С; ЕСЛИ заем (С==0), ТО выходим из цикла
goto LOOP2; ИНАЧЕ повторяем вычитание
decf AVERAGE,f; Компенсируем лишнюю операцию вычитания
... ...; Следующая процедура
Вопросы для самопроверки
5.1. Можете ли вы сказать, какую операцию выполняют следующие команды над байтом данных, находящимся в рабочем регистре W?
addwf FILE,w
subwf FILE,w
5.2. Как можно проще всего с помощью одной команды поменять значение 0-го бита любого регистра данных? Допускается затрагивать и другие биты.
5.3. Напишите программу, которая складывает два 16-битных числа, получая 17-битную сумму. Первое слагаемое размещается в регистрах памяти данных h’20’ (старший байт) и h’21’ (младший байт). Второе слагаемое размещается в регистрах h’22’ (старший байт) и h’23’ (младший байт). Сумма запоминается в трех регистрах: h’24’ (старший байт), h’25’ (средний байт) и h’26’ (младший байт).
5.4. Напишите программу для вычитания двухбайтного числа NUM_2, находящегося в регистрах h’22’:h’23’, из числа NUM_1, находящегося в регистрах h’20’:h’21’. Двухбайтная разность должна запоминаться в регистрах h’24’:h’25’. Не забудьте, что в случае возникновения заема при вычитании младших байтов, необходимо при вычитании старших байтов дополнительно вычесть единицу из NUM_1. Считается, что NUM_2 меньше или равно NUM_1. Как можно после завершения программы определить, что это условие не было выполнено?
5.5. Как можно доработать программу из Примера 5.3, чтобы результат ее выполнения представлял собой однобайтное значение в формате TENS: UNITS, сохраняемое в регистре h’21’? Такое представление числа называется упакованным двоично-десятичным форматом, при котором в каждом байте хранится значение двух декад (по одной на каждый полубайт). Подсказка: подумайте об использовании команды swapf.
5.6. Доработайте программу из Примера 5.3 для получения трехразрядного BCD-числа, удалив ограничение на максимальный размер исходного десятичного числа. Результат должен сохраняться в регистрах h’21’, h’22’ и h’23’ (сотни, десятки и единицы соответственно).
5.7. В качестве фрагмента процедуры тестирования памяти данных в каждый регистр диапазона h’20’…h’4F’ необходимо записать значение Ь’01010101’ (h’55’). Используя в качестве заготовки Программу 5.2, напишите эту процедуру.
5.8. Доработайте программу из Примера 5.1 таким образом, чтобы она могла декрементировать 32-битное число, расположенное в регистрах h’26’…h’29’ (первым расположен старший байт).
5.9. Данные из массива, расположенного в памяти данных по адресам h’30’…h’4F’, необходимо передать побайтно в удаленный компьютер по сети Интернет. Чтобы приемник мог проверить корректность принимаемых данных, предлагается добавлять один байт, представляющий собой дополнительный код 8-битной суммы всех переданных байтов данных. Если сложить все принятые байты данных и эту контрольную сумму, то при отсутствии ошибок сумма должна быть равна нулю. Напишите процедуру, просматривающую весь массив данных и помещающую эту контрольную сумму в регистр h’20’.
5.10. Взяв за основу программу регистратора из Примера 5.6, напишите программу, вычисляющую максимальную суточную температуру. При выходе из процедуры это значение должно находиться в регистре h’48’.
5.11. В Примере 5.6 среднее значение массива отсчетов температуры вычисляется путем суммирования всех байтов и последовательного вычитания из суммы числа 24 до тех пор, пока частное не станет меньше нуля. Доработайте программу таким образом, чтобы среднее значение округлялось до ближайшего целого, т. е. при остатке, большем 12, округление производилось бы в большую сторону.
5.12. Напишите процедуру умножения однобайтного числа, находящегося в регистре h’23’, на 13. Двухбайтное произведение следует поместить в регистры h’23’:h’24’. Распределение памяти данных для этой процедуры выглядит следующим образом:
(регистр h’21’ служит для расширения однобайтного множимого до 16 байт).
Обратите внимание, что для выполнения задачи потребуется три операции сдвига и сложения.
5.13. Одним из простейших методов шифрования данных является изменение порядка битов. Например, Ь’10111100’ — > Ь’00111101’. Напишите процедуру, выполняющую эту операцию над числом, находящимся в регистре h’20’. Зашифрованное значение должно остаться в рабочем регистре. Вы можете использовать регистр h’21’ в качестве временной переменной и W — в качестве счетчика цикла. Подсказка: используйте 8 раз команду сдвига влево и вправо.
5.14. Простейший цифровой фильтр нижних частот может быть реализован с использованием алгоритма:
где Sn — n-й отсчет 8-битного АЦП, подключенного к порту В.
Напишите процедуру, реализующую такой фильтр, при условии, что три отсчета Sn-2, Sn-1 и Sn хранятся в памяти данных по адресам h’20’, h’21’ и h’22’ соответственно. Итоговое значение Array[i] должно сохраняться в регистре h’48’.
5.15. Некое 3-байтное число размещено в памяти данных следующим образом: . Напишите процедуру, подсчитывающую количество единичных битов в этом числе.
5.16. В телевизионном шоу имеется 8 участников, разделенных на две команды: А и В. У каждого участника есть кнопка, формирующая при нажатии сигнал лог. 1. Состояние всех этих кнопок можно одновременно считать с порта В микроконтроллера. Кнопки команды А подключены к младшим четырем линиям порта.
Напишите процедуру, которая будет:
• Определять момент ответа на вопрос (нажата любая из кнопок).
• Определять ответившую команду (если команда А, то регистр h’20’ обнуляется, если команда В, то в него записывается ненулевое значение).
• Определять, кто из членов команды нажал на кнопку (номер участника помещается в регистр h’21’).
5.17. Контроль четности является простейшим методом защиты цифровых данных от помех. При проверке на нечетность (odd parity) к биту данных добавляется такой дополнительный бит, чтобы итоговое количество единичных битов получилось нечетным. Напишите процедуру, которая считывает 8-битное число, находящееся в регистре h’20’, и изменяет его старший бит в соответствии с описанным принципом. Можно допустить, что перед входом в процедуру 7-й бит исходного байта всегда сброшен. Подсказка: подсчитайте количество единичных битов как в Примере 5.2, а затем проверьте младший бит полученного значения. Любая степень двойки — четная, кроме нулевой (20 = 1). Соответственно, если 0-й бит равен 1, то число нечетное.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОК