Хитрые, необычные алгоритмы и код

Если ваш вопрос не влез ни в одну из вышеперечисленных тем, вам сюда.
Аватара пользователя
Gudd-Head
Друг Кота
Сообщения: 20092
Зарегистрирован: Чт сен 18, 2008 12:27:21
Откуда: Столица Мира Санкт-Петербург

Re: Хитрые, необычные алгоритмы и код

Сообщение Gudd-Head »

Хм... Тут и так дофига прилепленых тем.
[ Всё дело не столько в вашей глупости, сколько в моей гениальности ] [ Правильно заданный вопрос содержит в себе половину ответа ]
Реклама
kolobok0
Грызет канифоль
Сообщения: 296
Зарегистрирован: Ср дек 30, 2009 09:55:39

Re: Хитрые, необычные алгоритмы и код

Сообщение kolobok0 »

51 asm

порой необходимо сохранять на стэке регистры r0-r7.
код при этом типа такой.

Код: Выделить всё

   mov   a,r0
   push   a

;бла-бла-бла
   
   pop    a
   mov   r0,a
если в МК данные регистры банки отображаются на память(обычно это так), и банки не переключаются, то можно
1) объявить повыше

Код: Выделить всё

BR0   equ   000h
BR1   equ   001h
BR2   equ   002h
BR3   equ   003h
BR4   equ   004h
BR5   equ   005h
BR6   equ   006h
BR7   equ   007h
2) написать в коде типа

Код: Выделить всё

   push   BR0

;бла-бла-бла

   pop    BR0
(круглый)
Реклама
Аватара пользователя
ILYAUL
Держит паяльник хвостом
Сообщения: 906
Зарегистрирован: Ср мар 28, 2012 21:45:24
Откуда: ВО

Re: Хитрые, необычные алгоритмы и код

Сообщение ILYAUL »

Gudd-Head писал(а):Хм... Тут и так дофига прилепленых тем.
Так может , можно что-то и открепить :)))
Аватара пользователя
avreal
Опытный кот
Сообщения: 842
Зарегистрирован: Чт дек 31, 2009 19:27:45
Откуда: Бровари, Україна
Контактная информация:

Re: Хитрые, необычные алгоритмы и код

Сообщение avreal »

kolobok0 писал(а):51 asm
...

Код: Выделить всё

   push   BR0

;бла-бла-бла

   pop    BR0
А Вы гляньте внимательнее на свой инструмент. Я довольно много пользовался ассемблерами для mcs51 от AVOCET (это где-то в середине 90-ых) и Keil. У обоих уже были предопределены AR0..AR7 (Absolute Register), причём они зависели от директивы USING, которая указывала, какой сейчас банк регистров используется. Асмом 2500A.D. я пользовался некоторое время до AVOCET ASM51/C51 и сейчас не уверен, но, кажется, у него тоже такая возможность была.
Итого я спокойно писал

Код: Выделить всё

    USING 1
TF1_isr:
    _push   <PSW,ACC,AR0> ; Тут AR0 равно 8, так как объявлен банк 1.
    ...
    _pop <AR0,ACC,PSW>
    reti
Лень в виде мании величия: «ты гений, зачем стараться?». В виде комплекса: «всё равно не выйдет, зачем упираться?». Как логика: «если достаточно, зачем знать и уметь больше?». Цель одна: остановить. Не любит тепло работающих мышц и шум работающего мозга.
Реклама
Эиком - электронные компоненты и радиодетали
akl
Друг Кота
Сообщения: 4445
Зарегистрирован: Пт мар 07, 2008 06:54:43
Откуда: Ижевск

Re: Хитрые, необычные алгоритмы и код

Сообщение akl »

Леонид Иванович писал(а):...Для преобразования в BCD на asm применял такой код...
Здравствуйте. В качестве пятничной развлекухи прогнал в студии Ваш код. Преобразование занимает 2254 такта. Затем сделал тоже самое по алгоритму, предложенному здесь уважаемым umup. Преобразование занимает 798 тактов.
Спойлер

Код: Выделить всё

; Преобразователь BIN24-BCD8 

        .INCLUDE "2313def.inc"
	.CSEG

START:
	LDS	R16,$70
	LDS	R17,$71
	LDS	R18,$72

	CLR	R28		; BCD OUT 10'000'000,1'000'000
	CLR	R29		; BCD OUT 100'000,10'000
	CLR	R30		; BCD OUT 1'000,100
	CLR	R31		; BCD OUT 10,1

	LDI	R24,24
bin24bcd8:
	subi r31,-0x33	;add 0x33
	sbrs r31, 3	;if carry to bit 3
	subi r31, 3	;subtract 3
	sbrs r31, 7	;if carry to bit 7
	subi r31, 0x30	;subtract 0x30

	subi r30,-0x33      ; \n" /*add 0x33*/
	sbrs r30, 3         ; \n" /*if carry to bit 3,*/
	subi r30, 3         ; \n" /*subtract 3*/
	sbrs r30, 7         ; \n" /*if carry to bit 7,*/
	subi r30, 0x30      ; \n" /*subtract 0x30*/

	subi r29,-0x33       ;\n" /*add 0x33*/
	sbrs r29, 3          ;\n" /*if carry to bit 3,*/
	subi r29, 3          ;\n" /*subtract 3*/
	sbrs r29, 7          ;\n" /*if carry to bit 7,*/
	subi r29, 0x30       ;\n" /*subtract 0x30*/

	subi r28,-0x33       ;\n" /*add 0x33*/
	sbrs r28, 3          ;\n" /*if carry to bit 3,*/
	subi r28, 3          ;\n" /*subtract 3*/
	sbrs r28, 7          ;\n" /*if carry to bit 7,*/
	subi r28, 0x30       ;\n" /*subtract 0x30*/

