4925 subscribers

Микроконтроллеры для начинающих. Часть 19. Арифметические команды

752 full reads
1,9k story viewsUnique page visitors
752 read the story to the endThat's 39% of the total page views
2,5 minutes — average reading time

После довольно долгого перерыва продолжаем знакомство с процессорами микроконтроллеров. Ранее мы уже познакомились с командами пересылки данных, причем это заняло целых три статьи.

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

В большинстве случаев считается, что существует 4 арифметических действия: сложение, вычитание, умножение, деление. Однако, в этот список пожалуй стоит добавить и операцию изменения знака числа. С одной стороны, изменение знака числа можно выполнить с помощью вычитания а=0-а, но, с другой стороны, такую запись обычно не используют ограничиваясь более короткой а=-а.

Но количество арифметических машинных команд гораздо больше. Как обычно, в дело вмешиваются различные специфические машинные аспекты и тонкости. В большинстве случаев, все арифметические команды влияют на флаги результата. Однако, для разных процессоров воздействие на флаги может быть разным. Это нужно уточнять в описании системы команд используемого процессора/микроконтроллера. Но давайте начнем по порядку

Ссылки на предыдущие статьи, которые сегодня могут быть вам полезными

Микроконтроллеры для начинающих. Часть 15. Команды пересылки данных PIC

Микроконтроллеры для начинающих. Часть 16. Команды пересылки данных STM8

Микроконтроллеры для начинающих. Часть 17. Команды пересылки данных AVR

Микроконтроллеры для начинающих. Часть 14. Начинаем знакомство в системой команд. Флаги и статусы

Простые типы данных. Машинное представление простых типов. Операции с простыми типами.

Сложение

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

ADD - сложение

Это обобщенная мнемоника. В микроконтроллерах PIC она отсутствует, просто там используются ее уточненные варианты, как мы увидим чуть позже. Зато в STM8 и AVR используется именно такое обозначение.

Важно отметить, что команда ADD не учитывает состояние флага переноса. То есть, она просто складывает два числа (байта) игнорируя входящий перенос. А это значит, что при сложении многобайтовых чисел она может (и должна) использоваться только для сложения наименее значимых байт.

Специальные случаи и мнемоники

Как я уже сказал, в микроконтроллерах PIC обобщенная мнемоника не используется. Зато есть ее уточненные формы:

ADDWF - сложение рабочего регистра и ячейки памяти. Эта команда есть во всех моделях PIC.

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

Однако, уточненные мнемоники и специальные случаи есть не только в PIC.

ADIW - сложение непосредственного операнда и регистровой пары в AVR (не во всех моделях). Непосредственный операнд может иметь значение от 0 до 63. А поскольку речь идет о регистровой паре, то второй операнд, как и результат операции, 16 битные.

ADDW - сложение 16 битного операнда и индексного регистра в STM8. Напомню, что индексные регистры в STM8 16 битные. Эта команда похожа на ADIW для AVR, но при этом полностью 16 битная и есть во всех моделях микроконтроллеров.

ADDFSR - сложение непосредственного операнда и регистра FSR в старших моделях PIC (но не в PIC18). Еще одна очень специфическая команда в PIC. Непосредственный операнд может иметь значение от 0 до 31.

ADC - Сложение с учетом переноса

В отличии от простого сложения здесь уже на результат оказывает влияние входящий перенос. Другими словами, эта команда должна применяться для сложения многобайтовых чисел (кроме наименее значащего байта).

На первый взгляд, такое разделение команд, с учетом переноса и без учета, выглядит излишним. Действительно, почему бы не обойтись одной командой сложения, а если входящий перенос мешает, что его просто можно сбрасывать? Тут есть два объяснения.

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

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

Как и команда ADD, команда ADC есть в микроконтроллерах STM8 и AVR. А вот в PIC мы опять сталкиваемся с уточненными мнемониками. Кроме того, сложение с учетом переноса есть далеко не во всех PIC.

Специальные случаи и мнемоники

ADDWFC - аналогична уже рассмотренной команде ADDWF, но учитывает входящий перенос. Есть только в старших моделях PIC и PIC18.

Вычитание

