Арифметические операции. Умножение и деление

Умножение и деление
В микроконтроллерах PIC16 отсутствует аппаратный блок умножения и деления чисел, но эти арифметические операции можно реализовать программным путем.
Операцию умножения можно представить в виде многократного сложения, деление – многократным вычитанием. Например, выражение 8х3=24 равнозначно 8+8+8=24, для деления в качестве примера возьмем следующее выражение 36:10=3,6. Алгоритм деления можно представить так: 36-10-10-10-10=-4, то есть вычитаем из 36 число 10 до тех пор, пока не получим отрицательный результат. При этом результатом операции деления является количество вычитаний, но только без учета последнего вычитания, которое привело к отрицательному результату, так как я рассматриваю здесь только целочисленные операции без дробных частей. В данном примере после четырех вычитаний получаем -4, соответственно ответ равен 3, без учета последнего вычитания.

Рассмотрим подпрограмму умножения однобайтных чисел, перед вызовом подпрограммы загружаем числа в регистры varLL и tmpLL (число в varLL умножается на число в tmpLL), в подпрограмме первым делом очищаем регистр varLH, так как максимальный результат при перемножении однобайтных чисел равен 65025, соответственно результат это двухбайтное число (varLH, varLL). Далее проверяем перемножаемые числа на равенство нулю, если число в регистре varLL равно нулю, то выходим из подпрограммы с нулевым результатом, при обнаружении нуля в tmpLL, очищаем регистр varLL, также получая нулевой результат. Если ни одно из чисел не равно нулю копируем число, содержащееся в регистре varLL в дополнительный регистр regLL, это число будет выступать в качестве константы для дальнейшего многократного сложения. Регистр tmpLL будет выступать в качестве счетчика сложений, перед каждой операцией сложения (прибавляем однобайтное число из регистра regLL к двухбайтному числу в регистрах varLH, varLL – это операция сложения многобайтных чисел, о которой я писал в статье про сложение и вычитание) производим декремент регистра tmpLL. После обнуления регистра выходим из подпрограммы, умножение закончено.

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

Теперь рассмотрим деление однобайтных чисел. Также предварительно загружаем числа в регистры varLL и tmpLL, (число в varLL делится на число в tmpLL). Очищаем регистр результата rezLL, проверяем число в регистре tmpLL, если оно равно нулю, то выходим из подпрограммы без изменений, так как делить на ноль нельзя. Далее выполняем вычитание числа лежащего в tmpLL из числа в регистре varLL, при положительном результате инкрементируем регистр rezLL. При отрицательном результате выходим из подпрограммы, деление завершено.

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

Следует знать, что в отличие от подпрограмм сложения и вычитания, умножение и деление могут занять значительное процессорное время. Умножение чисел 255х255=65025 занимает около 2303 машинных циклов, при частоте тактового генератора в 20 Мгц, время расчета составит 460 мкс. В основном время расчета зависит от числа, на которое умножаем (прямая зависимость). При делении продолжительность расчета увеличивается с уменьшением числа, на которое делим, например операция деления чисел 255:1=255 займет около 1797 машинных циклов, при 20 МГц время расчета составит 359,4 мкс.

Деление и умножение на число 2 можно производить с помощью сдвига содержимого регистра вправо и влево соответственно, с помощью команд RRF и RLF. Сдвиг происходит через флаг C регистра STATUS, при сдвиге вправо, младший бит передается в C, а значение бита C передается в старший бит сдвигаемого регистра, при сдвиге влево все наоборот. Поэтому при применении команд сдвига для деления и умножения необходимо предварительно очистить бит C регистра STATUS.

У этой записи 9 комментариев

  1. Понятные рабочие алгоритмы замечательно и аккуратно выполнены … Автору – интересных заказов от достойных клиентов!

  2. В конце статьи упомянуты команды RRF и RLF, а ведь с их помощью приращение\уменьшение в регистрах с конечным результатом будет на много быстрее и для этого будет возможен выигрыш в количестве строк кода и времени выполнения? Или я ошибаюсь?

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

  3. Спасибо за отличный сайт!
    Было бы здорово, если бы в этой статье привели код деления трёхбайтного числа на двухбайтное. Это очень актуально для 8-битных микропроцессоров.

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

  4. Переписал Ваши программы умножения и деления чисел. Протестировал их в симуляторе MPLAB. Подсчеты идут правильно. Результаты совпадают с результатами подсчитанными на калькуляторе. Но у меня длительности подсчета в машинных циклах отличаются от Ваших. Например Вы пишите: “Умножение чисел 255х255=65025 занимает около 2303 машинных циклов” У меня же получилось ровно 2391 м.ц. И так во всех примерах длительность получается несколько бОльшей чем у Вас. Мой контроллер PIC16F628A, тактовая частота установленна 4МГц. Насколько я понимаю, от этой частоты зависит длительность выполнения вычесленний в микросекундах, но не в м.ц.

  5. Еще одно наблюдение. При делении максимально возможного двухбайтного числа 65535 на 1 вычесление результата, который виден глазом и без вычеслений занимает 823887 м.ц. Это при частоте микроконтроллера 4МГц составляет после округления 0,824 сек. Т.е. почти секунда! Если не использовать специальные меры по организации многозадачности, например RTOS, то устройство зависнет на секунду, или его сбросит сторожевой таймер, не дав досчитать до конца. Теперь понятно почему Вы не стали создавать код деления 3-х и 4-х байтных чисел.

  6. Просто, любопытства ради, в подпрограмму умножения двухбайтных чисел запустил две пары с максимально возможным значением, частоту процессора установил 32768 Гц (некоторые товарищи любят юзать свои микроконтроллеры на такой частоте) и любезно попросил симулятор посчитать мне произведение. Что вы думаете? Результат пришел через 200,907837 сек. 200 с лишним секунд! Я думаю, калькулятор созданный по такому принципу не будет пользоваться популярностью даже в среде школьников.

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

  7. В умножении байтов в подпрограмме и в описании одна и та же переменная обозвана varHL и varLH. На работе не сказывется, но всё же непорядок.

Имя (обязательно)Email (обязательно)Веб-сайт

Добавить комментарий