; по совету avreal!!!!!

	lsl R18
	rol R17		;shift input*/
	rol R16
   
	rol r31
	rol r30
	rol r29
	rol r28
   
	dec R24		;\n"
	brne bin24bcd8	;repeat for all bits*/
; опционально. Распаковка упакованного BCD по регистрам	
	MOV	R20,R28
	SWAP	R20
	ANDI	R20,$0F
	ANDI	R28,$0F
	MOV	R21,R28

	MOV	R22,R29
	SWAP	R22
	ANDI	R22,$0F
	ANDI	R29,$0F
	MOV	R23,R29

	MOV	R24,R30
	SWAP	R24
	ANDI	R24,$0F
	ANDI	R30,$0F
	MOV	R25,R30

	MOV	R26,R31
	SWAP	R26
	ANDI	R26,$0F
	ANDI	R31,$0F
	MOV	R27,R31

	RJMP	START
За время 2097 тактов этим алгоритмом можно преобразовать bin40->bcd13
Спойлер

Код: Выделить всё

; Преобразователь BIN40-BCD13 
;
; 02.01.2008
; 02.01.2008

        .INCLUDE "8515def.inc"

	.CSEG
;************************************************
START:
	LDS	R16,$70
	LDS	R17,$71
	LDS	R18,$72
	LDS	R19,$73		; HEX IN
	LDS	R20,$74

	CLR	R25		; BCD OUT 10'000'000'000'000,1'000'000'000'000
	CLR	R26		; BCD OUT 100'000'000'000,10'000'000'000
	CLR	R27		; BCD OUT 1'000'000'000,100'000'000
	CLR	R28		; BCD OUT 10'000'000,1'000'000
	CLR	R29		; BCD OUT 100'000,10'000
	CLR	R30		; BCD OUT 1'000,100
	CLR	R31		; BCD OUT 10,1

	LDI	R24,40
bin40bcd13:
	subi r31,-0x33	;add 0x33
	sbrs r31, 3	;if carry to bit 3
	subi r31, 3	;subtract 3
	sbrs r31, 7	;if carry to bit 7
	subi r31, 0x30	;subtract 0x30

	subi r30,-0x33      ; \n" /*add 0x33*/
	sbrs r30, 3         ; \n" /*if carry to bit 3,*/
	subi r30, 3         ; \n" /*subtract 3*/
	sbrs r30, 7         ; \n" /*if carry to bit 7,*/
	subi r30, 0x30      ; \n" /*subtract 0x30*/

	subi r29,-0x33       ;\n" /*add 0x33*/
	sbrs r29, 3          ;\n" /*if carry to bit 3,*/
	subi r29, 3          ;\n" /*subtract 3*/
	sbrs r29, 7          ;\n" /*if carry to bit 7,*/
	subi r29, 0x30       ;\n" /*subtract 0x30*/

	subi r28,-0x33       ;\n" /*add 0x33*/
	sbrs r28, 3          ;\n" /*if carry to bit 3,*/
	subi r28, 3          ;\n" /*subtract 3*/
	sbrs r28, 7          ;\n" /*if carry to bit 7,*/
	subi r28, 0x30       ;\n" /*subtract 0x30*/

	subi r27,-0x33       ;\n" /*add 0x33*/
	sbrs r27, 3          ;\n" /*if carry to bit 3,*/
	subi r27, 3          ;\n" /*subtract 3*/
	sbrs r27, 7          ;\n" /*if carry to bit 7,*/
	subi r27, 0x30       ;\n" /*subtract 0x30*/

	subi r26,-0x33       ;\n" /*add 0x33*/
	sbrs r26, 3          ;\n" /*if carry to bit 3,*/
	subi r26, 3          ;\n" /*subtract 3*/
	sbrs r26, 7          ;\n" /*if carry to bit 7,*/
	subi r26, 0x30       ;\n" /*subtract 0x30*/

	subi r25,-0x33       ;\n" /*add 0x33*/
	sbrs r25, 3          ;\n" /*if carry to bit 3,*/
	subi r25, 3          ;\n" /*subtract 3*/
	sbrs r25, 7          ;\n" /*if carry to bit 7,*/
	subi r25, 0x30       ;\n" /*subtract 0x30*/

; по совету avreal!!!!!

	lsl R20
	rol R19
	rol R18
	rol R17		;shift input*/
	rol R16

	rol r31
	rol r30
	rol r29
	rol r28		; \n" /*shift out buffer*/
	rol r27
	rol r26
	rol r25
   
	dec R24		;\n"
	brne bin40bcd13	;repeat for all bits*/

	RJMP	START
Может кому ещё, кроме меня, пригодится.
Изменил по совету avreal!
BOB51 писал(а):почему процесс ждет прерывание , а не прерывание процесс?
:beer:
Последний раз редактировалось akl Пт сен 28, 2012 10:30:49, всего редактировалось 1 раз.
Реклама
Аватара пользователя
avreal
Опытный кот
Сообщения: 842
Зарегистрирован: Чт дек 31, 2009 19:27:45
Откуда: Бровари, Україна
Контактная информация:

Re: Хитрые, необычные алгоритмы и код

Сообщение avreal »

А почему не обычное в таких местах

Код: Выделить всё

   lsl R18
   rol R17
   rol R16   ; r16 bit 7 in C
   rol r31   ; shift into LSB of output
   rol r30
   rol r29
   rol r28
вместо