Вторая из простейших математических операций. Казалось бы, тут не должно быть ничего сложного. Однако, вычитание, то есть a=b-c можно представить и так, a=b+(-c). Если помните, я ранее говорил, что операцию смены знака забывать не стоит. С точки зрения математики, обе формы вычитания идентичны, но для нас идентичны они будут далеко не всегда!

Сначала давайте вспомним, что флаг переноса для операции вычитания будет, на самом деле, флагом заема (borrow). Если, для наших примеров, с будет больше b, то не только результат будет отрицательным. Флаг C будет установлен для отражения того факта, что при выполнении многобайтовой операции должен быть выполнен заем из более значащих байт уменьшаемого.

Если же речь идет о числах разрядностью не превышающих разрядность процессора, то можно сказать следующее (для a=b-c)

  • Флаг С установлен c>b
  • Флаг С сброшен c<=b

А теперь давайте посмотрим, что будет, если мы операцию вычитания реализуем как b+(-c). Для примера рассмотрим а=15-4.

С точки зрения математики b-c тождественно равно b+(-c), а вот с машинной точки зрения результат совершенно разный! Иллюстрация моя
С точки зрения математики b-c тождественно равно b+(-c), а вот с машинной точки зрения результат совершенно разный! Иллюстрация моя
С точки зрения математики b-c тождественно равно b+(-c), а вот с машинной точки зрения результат совершенно разный! Иллюстрация моя

УПС! Результат кажется неожиданным. Однако, все верно. Мы получили правильное числовое значение результата (байт) 0b00001011 ⇒ 11. Но у нас флаг переноса/заема оказался установленным, хотя, с математической точки зрения, тут никакого заема при вычитании быть не может. Замена вычитания на сложение, совершенно законная с математической точки зрения, приводит к инверсному значению флага переноса/заема по результатам операции.

Этот факт нужно обязательно учитывать если вы пишете программу на ассемблере! А для того что бы узнать, как именно выполняется вычитания в используемом микроконтроллере нужно внимательно читать описание системы команд!

SUB - вычитание

Это обобщенная мнемоника операции вычитания. Эта команда есть в микроконтроллерах STM8 и AVR. Причем она выполняет именно вычитание. То есть, флаг С будет установлен при возникновения заема (вычитаемое больше уменьшаемого). Входящий перенос/заем (состояние флага С до операции) не учитывается.

Специальные случаи и мнемоники

SUBWF - как вы уже догадались, это команда микроконтроллеров PIC. Она есть во всех моделях. Однако, в данном случае не только мнемоника иная, но и команда работает не так, как в STM8 и AVR. Этот именно тот случай, когда вычитание заменяется на сложение со сменой знака вычитаемого! А значит, мы получаем инверсное значение флага С после выполнения операции! Другими словами, установленный флаг С означает отсутствие переноса/заема.

SUBLW - вычитание непосредственного операнда из рабочего регистра в PIC. Есть не во всех моделях. Обратите внимание, что эта команда работает аналогично SUBWF, то есть, выполняет вычитание через сложение со сменой знака вычитаемого!

SUBI - вычитание представлено непосредственным операндом. Это команда микроконтроллеров AVR. Обратите внимание, что парная команда ADDI отсутствует!

SBIW - вычитание непосредственного операнда из регистровой пары для AVR. Есть не во всех моделях. Эта команда обратна команде ADIW, которая упоминалась ранее.

SUBW - вычитание 16 битного операнда из индексного регистра в STM8. Эта команда обратна команде ADDW, которая упоминалась ранее.

SBC - Вычитание с заемом

Как и в случае сложения, может потребоваться вычитание многобайтовых чисел. А значит, нужна команда вычитания учитывающая входящий заем (состояние флага С до выполнения команды). Такая команда есть в STM8 и AVR, но далеко не во всех PIC.

Специальные случаи и мнемоники

SUBWFB - вычитание с заемом в старших моделях PIC и PIC18. Обратите внимание, что эта команда заменяет вычитание на сложение со сменой знака вычитаемого.

SBCI - вычитание непосредственного операнда с учетом заема в AVR. Обратите внимание, что парная команда ADCI отсутствует.

Увеличение на 1 и уменьшение на 1

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

INC - инкремент (увеличение на 1)

Это обобщенная мнемоника инкремента. Как вы уже вероятно догадались, она используется в STM8 и AVR. У PIC команда инкремента имеет, как и большинство команд, немного иной вид - INCF, на выполняет точно ту же самую операцию. Кроме того, у STM8 есть 16 битный эквивалент для инкремента индексных регистров - INCW.

