Занимательная микроэлектроника — страница 75 из 117

менно.

Глава 15Вычисления в МК и использование АЦП

В давние, давние времена компьютеры занимались только своими прямыми обязанностями: они считали.

Вадим Житников «Компьютеры, математика и свобода»


Обычные операции сложения и вычитания для 8-разрядных чисел (которые на поверку все равно оказываются 16-разрядными операциями) мы уже «проходили» в главе 13. Здесь мы попытаемся понять, как в 8-разрядном МК можно работать с многоразрядными и дробными величинами, в том числе осуществлять операции деления и умножения.

Подумаем сначала, — ас какими числами приходится работать на практике? Если говорить о целых числах, то большинство реальных нужд вполне укладывается в трехбайтовое значение (224 или 16 777 216). Этот же диапазон дает достаточное для практики значение точности (7 десятичных разрядов), большие числа обычно округляют и записывают в виде «мантисса — порядок», с добавкой степени 10. То же касается и разрядности дробных чисел. При этом следует учесть, что любая арифметическая операция дает погрешности округления, которые могут накапливаться. Углубляться в этот достаточно сложный вопрос мы не будем, нам достаточно того факта, что оперируя с трехбайтовыми числами, как результатом обычных операций деления и умножения, мы не выходим за пределы погрешности в шестом знаке, что значительно превышает разрешение рядовых 10-разрядных АЦП — с числом градаций 1024, означающем ошибку уже в третьем, максимум (в реальности так не бывает) в четвертом знаке.

Потому, хотя в типовых приложениях для микропроцессоров оперируют либо 16-, либо 32-разрядными двоичными числами, мы в большинстве случаев ограничимся трехбайтовыми (24-разрядными) числами — нет смысла занимать дефицитный регистр (причем в арифметических операциях — и не один), если он все равно всегда будет равен нулю. Однако возможность получения полных 32 разрядов также не следует упускать из виду, т. к. в некоторых случаях это может понадобиться.


Процедуры умножения для многобайтовых чисел

Чтобы более четко разделить задачи с различной разрядностью, приводимые в «аппнотах» процедуры для наших целей придется творчески переработать, тем более, что в них имеются ошибки. Ошибки эти мы разбирать подробно не будем, да и вообще не будем останавливаться на внутреннем механизме работы таких процедур — к чему углубляться в теорию вычислений? Желающих я отсылаю к упоминавшемуся фундаментальному труду [10]. Здесь мы займемся лишь практическими алгоритмами.

На рис. 15.1 приведена блок-схема алгоритма перемножения двух 16-разрядных беззнаковых чисел (MPY16U), скопированная из pdf-файла Application note AVR200. Жирным выделено необходимое исправление, то же следует сделать в алгоритме перемножения двух 8-разрядных чисел (MPY8U).

Ассемблерный текст процедур умножения можно найти в той же «аппноте», только представленной в виде asm-файла (его можно скачать с сайта Atmel по адресу, приведенному в главе 13, если в таблице с перечнем Application notes щелкнуть по значку диска, а не PDF-документа). Для внесения изменений найдите в тексте процедуру mpy16u, переставьте метку m16u_1 вместо команды brcc noad8, перед которой она стоит, двумя командами ранее — перед командой lsr mp16uH. Аналогичную манипуляцию надо проделать в процедуре mpy8u с меткой m8u_1, если вы желаете воспользоваться 8-разрядным умножением.

Рассмотрите внимательно текст процедуры 16-разрядного умножения в «аппноте» 200 и обратите внимание на две особенности. Во-первых, для представления результата в общем случае требуется 4 байта (регистра), т. е. результат будет 32-разрядным числом, что понятно. Поэтому, во-вторых, разработчики используют в целях экономии одни и те же регистры для множителя и младших байтов результата, но называют их разными именами. Такой прием мы уже обсуждали, и ясно, для чего он применяется — для большей внятности текста процедуры. И тем не менее, вообще говоря, это недопустимо — слишком легко забыть про то, что под разными именами кроется один и тот же регистр, и использовать его еще где-то (не будете же вы, в самом деле, держать неиспользуемыми аж целых 7 регистров только для того, чтобы один раз где-то что-то перемножить, правда?). Потому мы в дальнейшем обойдемся без таких фокусов — лучше написать внятные комментарии, а имена оставить уникальные, даже если они и не окажутся «говорящими».



Рис. 15.1.Процедура перемножения двух 16-разрядных чисел из pdf-файла Atmel Application note AVR200 с исправленной ошибкой