Код: Выделить всё

   lsl r31
   rol r30
   rol r29
   rol r28
   
   sbrc R16, 7   ;skip if msbit of input =0*/
   ori  R31, 1        ;set lsb of output*/

   lsl R18
   rol R17      ;shift input*/
   rol R16
Лень в виде мании величия: «ты гений, зачем стараться?». В виде комплекса: «всё равно не выйдет, зачем упираться?». Как логика: «если достаточно, зачем знать и уметь больше?». Цель одна: остановить. Не любит тепло работающих мышц и шум работающего мозга.
Реклама
akl
Друг Кота
Сообщения: 4445
Зарегистрирован: Пт мар 07, 2008 06:54:43
Откуда: Ижевск

Re: Хитрые, необычные алгоритмы и код

Сообщение akl »

Да, так лучше. :beer:
Аватара пользователя
Ser60
Друг Кота
Сообщения: 3784
Зарегистрирован: Ср дек 24, 2008 09:58:58

Re: Хитрые, необычные алгоритмы и код

Сообщение Ser60 »

При выводе текстовых строк на дисплей очень часто многие строки используются лишь один раз в программе (например при выводе строк меню). Было-бы очень удобно написать как в языках высокого уровня что-то типа PrintString(“Hello World!”), где выводимая строка и место в программе где она выводятся (вызов функции вывода) расположены рядом в программе. Этого можно добиться на ассемблере, поместив выводимую строку сразу после вызова функции PrintString. Идея эта не новая, но я не видел ее имплементации в микроконтроллерах, поэтому привожу свою имплементацию на примере архитектуры MSP430.

Итак, мы будем выводить строку следующим образом:

Код: Выделить всё

	call	#PrintString		; output procedure call
	DB	"Hello World!"		; trailing zero is added
	EVEN
	clr.w	R5			; next instruction in code
Первая строчка в этом коде – собственно вызов функции вывода строки текста, описанная ниже. Сама текстовая строка определена во второй строчке кода. При заключении текста для вывода в двойные кавычки IAR IDE автоматически добавит в конец строки 0, используемый как критерий окончания строки. Последняя строчка кода – это продолжение программы, я просто поместил туда какую-то команду (clr.w R5) для теста. Наконец, директива EVEN говорит компилятору разместить эту команду начиная с четного адреса. В MSP430 каждая машинная команда должна размещаться по четному адресу в памяти.

Таким образом, сама строка для вывода в функцию PrintString явно не передается и у этой строки даже нет имени. Однако, ее начальный адрес есть не что иное, как адрес инструкции следующей после вызова функции, т.е. адрес возврата из функции, который помещается в стек командой call. После вывода текстовой строки нам надо вернуться не на ее начало для продолжения кода, а на инструкцию после нее (в нашем примере на команду clr.w R5). Следовательно, нужно будет скорректировать адрес возврата в стеке. В процедуре вывода я опустил сам вывод и оставил лишь инструкции для извлечения символов строки для памяти. Прошу прощения за нарушения табуляции - не знаю как здесь с этим бороться - в редакторе IDE все выглядит красиво.
Спойлер

Код: Выделить всё

PrintString:
	mov.w	@SP, R4			; pointer to string character
ps1:	mov.b	@R4+, R5		; character to output
	inc.w	0(SP)			; update the return address
	tst.b	R5			; end of string?
	jz	ps2			; YES - break the loop
        
	; put here instructions for character processing

	jmp	ps1			; loop back

ps2:	bit.w	#1, 0(SP)		; move to the next even address
	jz	ps3			;  if needed
	inc.w	0(SP)			; even alignment of the return address
ps3:	ret
Процедура начинается с копирования адреса первого символа текстовой строки в R4 (этот адрес как отмечено выше равен адресу возврата из функции). Таким образом, R4 будет поинтер на выводимый символ в строке, который извлекается из памяти и помещается в R5. После этого адрес возврата в стеке увеличивается на 1 и происходит проверка на окончание строки, т.е. на равенство R5 нулю. Если полученный символ не 0, мы его выводим и переходим на начало цикла с меткой ps1. В противном случае выходим из цикла на метку ps2. Там текущий адрес возврата в стеке проверятся на четность, и если он оказался нечетным, то к нему добавляется 1 (последствия директивы EVEN).

Функцию можно сделать более универсальной, чтобы иметь возможность работать со строками размещенными непосредственно за ней или передаваемых функции в качестве параметра через стек. В стек при этом следует заносить адрес начала строки для вывода, а в теле функции добавится еще одна точка входа. В нижеприведенном коде точки входа в функцию помечены метками PrintString2 и PrintString3. При этом вызовы можно осуществить следующим образом:

Код: Выделить всё

; первый вариант вызова с передачей параметра через стек
	push.w	#hi			; passing parameter string
	call	#PrintString2		; using another entry point

; второй вариант вызова с размещением строки сразу под вызовом функции
	call	#PrintString3		; output procedure call
	DB	"Hello World!"		; trailing zero is added
	EVEN
	clr.w	R5			; next instruction in code
В первой строчке этого кода функции передается адрес текстовой строки для вывода. Сама строка определена где-то в другом месте программы, например как

hi: DB "Hi, there"

Во втором варианте вызова функции строку для вывода следует разместить в коде сразу после вызова как и раньше.

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

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

Код: Выделить всё

PrintString2:		
	pop.w	R6			; load return address in R6
	mov.w	@SP, R4			; load string address in R4
	mov.w	R6, 0(SP)		; put return address in stack
	clr.w	R6			; do not update the return address
	jmp	ps4
	
PrintString3:
	mov.w	#1, R6			; add 1 to the return address
	mov.w	@SP, R4			; pointer to string character
