AVR - как избавиться от чисел с плавающей точкой |
![]() |
Добавил(а) microsin |
Одно из сильных ограничений AVR (на платформе MCS51 это сказывается не так) - быстрый расход памяти при написании программ на Си. Что поделаешь, за удобства Си и скорость RISC надо платить. Еще больший соблазн - использование для вычислений (например, при измерении напряжений с помощью ADC) чисел float (числа с плавающей запятой). Несмотря на то, что эти вычисления не точны, их слишком просто и удобно применять - можно делить и умножать, не задумываясь о переполнении, и легко представлять результаты вычисления в формате, понятном человеку. Но плата за float слишком высока - линкер добавляет код библиотек математики, и память программ кончается очень быстро. Другое ограничение - вычисления с float выполняются слишком медленно (если конечно не используется аппаратный перемножитель типа [1]). Если нет возможности реализовать алгоритм программы с применением чисел float (из-за вышеуказанных ограничений по объему кода и скорости), одним из вариантов решения проблемы является переход на числа с фиксированной запятой. Их математические операции (вычитание, сложение, деление, умножение) ничем не отличаются от математических операций с простыми целыми числами, код получается компактный и быстрый. Число с фиксированной запятой (обычно байт или слово из двух байт) состоит из целой части (находится в старших битах 8 или 16-разрядного числа) и дробной части (находится в младших битах). Пример числа с фиксированной запятой указан на рисунке, с разрядностью в 8 бит (1 байт). Перед использованием чисел с фиксированной запятой главное - выбрать разрядность числа (байт или слово), и также выбрать положение запятой. С разрядностью вроде все понятно - если возьмем слово (16 бит), вычисления будут точнее, но скорость упадет и объем кода вырастет (и то и другое не меньше чем в 2 раза), а если возьмем байт (8 бит), то получим максимальное быстродействие и самый маленький код, но ухудшится точность. Как обычно, нужен компромисс, и Ваша задача - принять верное решение. Положение запятой никак не влияет на объем кода и быстродействие при математических операциях. Она просто распределяет соотношение точности между целой и дробной частью. Обычно вычисляют, сколько разрядов надо выделить на целую часть, и оставшуюся часть достается дробной части. Например, наше 8-битное число будет хранить напряжение, которое будет меняться от 0 до 5 вольт. Число от 0 до 5 может кодироваться минимум тремя разрядами, поэтому разряды 7, 6 и 5 будут хранить целую часть (3 разряда), а разряды 4, 3, 2, 1 и 0 - остаются под дробную часть (5 разрядов). Число в дробной части будет показывать, сколько 1/32 от вольта будет в дробной части. Например, если в битах 7, 6 и 5 будет число 4, а в битах 4, 3, 2, 1 и 0 - число 30, то это будет кодировать напряжение 4.9375 вольта (0.9375 = 30/2^5 = 30/32). Значение байта при этом будет 100.11110b или 0x9E. При вычислениях с фиксированной запятой (как и с простыми вычислениями на целых числах) нужно применять особые правила: 1. В результате сложения двух чисел возможно появление дополнительного разряда. Это происходит, если произошло переполнение. Если возможность переполнения нужно учитывать, то дополнительный 1 бит числа надо где-то хранить. 2. Результат умножения двух 8-битных чисел хранить в 16-разрядном числе, двух 16-битных в 32-разрядном, и т. п. (разрядность при умножении складывается). 3. При делении (малого числа на большое особенно) нужно предварительно делимое умножить на константу. Самое простое - сдвинуть число влево на нужное число раз (каждый сдвиг умножает на 2), поместить сдвинутый результат в число вдвое большей разрядности, и потом уже спокойно делить. В результате получим число с фиксированной запятой. Например, если делим 8-разрядное целое делимое, сдвинутое влево на 5 разрядов (получили 16-разрядное делимое), на целый делитель, то получаем дробное 16-битное число с фиксированной запятой, где запятая находится между 5 и 4 разрядами. 4. Лучше как можно больше пользоваться предварительно вычисленными на этапе компилирования константами, чтобы убрать код, который будет их генерировать. Например, если мы должны сравнить напряжение на аккумуляторе с напряжением 1.05 вольт, то это напряжение 1.05 вольт лучше сразу представить в нужном формате и определить директивой #define. Когда нужно отобразить число с фиксированной запятой как набор десятичных цифр, действуют по простому алгоритму: Чтобы пояснить эти "премудрости" возьмем все тот же пример - переведем дробное число с фиксированной запятой 100.11110b (== 0x9E, наши 4.9375 вольта) в символьное представление: При подборе констант для умножения/деления удобно использовать смекалку и старые добрые электронные таблицы Microsoft Excel. [Ссылки] |