На практике, как мы говорили ранее, 32-разрядное число (максимум 4 294 967 296, т. е. более 9 десятичных разрядов) с точки зрения точности в большинстве случаев избыточно. Если мы ограничимся 24 разрядами результата, то нам придется пожертвовать частью диапазона исходных чисел так, чтобы сумма двоичных разрядов сомножителей не превышала 24. Например, можно перемножать два 12-разрядных числа (в пределах 0—4095 каждое) или 10-разрядное (скажем, результат измерения АЦП) на 14-разрядный коэффициент (до 16 383). Так как при умножении точность не теряется, то этого оказывается более чем достаточным, чтобы обработать большинство практических величин.

Процедура перемножения двух таких величин в исходном 16-разрядном виде, с представлением результата в трехбайтовой форме может быть легко получена из исправленной нами процедуры MPY16U по «аппноте» 200, но я решил воспользоваться тем обстоятельством, что для контроллеров семейства Mega определены аппаратные операции умножения (в Приложении 4 я их не привожу). Тогда алгоритм сильно упрощается, причем он легко модифицируется как для 32-, так и для 24-разрядного результата. Таким образом, для Tuny и Classic по-прежнему следует пользоваться обычными процедурами из «аппноты» (исправленными), а алгоритм для Mega приведен в листинге 15.1 (в названиях исходных переменных отражен факт основного назначения такой процедуры — для умножения неких данных на некий коэффициент). Сокращения LSB и MSB, которые нам еще встретятся не раз, означают least (most) significant bit — младший (старший) значащий разряд, по-русски МЗР и СЗР соответственно.

Листинг 15.1

.def dataL = r4  ;multiplicand low byte

.def dataH = r5  ;multiplicand high byte

.def KoeffL = r2  ;multiplier low byte

.def koeffH = r3  ;multiplier high byte

.def temp = r16  ;result byte 0 (LSB — младший разряд)

.def temp2 = r17  ;result byte 1

.def temp3 = r18  ;result byte 2 (MSB — старший разряд)

;**********

;умножение двух 16-разрядных величин, только для Меда

;исходные величины dataH: dataL и KoeffH: KoeffL

;результат 3 байта temp2:temp1:temp;

;**********

Mu1616:

clr temp2  ;очистить старший

mul dataL,KoeffL  ;умножаем младшие

mov temp,r0  ;в r0 младший результата операции mu1

mov tempi,r1  ;в r01 старший результата операции mu1

mul dataH,KoeffL  ;умножаем старший на младший

add temp1,r1  ;в r0 младший результата операции mu1

adc temp2,r1  ;в r01 старший результата операции mu1

mul dataL,KoeffH  ;умножаем младший на старший

add temp1,r0  ;в r0 младший результата операции mu1

adc temp2,r01  ;в r01 старший результата операции mu1

mul dataH,KoeffH  ;умножаем старший на старший

add temp2,r0  ;4-й разряд нам тут не требуется, но он — в r01

ret

;**********

Как видите, эта процедура легко модифицируется под любую разрядность результата, если нужно получить полный 32-разрядный диапазон, просто добавьте еще один регистр для старшего разряда (temp3, к примеру) и одну строку кода перед командой ret:

adc temp3,r01

Естественно, можно просто обозначить r01 через temp3, тогда и добавлять ничего не придется.


Процедуры деления для многобайтовых чисел

Деление — значительно более громоздкая процедура, чем умножение, требует больше регистров и занимает больше времени (MPY16U из «аппноты» занимает 153 такта, по уверению разработчиков, а аналогичная операция деления двух 16-разрядных чисел — от 235 до 251 тактов). Операции деления двух чисел (и для 8-, и для 16-разрядных) приведены в той же «аппноте» 200, и на этот раз без ошибок, но они не всегда удобны на практике: часто нам приходится делить результат какой-то ранее проведенной операции умножения или сложения, а он нередко выходит за пределы двух байтов.

Потому пришлось разрабатывать свои операции. Например, часто встречается необходимость вычислить среднее значение для уточнения результата по сумме отдельных измерений. Если даже само измерение укладывается в 16 разрядов, то сумма нескольких таких результатов уже должна занимать 3 байта. В то же время делитель — число измерений — может быть и относительно небольшим, и укладываться в один байт. В листинге 15.2 я привожу процедуру деления 32-разрядных чисел (на всякий случай) на однобайтное число, которая представляет собой модификацию оригинальной процедуры из Application notes 200. Как и ранее, названия переменных отражают назначение процедуры — деление состояния некоего 4-байтового счетчика на число циклов счета (определения регистров-переменных не приводятся, комментарии сохранены из оригинального текста «аппноты», они соответствуют блок-схеме алгоритма, размещенной в pdf-файле).

Листинг 15.2

;********

;div32x8» — 32/8 деление беззнаковых чисел

;Делимое и результат в count_HH (старший), countTH,

;countTM, countTL (младший)

;делитель в cikle

;требуется четыре временных регистра dremL — dremHH