ps4:	mov.b	@R4+, R5		; character to output
	add.w	R6, 0(SP)		; update the return address
	tst.b	R5			; end of string?
	jz	ps5			; YES - break the loop
        
	; put here instructions for character processing

	jmp	ps4			; loop back

ps5:	bit.w	#1, 0(SP)		; move to the next even address
	jz	ps6			;  if needed
	add.w	R6, 0(SP)		; even alignment of the return address
ps6:	ret
Эти примеры можно легко адаптировать на случай если нужно сохранять рабочие регистры (или другие данные) в стеке при работе функции или если происходит работа с 20-битными адресами в архитектуре MSP430X.
Аватара пользователя
avreal
Опытный кот
Сообщения: 842
Зарегистрирован: Чт дек 31, 2009 19:27:45
Откуда: Бровари, Україна
Контактная информация:

Re: Хитрые, необычные алгоритмы и код

Сообщение avreal »

Ser60 писал(а):При выводе текстовых строк на дисплей очень часто многие строки используются лишь один раз в программе (например при выводе строк меню). Было-бы очень удобно написать как в языках высокого уровня что-то типа PrintString(“Hello World!”), где выводимая строка и место в программе где она выводятся (вызов функции вывода) расположены рядом в программе. Этого можно добиться на ассемблере, поместив выводимую строку сразу после вызова функции PrintString. Идея эта не новая, но я не видел ее имплементации в микроконтроллерах, поэтому привожу свою имплементацию на примере архитектуры MSP430.
Итак, мы будем выводить строку следующим образом:

Код: Выделить всё

	call	#PrintString		; output procedure call
	DB	"Hello World!"		; trailing zero is added
	EVEN
	clr.w	R5			; next instruction in code
Чтобы тут не повторяться, вон там было то же самое для AVR
Лень в виде мании величия: «ты гений, зачем стараться?». В виде комплекса: «всё равно не выйдет, зачем упираться?». Как логика: «если достаточно, зачем знать и уметь больше?». Цель одна: остановить. Не любит тепло работающих мышц и шум работающего мозга.
Аватара пользователя
Ser60
Друг Кота
Сообщения: 3784
Зарегистрирован: Ср дек 24, 2008 09:58:58

Re: Хитрые, необычные алгоритмы и код

Сообщение Ser60 »

Вот еще трюк для деления 4-значного десятичного числа (т.е. не превышающего 9999) на 10. Для этого его надо просто умножить на "магическую константу" 6554 и отбросить младшие 16 бит результата. Иными словами, справедливо равенство

[х / 10] = (x * 6554) >> 16

где [x/10] - целая часть от деления. Основанием для этого является школьная формула для суммы геометрической прогрессии степеней 1/4:

1 - 1/4 + 1/16 - 1/64 + ... = 1/(1 + 1/4) = 4/5

Если поделить обе части на 8, получим

1/8 - 1/32 + 1/128 - ... = 1/10

Таким образом, x/10 = х/8 - х/32 + x/128 - ...
и после имножения обеих частей на 2^16, окончательно получим

х/10 * 2^16 = x*(2^13 - 2^11 + 2^9 - 2^7 + 2^5 - 2^3 + 2^1 - ...)

Вопрос сколько членов (бесконечного) знакочередующегося ряда следует взять для достижения требуемой точности? Оказалось, что при х < 16384 достаточно взять указанные выше 7 членов ряда, которые в сумме дают то самое число 6554. Реализация этого способа особенно эффективна на МК с аппаратным 16х16 перемножителем, например MSP430 или PIC24. Для первого из них, например, программа деления числа в R4 занимает 4 операции и выполняется за 11 циклов:

Код: Выделить всё

mov.w	#6554,&MPY		; load the "magic" constant
mov.w	R4, &OP2		; compute R4 *= 6554
nop
mov.w	&RES1, R4		; R4 = R4 / 10
Для чисел превосходящих 16384 начинает накапливаться погрешность и результат оказывается неточным. Однако, положение можно легко исправить добавлением еще двух членов ряда и умножением результата на 2^20. Магическая константа в этом случае будет иной:

[x / 10] = (x * 104858) >> 20

Эта формула справедлива для любых 16-битных чисел. Однако, для ее эффективной реализации желательно иметь 32х32 аппаратный перемножитель, поскольку магическая константа 17-битная и результат умножения может быть максимум 33-битным. Опять-же для архитектуры MSP430X с аппаратным перемножителем MPY32 программа будет аналогична предыдущей.

Этот алгоритм был применен в программе вычисления цифр у чисел с 4 десятичными знаками. Полный текст программы ниже. По сравнению со стандартным алгоритмом, основанным на схеме Хорнера, этот чуть медленнее (около 100 циклов против 81). Однако, мне нужно было иметь каждую цифру в отдельности, а не BCD представление, получаемое в схеме Хорнера, чтобы потом не вырезать из него цифры. В процессе алгоритма еще вычисляется и остаток от деления на 10, но это пока не так красиво и основано на специальной таблице, поэтому от комментов я здесь воздержусь.
Спойлер

Код: Выделить всё

	mov.b	#4, R7			; digit counter
	clr.b	R8			; pointer correction for
	cmp.w	#8192, R4		; the 2nd part of table
	jl	d2b			; it is needed for numbers
	mov.b	#16, R8			; exceeding 8192

	mov.w	#6554,&MPY		; load the "magic" constant
d2b:	mov.w	R4, &OP2		; compute R4 *= 6554
	nop
	mov.w	&RES1, R4		; R4 = R4 / 10
	mov.w	&RES0, R5		; computing R4 % 10
	swpb	R5
	rram.w	#4, R5
	and.b	#0x0F, R5
	add.b	R8, R5
	mov.b	table(R5), R5		; R5 = remainder
	clr.b	R8
	dec.b	R7
	jnz	d2b
