- Vote on answer
- posted
19 years ago
BIN 2 BCD for AVR
- Vote on answer
- posted
19 years ago
Wed, 03 Nov 2004 20:25:34 +0300 Unrau Alexander wrote to All:
UA> Блин! И ведь пашет. UA> А название у этого алгоритма есть? UA> И существует ли что-то подобное для преобразования дробной части числа UA> (битов после запятой)?
Может кому-то пригодится. Как-то попались две мессаги на эту тему, я их затарил. А позже они очень пригодились, когда пришлось реализовывать подобное на MSP430 - помогли быстро разобраться с алгоритмом. Мессаги следующие:
Первая.
=========Beginning of the citation============== From: "Alexander Volkov" snipped-for-privacy@p55.f.n5051.z2.fidonet.org>
Subject: Re: BinBcd Date: 18 февраля 2002 г. 13:10
Привет, Mihail! MP> Подскажите алгоритм сабжа для 16 разрядного числ. MP> Hужно реализовать на 8051. Алгоритм простой: сначала определяется цифра десятков тысяч вычитанием 10000 из исходного числа, затем цифра тысяч последовательным вычитанием 1000, затем цифры сотен, десятков. Остаток дает цифру единиц. Алгоритм менее простой:десятичный эквивалент числа Х можно получить, сдвигая двоичное число влево и подавая выдвигаемые двоичные цифры в младший разряд десятичного регистра. Одновременно со сдвигом двоичного регистра необходимо удваивать содержимое десятичного регистра. Метод основан на представлении числа в виде полинома Горнера: Х=Xn-1*2^(n-1)+Xn-2*2^(n-2)+...+X1*2+X0 - это дв.представление X=(...(Xn-1*2+Xn-2)*2+...+X1)*2+X0 - это по Горнеру
PS: А исходник у меня есть в кодах К580ИК80(это не одно и тоже с 8051 ?)
С наилучшими пожеланиями. Alexander. =========The end of the citation================
Вторая.
=========Beginning of the citation============== From: "Alexander Volkov" snipped-for-privacy@p55.f.n5051.z2.fidonet.org>
Subject: Re: BinBcd Date: 20 февраля 2002 г. 1:16
Привет, Roman! >> Алгоритм менее простой:десятичный эквивалент числа Х можно >> получить, RS> сдвигая >> двоичное число влево и подавая выдвигаемые двоичные цифры в младший >> разряд десятичного регистра. Одновременно со сдвигом двоичного >> регистра RS> необходимо >> удваивать содержимое десятичного регистра. Метод основан на >> представлении RS> числа RS> непонятно. точнее то, что тут написано никак не является сабжем. Абисняю на примере. Имеем дв.код 1111 , т.е. 15 1) выдвигаем влево 1-ю ед. из дв.рг 2) удваиваем сод. дес.рг (0*2=0000_0000) не забываем, что удвоение - десятичное, т.е. с коррекцией 3) вдвигаем выдв.1 в дес.рг(0000_0001) 4) шаг 1 5) шаг 2 (1*2=0000_0010) 6) шаг 3 (0000_0011) 7) шаг 1 8) шаг 2 (3*2=0000_0110) 9) шаг 3 (0000_0111) 10) шаг 1 11) шаг 2 Внимание! Самое интересное: в данном случае удвоение происходит так: т.к. исх.число >= 5 , то вводим коррекцию +3 получаем 0000_1010, удваиваем ЭТО число (0001_0100) 12)шаг 3 (0001_0101) Это что? Вроде бы дв/дес. 15. Hе так ли ?
С наилучшими пожеланиями. Alexander. =========The end of the citation================
На основе этого код для MSP430 (для IAR'овского ассемблера) выглядит так:
****************************************************** module bin2BCDpublic bin2BCD16
; r11 - bit counter
; r12 - input binary number ; r13 - result (low: 4 digits) ; r14 - addr ; r15 - result (high: 1 digit)
rseg CODE(1)
;---------------------------------------------------- bin2BCD16: push r11 push r10
mov.w #16,r11 clr r13 clr r15
;----------------------------------- ; ; собственно преобраование ; convert: clr r10 rla r12 rlc r10
dadd.w r13,r13 dadd.w r15,r15 dadd.w r10,r13 dadd.w #0,r15
dec r11 jne convert ;-----------------------------------
; ; дальше просто распаковка десячисных цифр каждую в отдельный байт, ; к самому алгоритму отношения не имеет. ; ;
unpack: call #extract_byte swpb r13 call #extract_byte mov.w r15,r12 call #store_byte
pop r10 pop r11 ret ;----------------------------------- extract_byte: mov.w r13,r12 call #store_byte mov.w r13,r12 call #get_high_nibble call #store_byte ret ;----------------------------------- store_byte: bic.w #0xfff0,r12 mov.b r12,0(r14) inc r14 ret ;----------------------------------- get_high_nibble: rlc.b r12 rlc.b r12 rlc.b r12 rlc.b r12 rlc.b r12 ret ;------------------------------------------------------------
end
******************************************************Сам алгоритм очень короткий и эффективный благодаря наличию арифметических команд с десятичной коррекцией (то самое прибавление по условию).
Потом, правда, нашел похожую реализацию в одной из аппликух, но не жалею о проделанной работе, было интересно разобраться. :)
Для AVR есть у Atmel подобная функция, написанная на асме. Адаптированная под IAR она выглядит так:
Сишный прототип: void bin2BCD16(word num, byte* Buf);
****************************************************** module Num_2_BCD_Modulepublic bin2BCD16
rseg CODE:CODE:NOROOT(1)
;--------------------------------------------------------------------------- ;* ;* "bin2BCD16" - 16-bit Binary to BCD conversion ;* ;* This subroutine converts a 16-bit number (fbinH:fbinL) to a 5-digit ;* packed BCD number represented by 3 bytes (tBCD2:tBCD1:tBCD0). ;* MSD of the 5-digit number is placed in the lowermost nibble of tBCD2. ;* ;* Number of words :25 ;* Number of cycles :751/768 (Min/Max) ;* Low registers used :3 (tBCD0,tBCD1,tBCD2) ;* High registers used :4(fbinL,fbinH,cnt16a,tmp16a) ;* Pointers used :Z ;* ;---------------------------------------------------------------------------
;***** Subroutine Register Variables #define zl r30 #define zh r31
#define AtBCD0 0 // address of tBCD0 #define AtBCD2 1 // address of tBCD2
#define tBCD0 r0 // BCD value digits 1 and 0 #define tBCD1 r1 // BCD value digits 3 and 2 #define tBCD2 r2 // BCD value digit 4 #define fbinL r16 // binary value Low byte #define fbinH r17 // binary value High byte #define cnt16a r20 // loop counter #define tmp16a r21 // temporary value
#define rtmp tmp16a #define BCD_AddrLow r18 #define BCD_AddrHigh r19
bin2BCD16: ldi cnt16a,16 ;Init loop counter clr tBCD2 ;clear result (3 bytes) clr tBCD1 clr tBCD0 clr zh ;clear ZH (not needed for AT90Sxx0x)
bBCDx_1: lsl fbinL ;shift input value rol fbinH ;through all bytes rol tBCD0 ; rol tBCD1 rol tBCD2 dec cnt16a ;decrement loop counter brne bBCDx_2 ;if counter not zero
;fill array and ... mov zl,BCD_AddrLow mov zh,BCD_AddrHigh mov rtmp,tBCD0 andi rtmp,0x0f st z+,rtmp ;BCD[0] = value0 (LSD) swap tBCD0 mov rtmp,tBCD0 andi rtmp,0x0f st z+,rtmp ;BCD[1] = value1 mov rtmp,tBCD1 andi rtmp,0x0f st z+,rtmp swap tBCD1 ;BCD[2] = value2 mov rtmp,tBCD1 andi rtmp,0x0f st z+,rtmp ;BCD[3] = value3 st z,tBCD2 ;BCD[4] = value4 (MSD) ret ; ... return
bBCDx_2: ldi r30,AtBCD2+1 ;Z points to result MSB + 1
bBCDx_3: ld tmp16a,-z ;get (Z) with pre-decrement subi tmp16a,-$03 ;add 0x03 sbrc tmp16a,3 ;if bit 3 not clear st z,tmp16a ; store back ld tmp16a,z ;get (Z) subi tmp16a,-$30 ;add 0x30 sbrc tmp16a,7 ;if bit 7 not clear st z,tmp16a ; store back cpi zl,AtBCD0 ;done all three? brne bBCDx_3 ;loop again if not rjmp bBCDx_1
end
Это, кстати, обсосано в соответствующей аппликухе. Только там принцип не объяснен (afair), а только последовательность действий.
- Vote on answer
- posted
19 years ago
День добрый!
..............
А не писал ли кто подобный код на С (8051) и не поделится ли? - сэкономит время на упражнения самому.
С уважением, Юрий Копылов inlog(@)mtu-net.ru
- Vote on answer
- posted
19 years ago
YK> А не писал ли кто подобный код на С (8051) и не поделится ли? - сэкономит YK> время на упражнения самому.
Вдогонку. Этот алгоритм - это как бы "по определению", мы просто берём запись числа в виде
sum( A[n] * E^n )
где A[n] - n-я цифра числа, E - основание системы счисления, '^' - "в степени"
представив A[n] и E исходной системы счисления в виде чисел целевой системы и производим все вычисления в целевой системе. Естественно, как тут уже говорилось, для упрощения вычислений сумму расписываем по Горнеру. При переводе из двоичной системы A[n] - однобитовые знаки а E==2 и "умножить на E" в цепочке вместе с прибавлением очередного A[n] превращается в сдвиг и последующую десятичную коррекцию.
wbr,
- Vote on answer
- posted
19 years ago
YK> ..............
YK> А не писал ли кто подобный код на С (8051) и не поделится ли? - сэкономит YK> время на упражнения самому.
Если на C, то причём тут 8051?
Чесслово, очень фигово писать не на С, а на "С для 8051", "С для XXX", "С для YYY". Практически полностью теряется смысл писания на C, потом для другого процессора хоть переписывай, хоть заново пиши. Это говорит печальный опыт 10-летней давности, когда в погоне за призрачной "эффективностью" я писал не просто "на C для 8051", а вообще "на AVOCET C51", используя всякие фокусы, основанные на знании того, как ЭТОТ компилятор компилирует те или иные конструкции. В частности, в программе нигде не было if(a != b) было if(a^b)
В итоге при полной переделке проекта под новое железо, но тоже на 8051, и переходе на Keil - я те места, где было действительно критично - переписал на ассемблере (число трок асма по сравнению спервым вариантом вырасло раза в два, с 10 до 20% строк проекта), а остальное писал на "гораздо более натуральном С".
Итого "а если для 8051, то почему не на асме?".
Я когда-то кидал сюда куски на асме с соглашениями о регистрах для вызова из Keil C
bin -> packed BCD packed -> unpacked unpacked -> ASCII
единое компактное тело для работы с 8,16,24,32 - битными целыми. Дело в том, что у 51-го есть команда десятичной коррекции после сложения, которая очень хорошо в этих алгоритмах исползуется, причём и для преобразования тетрады в ascii 0-9,A-F тоже. Это "ещё более когда-то" кидал сюда Василевский, но для какой-то фиксированной разрядности и фиксированной длины ASCII-выхода. А году в 98 я переписал под переменную длину (как во входных битах, так и в выходных цифрах, например, заданием нужных параметров при вызове word2ascii для выдачи чисел 0..9999 в 4-байтовый выходной массив).
Лень сейчас рыться на компакте с архивом проектов, заново выковыривать, переводить комментарии... Должно быть в архивах эхи.
wbr,
- Vote on answer
- posted
19 years ago
- Vote on answer
- posted
19 years ago
Добрый день!
Oleksandr Redchuk snipped-for-privacy@real.kiev.ua> пишет
Но ведь понятно же, что я имел ввиду - у меня и так сейчас есть процедуры перевода форматов чисел, которые уже давно тягаю из проекта в проект. На Keil-С для 8051. А компилятор тут при том, что у многих м.б. разные библиотеки на циклический сдвиг, например, наличие или отсутствие к-д дес. коррекции и пр. Либо м.б. asm-вставки. И вопрос-то не в том, чтобы написать код на С - текст-то действительно может быть универсальным и переносимым. А вопрос в скорости, оптимальности и занимаемой памяти. И даже после того, как взял чужой С-код, обычно есть возможность его сократить при адаптации хотя бы за счет разных вариантов использования памяти - data или idata, использования common-переменных и пр.
Например, на моем процессоре AT89S53 по-разному написанная процедура
void long_to_ascii(long d) // char знак + 7 цифр = 8 символов
с использованием операций вида с = (d % 10000)/1000 | 0x30; // Тысячи выполняется около 3.5 мсек, а с использованием d -= ((long)c)*100000; c = (char)(d/10000);
не более 2 мсек, хотя и на 160 байт длиннее.
Вот об этом я и говорил - может, можно еще быстрее и короче. Кому что когда нужно.
Лично для меня сейчас на С - намного быстрее написать и изменить. И в конкретном случае (на С) общий код получается короче и быстрее универсального, поскольку моя процедура перевода не возвращает некоторую строку как параметр, а внутри себя пишет результат в разные места, в том числе в буфер вывода RS-232. Еще и учитывается проблема с местом под стек. Да и нужен мне пока перевод фиксированной длины.
Попробую посмотреть. Спасибо.
WBR Юрий Копылов
- Vote on answer
- posted
19 years ago
Привет!
Vitaliy Romaschenko snipped-for-privacy@p6.f.n5060.z2.fidonet.org> пишет
См. мой ответ OR - вопрос в скорости выполнения и занимаемой памяти. По моим измерениям, быстрее оказалось использовать деление/умножение вместо % и линейная программа на 8 десятичных знаков вместо цикла. Попробую померять в симуляторе твою конструкцию -результат напишу. Сейчас у меня используется следующий вариант (аналогично делаю перевод float_to_ascii - писал неск. лет назад, когда начал писать на С вместо asm):
void long_to_ascii(long d) // знак + 7 цифр = 8 знаков, тестовое время выполнения 1.943...1.961 msec при 24 мГц { char c;
if (d < 0) {que[qlen++] = '-'; d = -d;} else que[qlen++] = '+';
c = (char)(d/1000000); que[qlen++] = c | 0x30; // Миллион d -= ((long)c)*1000000; c = (char)(d/100000); que[qlen++] = c | 0x30; // Сотни тысяч d -= ((long)c)*100000; c = (char)(d/10000); que[qlen++] = c | 0x30; // Десятки тысяч d -= ((long)c)*10000; c = (char)(d/1000); que[qlen++] = c | 0x30; // Тысячи d -= ((long)c)*1000; c = (char)(d/100); que[qlen++] = c | 0x30; // Сотни d -= ((long)c)*100; c = (char)(d/10); que[qlen++] = c | 0x30; // Десятки c = (char)(d-((long)c)*10); que[qlen++] = c | 0x30; // Единицы }
С уважением, Юрий Копылов
- Vote on answer
- posted
19 years ago
Привет!
Vitaliy Romaschenko snipped-for-privacy@p6.f.n5060.z2.fidonet.org> пишет
8051, 22.1184 МГц. Перевожу в символы число 1234567 dec.- Программа на базе твоей строчки (выше) короче моего линейного варианта на ~ 479 байтов Время выполнения тестового примера - 4.63867 мсек
- Мой текущий вариант (линейная программа и деления на 10 и вычитания): Время выполнения тестового примера - 2.36111 мсек
В 2 раза быстрее. Вот потому и хотел попробовать что-либо поинтеллектуальнее, чем простое деление на 10 и вычитание. Тем более, если микро имеет к-ды BCD и если есть возможность при этом не лезть в asm.
WBR Юрий Копылов
- Vote on answer
- posted
19 years ago
- Vote on answer
- posted
19 years ago
- Vote on answer
- posted
19 years ago
YK> 8051, 22.1184 МГц. Перевожу в символы число 1234567 dec.
YK> 1. Программа на базе твоей строчки (выше) короче моего линейного варианта YK> на YK> ~ 479 байтов YK> Время выполнения тестового примера - 4.63867 мсек
YK> 2. Мой текущий вариант (линейная программа и деления на 10 и вычитания): YK> Время выполнения тестового примера - 2.36111 мсек YK> В 2 раза быстрее. Понятно почему - одно деление заменено на умножение, которое гораздо быстрее.
YK> поинтеллектуальнее, чем простое деление на 10 и вычитание. Тем более, YK> если YK> микро имеет к-ды BCD и если есть возможность при этом не лезть в asm. Я даже не вникал - какие у Keil есть inline функции "ближе к железу", кроме просто необходимых cli() sei().
Продолжаю утверждать, что если какой-то кусок типа этого конвертора нужно вылизать на объём и/или скорость, то гораздо правильнее его выписать на ассемблере - это проще, чем извращаться на С и сопровождаться/изменяться оно будет не сложнее, чем "якобы С-шный код". В данном конкретном случае его в строках будет ненамного больше, чем "С"-шного.
Поэтому сейчас продолжаю обсуждать "стандартно-С-шные" методы.
Непонятно почему, но Keil не даёт в стандартной библиотеке функций div и ldiv, ведь тело для них в библиотеке есть гарантированно, надо только засунуть его в нужную обёртку.
#include <stdlib.h>
div_t div(int num, int denom); ldiv_t ldiv(long num, long denom);
Дело в том, что внутренняя библиотечная функция деления в процессе рабоы всегда получает и частное, и остаток. При / вызывается эта функция и берётся частное, при % - ещё раз вызывается она же и берётся остаток. Функции div, ldiv просто возвращают оба значения сразу - и частное, и остаток. Итого
int8_t dig[4]; div_t dt;
dt.quot = N; for(i =3; i>=1; --i) { dt = div( dt.quot, 10); dig[i] = dt.rem; } dig[0] = dt.quot;
Для AVR (mega64) этот код несколько длиннее, чем с / % (в основном за счёт увеличенного количества обращений к памяти), но в два раза быстрее (что вполне естественно). И немного медленнее, чем линейная программа с делением и вычитанием умноженного (не стал разбираться почему), зато более чем в 2 раза короче.
wbr,
- Vote on answer
- posted
19 years ago
OR>> Чесслово, очень фигово писать не на С, а на "С для 8051", "С для XXX", "С OR>> для YYY". Практически полностью теряется смысл писания на C, потом для OR>> другого процессора хоть переписывай, хоть заново пиши. Это говорит
VV> Я стараюсь писать по-возможности портабельно и прозрачно, однако все VV> равно VV> случалось наступать на хорошие грабли. Однажды пришлось портировать код VV> на TMS320C55xx, у которого в С нет 8-битного типа. В коде много где Дык я и не говорю, что С асолютно портабельный. Но самому себе усложнять будущее, применяя в "общеупотребительном" коде небольшого размера фичи, завязанные на пару кристалл+производитель_компилятора - считаю "несколько необдуманным".
wbr,
- Vote on answer
- posted
19 years ago
- Vote on answer
- posted
19 years ago