Операция инкремента не учитывает входящий перенос.

DEC - декремент (уменьшение на 1)

Эта операция обратна INC. Естественно, ее мнемоника для PIC будет несколько иной - DECF, а STM8 имеет операции для декремента индексных регистров - DECW.

Смена знака числа

Да, смена знака числа может быть выполнена вычитанием его из 0. Но поскольку такое действие требуется часто для операции были введены отдельные команды для многих процессоров.

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

Важно не путать смену знака (дополнение до 2) с логическим дополнением (дополнение до 1), которое будет рассматривать в последующих статьях.

NEG - смена знака числа

Это обобщенная мнемоника операции. Данная команда есть в STM8 и AVR. Обратите внимание, что не смотря на мнемонику эта операция меняет знак числа, а не делает его отрицательным. То есть, положительное число станет отрицательным, а отрицательное положительным.

У 8-битных микроконтроллеров Microchip команда смены знака есть только в PIC18 и, естественно, ее мнемоника NEGF.

В STM8 есть, как и следовало ожидать, команда смены знака 16-битных индексных регистров - NEGW.

Умножение

Умножение гораздо сложнее ранее рассмотренных операций. Аппаратные перемножители есть далеко не во всех процессорах. Даже если в системе команд имеется операция умножения выполняться она может как последовательность сложений и сдвигов, на микропрограммном уровне, а ее время выполнения может зависеть от исходных значений операндов.

Настоятельно рекомендую, если вы этого еще не сделали, прочитать статью о простых типах данных, так я не буду повторять изложенное там, а оно очень важно для понимания.

MUL - умножение

Это обобщенная мнемоника операции умножения. Данная операция есть в STM8 и AVR (не во всех моделях). Причем в данном случае речь идет только о беззнаковом умножении. Результат операции занимает 16 бит, то есть, в два раза больше, чем исходные операнды. Почему так, описано в статье про простые типы данных, повторяться не буду.

У микроконтроллеров Microchip беззнаковое умножение есть только в PIC18. Как всегда, мнемоника отличается от обобщенной. MULWF выполняет умножение рабочего регистра и указанной ячейки памяти. А команда MULLW выполняет то же самое для непосредственного операнда.

Если команда умножения отсутствует, то остается только заменять ее программной реализацией, что гораздо медленнее.

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

Специальные случаи и мнемоники

MULS - выполняет умножение знаковых чисел в AVR. Имеется не во всех моделях.

MULSU - выполняет умножение знакового и беззнакового числа. Имеется не во всех моделях.

FMUL, FMULS, FMULSU - есть не во всех моделях микроконтроллеров. Предназначены для помощи в умножении дробных чисел с фиксированной точкой. От вариантов команд без буквы F они отличаются тем, что сдвигают результат умножения влево на 1 бит. Работа с дробными числами в программах на ассемблере выходит за рамки тем "для начинающих", поэтому я не буду углубляться.

Деление

Деление еще более сложная операция, с дополнительными исключительными случаями. По этой причине встречается еще реже, чем операция умножения. Из всех рассматриваемых в цикле микроконтроллеров команда деления есть только у STM8.

DIV - деление

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

Существует 16 битная разновидность деления, команда DIVW, которая делит содержимое индексного регистра X на содержимое индексного регистра Y. Частное помещается в X, а остаток в Y.

Заключение

Арифметические команды кажутся довольно простыми, а их назначение более понятным, чем ранее рассмотренные команды пересылки данных. Однако, записать математическую формулу в виде программы на ассемблере зачастую не столь простая задача. Особенно, если необходимая команда у микроконтроллера отсутствует. По этой причине, новички предпочитают писать первые программы использующие математические вычисления на языках высокого уровня.

Если все таки возникает желание написать вычисления на ассемблере, например, из-за требований повышенного быстродействия, то следует внимательно изучить описания машинных команд и их влияние на статусы/флаги для используемого микроконтроллера. Но даже при этом нельзя быть на 100% уверенным, что ваша программа окажется быстрее сгенерированной компилятором и, возможно, библиотечных функций.

В следующий раз поговорим о логических командах.

До новых встреч!