table:
	DB	0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9
	DB	0, 1, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 7, 8, 9, 9
Вот для полноты картины алгоритм преобразования числа в R10 в BCD по схеме Хорнера:
Спойлер

Код: Выделить всё

        clr.w   R4                      ; storage for 4-digit BCD
        mov.b   #16, R5                 ; loop cnt = # of bits        
dp0:    rla.w   R10                     ; use decimal addition
        dadd.w  R4, R4                  ; operation to convert R4
        dec.b   R5                      ; into BCD
        jnz     dp0
akl
Друг Кота
Сообщения: 4445
Зарегистрирован: Пт мар 07, 2008 06:54:43
Откуда: Ижевск

Re: Хитрые, необычные алгоритмы и код

Сообщение akl »

"Магические" константы, по мне, получаются гораздо проще X/10=X*K/65536 или K=65536/10=6553,6~6554. Для 20-разрядного числа X/10=X*L/2^20 или L=1048576/10=104857,6~104858.
Перед отбрасыванием младшего слова значение старшего бита этого слова прибавить к результату. Получается правильное математическое округление.

Код: Выделить всё

;результат умножения в R16...R19 в формате старший-младший
	CLR	ZH
	LSL	R18 ; старший бит, имеющий вес 0.5  в С-флаг
	ADC	R17,ZH
	ADC	R16,ZH
Аватара пользователя
avreal
Опытный кот
Сообщения: 842
Зарегистрирован: Чт дек 31, 2009 19:27:45
Откуда: Бровари, Україна
Контактная информация:

Re: Хитрые, необычные алгоритмы и код

Сообщение avreal »

Так, мелочь, даже на эту тему не тянет.
Но я предпочитаю в качестве рабочей пары регистров использовать не R16, R17, а R24, R25 (которые обзываю WL, WH соответственно). С этой парой работают adiw/sbiw и вышеприведенный фрагмент будет таким (R22..R25 -- младший-старший).

Код: Выделить всё

   SBRC R23, 7
   ADIW R24, 1
Понятно, что при этом я держу младший байт данных в регистре с меньшим номером, но это и более естественно для AVR, и совместимо с С-компиляторами.
Лень в виде мании величия: «ты гений, зачем стараться?». В виде комплекса: «всё равно не выйдет, зачем упираться?». Как логика: «если достаточно, зачем знать и уметь больше?». Цель одна: остановить. Не любит тепло работающих мышц и шум работающего мозга.
Аватара пользователя
Gudd-Head
Друг Кота
Сообщения: 20092
Зарегистрирован: Чт сен 18, 2008 12:27:21
Откуда: Столица Мира Санкт-Петербург

Re: Хитрые, необычные алгоритмы и код

Сообщение Gudd-Head »

AVR. Задержка на 2 такта одним машинным словом:

Код: Выделить всё

rjmp PC+1;
вместо двух слов

Код: Выделить всё

nop;
nop;
[ Всё дело не столько в вашей глупости, сколько в моей гениальности ] [ Правильно заданный вопрос содержит в себе половину ответа ]
Аватара пользователя
avreal
Опытный кот
Сообщения: 842
Зарегистрирован: Чт дек 31, 2009 19:27:45
Откуда: Бровари, Україна
Контактная информация:

Re: Хитрые, необычные алгоритмы и код

Сообщение avreal »

Gudd-Head писал(а):AVR. Задержка на 2 такта одним машинным словом:
Именно этот вариант показан в регулируемой задержке в этой же теме. Там же для однословной 3-тактовой задержки в случае наличия регистра, который можно испортить, использован LPM. Ещё когда-то обсуждалось, что в прерывани Z при этом можно не сохранять :-)
В регулируемой задержке уже есть всё равно испорченный регистр, поэтому LPM R, Z. Если регистр R0 свободный в этом месте кода, то можно просто LPM.

Также 2-тактовую задержку сделает

Код: Выделить всё

    adiw  R24, 0 ; или другая пара
но при наличии rjmp . использовать как задержку смысла нет, зато как однословную проверку регистровой пары на 0 - запросто.
Лень в виде мании величия: «ты гений, зачем стараться?». В виде комплекса: «всё равно не выйдет, зачем упираться?». Как логика: «если достаточно, зачем знать и уметь больше?». Цель одна: остановить. Не любит тепло работающих мышц и шум работающего мозга.
Аватара пользователя
Ser60
Друг Кота
Сообщения: 3784
Зарегистрирован: Ср дек 24, 2008 09:58:58

Re: Хитрые, необычные алгоритмы и код

Сообщение Ser60 »

Опять извечная проблема: преобразование 16-битного беззнакового целого числа в строку (BCD).

Приводимый ниже рекордно быстрый алгоритм годится только для архитектуры MSP430X. В ней имеется таймер реального времени RTC_B, в состав которого входит регистр BIN2BCD. Странно, что в документации slau272a и slau208k на семействa F5xx/F6xx и FR57xx, соответственно, об этом регистре нет ни слова в тексте. Есть лишь краткое упоминание этого регистра в составе регистров RTC, где ему посвящено лишь одно предложение, поэтому его очень легко не заметить. Согласно ДШ, в этот регистр следует сначала записать 12-битное число, а при чтении из него получите 16-битное BCD этого числа. Проще не бывает. Вот пример преобразования 12-битного числа в R4 в BCD в R5. Процедура занимает всего 6 циклов !!!

Код: Выделить всё

 	mov.w	R4, &BIN2BCD
 	mov.w	&BIN2BCD, R5
Почему в документации упомянуты лишь 12-битные исходные числа? Ведь максимальное 12-битное число - это 4095, а четырех BCD знаков в 16-битном регистре достаточно для представления чисел вплоть до 9999. Это для меня осталось загадкой. Я попробовал переводить числа вплоть до 9999 - все действительно работает! Более того, оказалось, что при записи в этот регистр чисел от 10000 до 16383 (т.е. 14-битных) в регистре остается лишь последние 4 цифры BCD, а для чисел от 16384 до 65535 процессор оставляет лишь 14 младших бит числа и переводит полученный результат в BCD. Отмечу, что работа этого регистра не эмулируется в симуляторе IAR, по крайней мере я не знаю как его заставить. Однако, посмотреть регистр в работе можно с помощью внутрисхемного отладчика. Я проверял его на процессоре MSP430FR5728.

На основе этого регистра можно сделать быструю процедуру перевода 16-битных беззнаковых целых в R4 в BCD в R5. Процедура использует расширенный набор инструкций MSP430X для работы с 20-битными регистрами и занимает лишь 19 циклов процессора!!!

Код: Выделить всё

 	rrcm.a 	#3, R4
 	mov.w  	R4, &BIN2BCD
 	mov.w  	&BIN2BCD, R5
 	daddx.a	R5, R5
 	rlax.a 	R4
 	daddx.a	R5, R5
 	rlax.a 	R4
 	daddx.a	R5, R5
Работает этот код так: исходное 16-битное число N в 20-битном регистре R4 сдвигается циклически на 3 бита вправо через бит С. При этом в 16 младших битах R4 получается N/8, которое не превосходит 8191, т.е. может быть непосредственно переведено в BCD двумя следующими инструкциями. Эти инструкции не изменяют бит С, который все еще содержит бит 2 числа N, а его биты с номером 1 и 0 после сдвига находятся соответственно в битах 19 и 18 регистра R4. После этого выполняются 3 операции 20-битной десятичной арифметики сложения R5 с самим собой и битом С. Фактически, производятся 3 итерации схемы Хорнера. В результате 5 символов BCD представления получаются в 20-битном регистре R5. Если нужно восстановить исходное число в R4 после работы алгоритма, в конец кода следует добавить инструкцию "rlax.a R4".

Отмечу, что в состав RTC_B также входит регистр BCD2BIN для преобразования BCD в двоичный код, но о нем как-нибудь в другой раз.
Аватара пользователя
YS
Друг Кота
Сообщения: 7518
Зарегистрирован: Вс мар 29, 2009 22:09:05
Контактная информация:

Re: Хитрые, необычные алгоритмы и код

Сообщение YS »

В ней имеется таймер реального времени RTC_B, в состав которого входит регистр BIN2BCD.
:shock:
Странно, что в документации slau272a и slau208k на семействa F5xx/F6xx и FR57xx, соответственно, об этом регистре нет ни слова в тексте.
Есть еще в Real-Time Clock (RTC) Overview, стр. 563. Собственно, а что там еще описывать? :) Записал - прочел, вся работа.
Разница между теорией и практикой на практике гораздо больше, чем в теории.
Аватара пользователя
Ser60
Друг Кота
Сообщения: 3784
Зарегистрирован: Ср дек 24, 2008 09:58:58

Re: Хитрые, необычные алгоритмы и код

Сообщение Ser60 »

Еще одна извечная проблема: реализация деления 32-битных чисел с плавающей точкой, представленных в формате IEEE-754 (С-тип float). Цель – скорость. Архитектура – 16-битные МК семейства RL78 фирмы Renesas.

Выше я описал алгоритм быстрого обращения числа, который имеет смысл для процессоров, не имеющих аппаратной поддержки деления. В противном случае есть шанс выполнить операцию быстрее. Семейство RL78 оснащено аппаратным 32/32 делителем целых чисел, вычисляющим для 32-битных чисел 32-битную целую часть и 32-битный остаток от деления за 16 циклов. Этот делитель оформлен как периферийный модуль, независимый от процессора. “Хитрость” алгоритма в том, что пока делитель занят вычислением результата, процессор одновременно можно загрузить еще чем-то полезным для алгоритма. Таким образом получится что-то типа параллельных вычислений. Приводимый алгоритм использует 3 операции аппаратного деления и гарантированно работает только для нормализованных чисел и вычисляет частное за 97 - 110 циклов в зависимости от данных.

Напомню, что в формате IEEE-754 число с плавающей точкой представленно 23-битной мантиссой и 8-битным порядком. Для вычисления частного нужно грубо говоря вычесть порядки и поделить мантиссы. Грубо – потому что для результата меньшего 1 потребуется еще нормализация результата, а к разности порядков нужно добавить bias в соответствии со стандартом. Кроме того, следует учесть возможное переполнение порядка и специальные случаи представления чисел типа 0 или бесконечности. Приведенная ниже программа делит число с символьным именем dev, расположенное в RAM (точнее в ее области, соответствующей saddrp в терминологии адресации RL78), на число под именен der, расположенное там-же. Результат помещается в RAM под именем res.

Перед делением из чисел вырезаются мантиссы и дополняются целой частью, которая для нормализованных чисел всегда равна 1. Фактически, принудительно ставится единица в бите 23 мантиссы каждого из чисел. Полученные мантиссы делимого и делителя сравниваются и если первая не меньше второй, то делимое сдвигается на 7 бит влево. В противном случае оно сдвигается на 8 бит влево (в этом случае из порядка результата следует вычесть 1). Затем полученное 31- или 32-битное делимое делится на 24-битный делитель аппаратным делителем МК. Таким образом, получаем старшие 8 бит мантиссы окончательного результата, включая целую часть. Нетрундо видеть, что старший из полученных 8 бит всегда равен 1. Этот бит равен целой части нормализованного результата, при условии что нет проблем с порядком, например переполнения. На втором шаге остаток от деления (получаемый в 16-битных регистрах MDCH:MDCL) сдвигается на 8 бит влево и загружается в 16-битные регистры делимого (MDAH:MDAL). Сдвиг производится за 11 циклов путем пересылки байтов. Содержимое регистров делителя (MDBH:MDBL) при этом не меняется. После второго деления получаем следуюшие 8 бит результата. Затем опять сдвигаем остаток на 8 бит влево и производим третье деление на делитель, в результате чего получаются последние 8 (из 24) бит мантиссы результата.

Пока производится первое деление мантисс, процессор вычисляет порядок результата. Это занимает 10 циклов из 16 необходимых для деления. В программе ниже я не привел обработку специальных случаев переполнения или “недополнения” (underflow) порядков, при необходимости это легко сделать по усмотрению пользователя. Во время второго деления процессор помещает результат первого деления в регистры результата res, а также вычисляет и помещает туда-же знак частного. Это тоже занимает 10 циклов из 16. Во время третьего деления процессор помещает в регистры res вычисленные ранее вторые 8 бит результата. Это также занимает менее 16 циклов, требуемых для деления, так что остается достаточно времени на обработку специальных случаев (в приводимой тестовой программе эта обработка отсутствует).

В алгоритме имеется резерв по точности. Я имею в виду, что после 3 делений можно получить до 27 бит результата (точнее 26 или 27 в зависимости от нормализации на первом шаге). Дополнительные 2-3 бита можно использовать, например, для повышения точности или округления 24-битной мантиссы. Для этого перед вторым и третьим делениями мантиссу делимого следует сдвигать на 9 бит влево а не на 8 (при этом ее бит целой части попадет в бит 31 регистра делимого). В этом случае после второго и третьего делений будем получать по 9 бит результата (а не по 8 как ранее).

Отмечу, что в имеющейся на момет написания версии системы IAR Kickstart отсутствует эмуляция аппаратного делителя/перемножителя в симуляторе. Отладка алгоритма производилась живьем (внутрисхемно) на МК R5F1006A подсемейства RL78G13. Кроме того, для этого процессора в составе его регистров в IAR отсутствуют MDAH и MDAL (?!) Для подсемейства R78G14 имеется шанс по небольшому ускорению алгоритма за счет того, что регистры данных перемножителя/делителя совмещены с регистрами общего назначения МК.
Спойлер

Код: Выделить всё

#include "ior5f1006a.h"         
#include "ior5f1006a_ext.h"

#define SCL	0
#define	SDA 	1

        RSEG    CSTACK          ; stack segment definition
	RSEG	SADDR_N		; data segment
dev:    DS 4			; devidend
der:	DS 4			; divisor
res:	DS 4			; result
tmp:	DS 2

        ASEGN   RCODE:CODE, 0   ; vector table
        ORG     RST_vect
        DW      RESET           ; Reset vector

	ORG	0xC0		; option byte 
	DB	0x6E		; disable watchdog
	DB	0x1F		; LVD off 
	DB	0xAA		; 8 MHz LS mode 
	DB	0x85		; enable on-chip debugging

        RSEG    CODE            ; code segment
RESET: 	movw    SP, 	#SFE(CSTACK)
	mov	CMC, 	#0x04	; input mode on X1 pins
	mov	HOCODIV, #0x02	; set 8 MHz system clock
	set1	PER0.7		; enable RTC operation
	mov	OSMC, 	#0x90	; select LFO as s/system clock

; setup data
	movw	AX,	#0x70A4	; setup dividend 0x414570A4 = 12.34
	movw	dev,	AX
	movw	AX,	#0x4145
	movw	dev+2, 	AX

	movw	AX,	#0x3333	; setup divisor 0x40233333 = 2.55
	movw	der, 	AX
	movw	AX,	#0x4023
	movw	der+2, 	AX

	mov	MDUC, 	#0xC0	; division setup, no interrupt
	mov	A,	der+2
	set1	A.7		; add hidden 1
	mov	B,	A
	xch	A,	X	
	clrb	A
	movw	MDBH,	AX	; load divisor MSW
	movw	AX,	der
	movw	MDBL, 	AX	; load divisor LSW	
	
	mov	H, 	#127	; bias
	mov	A,	dev+2
	set1	A.7		; add hidden 1
	mov	X,	dev+1
	
	cmp	A,	B	
	bh	sh7		; dividend > divisor
	bnz	sh8		; dividend < divisor
	movw	BC,	AX	; preserve AX
	movw	AX,	dev	; MSB(dividend) = MSB(divider)
	cmpw	AX,	der
	movw	AX,	BC	; restore AX
	bh	sh8		; dividend > divisor
	bnz	sh7		; dividend < divisor
	
sh8:	movw	MDAH,	AX	; shift dividend 8 bits left
	mov	A, 	dev	; compose LSW(dividend)
	clrb	X
	dec	H		; adjust the bias	
	br	ft0

sh7:	shrw	AX,	1	; shift dividend 7 bits left
	movw	MDAH,	AX	; load dividend MSW		
	mov	A,	dev
	clrb	X
	rorc	A,	1
	xch	A,	X
	mov1	A.7,	CY
	xch	A, 	X
ft0:	movw	MDAL, 	AX	; load dividend MSW

;---------------------------------------------------------
	set1	MDUC.0		; first division
	movw	BC,	der+2	; compute the order
	shlw	BC,	1	; B = divisor order (biased)
	movw	AX,	dev+2 
	shlw	AX, 	1	; A = dividend order (biased)
	sub	A,	B	; subtract the orders
	add	A, 	H	; add the bias	
	clrb	X
	shrw	AX,	1
	movw	res+2, 	AX	; order	
	
ft1:	mov	A, 	MDUC
	bt	A.0,	ft1	; wait for completion

	movw	AX,	MDAL	; get 8 bits of result
	movw	BC,	AX	; save it temporarily

	movw	AX,	MDCL	; dividend = remainder shifted
	movw	tmp,	AX
	movw	AX,	MDCH
	xch	A,	X
	mov	X,	tmp+1
	movw	MDAH,	AX	; new devidend MSW
	mov	A,	tmp
	clrb	X
	movw	MDAL,	AX	; new devidend LSW
	
;--------------------------------------------------------	
	set1	MDUC.0		; second division
	movw	AX, 	BC	; get back the first quotient	
	xch	A,	X	; A = result bits
	clr1	A.7		; remove hidden 1
	or	A,	res+2
	mov	res+2, 	A	; first byte of result + order	
	
	mov	A,	der+3	; compose the sign
	xor	A,	dev+3	; msb(A) = sign bit
	and	A, 	#0x80
	or	A,	res+3
	mov	res+3,	A	; sign + order
	
ft2:	mov	A, 	MDUC
	bt	A.0,	ft2	; wait for completion

	movw	AX,	MDAL	; get 8 bits of result
	movw	BC,	AX

	movw	AX,	MDCL	; dividend = remainder shifted
	movw	tmp,	AX
	movw	AX,	MDCH
	xch	A,	X
	mov	X,	tmp+1
	movw	MDAH,	AX	; new devidend MSW
	mov	A,	tmp
	clrb	X
	movw	MDAL,	AX	; new devidend LSW
	
;--------------------------------------------------------	
	set1	MDUC.0		; third division
	movw	AX,	BC	; get back the second quotient
	xch	A,	X	
	mov	res+1, 	A	; second byte of result 
ft3:	mov	A, 	MDUC
	bt	A.0,	ft3	; wait for completion

	movw	AX,	MDAL	; get 8 bits of result
	xch	A,	X	
	mov	res, 	A	; third byte of result
        nop
        END 
Аватара пользователя
Kavka
Мудрый кот
Сообщения: 1810
Зарегистрирован: Чт июн 10, 2010 08:55:35
Откуда: Сибирские Афины

Алгоритмы

Сообщение Kavka »

К вопросу о хитрых алгоритмах.
Полезная книга: Генри Уоррен "Алгоритмические трюки для программистов" (оригинальное название "Hacker's Delight" ).
Есть сайт посвящённый этой книге - http://www.hackersdelight.org/ (Google-Перевод)
СпойлерКнига в Сундуке Кота
Изображение
Вложения
Hacker's_Dilight.jpg
(53.46 КБ) 2434 скачивания
Последний раз редактировалось Kavka Пт фев 01, 2013 06:58:23, всего редактировалось 1 раз.
Аватара пользователя
Rimsky
Грызет канифоль
Сообщения: 299
Зарегистрирован: Вт июн 15, 2010 07:16:42
Откуда: Иркутск
Контактная информация:

Re: Хитрые, необычные алгоритмы и код

Сообщение Rimsky »

Очень удобные макросы от уважаемого Чена (AVR ассемблер)
Вложения
avr.inc.rar
(982 байт) 584 скачивания
Аватара пользователя
Kavka
Мудрый кот
Сообщения: 1810
Зарегистрирован: Чт июн 10, 2010 08:55:35
Откуда: Сибирские Афины

Re: Хитрые, необычные алгоритмы и код

Сообщение Kavka »

Язык программирования Си, препроцессор, макросы, 8-bit AVR

Далеко не все в должной степени уделяют внимание препроцессору. И потом появляются обсуждения подобные этому.
Всё бы ничего, но утверждение, что для работы с ножками МК нужно три define-а, IMHO, показывает недостаточное знание темы.
Никого ругать и переубеждать не собираюсь. Как говориться "на вкус и цвет...".

Я взял из своих исходников некоторые макросы.
"Причесал" их и собрал в один файл. (Надеюсь нигде не ошибся.)
Возможно они не идеальны :) , но работают.
Примеры в комментариях и под спойлером.
Спойлер

Код: Выделить всё

#include <avr/io.h>

#include "avr_io_macros.h"

#define LED1 B, 1
#define LED2 B, 2

void main()
{
  char a;
  
  PIN_INIT_AS_INPUT(LED1);
  PIN_PULLUP_ON(LED1);
  a = PIN_GET(LED1);
  PIN_PULLUP_OFF(LED1);
    
  PIN_INIT_AS_OUTPUT(LED1);
  PIN_INIT_AS_OUTPUT(LED2);

  PIN_CLR(LED1);
  PIN_CLR(LED2);

  while (1)
  {
    PIN_NEG(LED1);
    PIN_NEG(LED2);
  }
}
Сама по себе тема макросов банальна.
Поэтому прошу - хватит с макросами для ассемблера и Си (достаточно примеров от Чана и этих).
Кому интересно посмотрит, почитает документацию - разберётся.
Вложения
avr_io_macros (2).h
(3.83 КБ) 574 скачивания
Когда уже ничего не помогает - прочтите, наконец, инструкцию.
Лучший оптимизатор находится у вас между ушей. (Майкл Абраш, программист Quake и QuakeII)
Избыток информации ведёт к оскудению души - Леонтьев А. (сказано в 1965 г.)
Ответить

Вернуться в «Разные вопросы по МК»