Kupiwszy na odść znanym portalu aukcyjnym, przecenione i jak widać nieco wybrakowane (co w niczym nie wadzi dla celów edukacyjno rozwojowych) moduły LED P3 RGB 64x64 pix,
zbudowałem taki oto ledowizor 192x128 pix, RGB bez skali, czyli 7 barw + czarny (jak zauważył kolega, nawet potrafię diodę na czarno zaświecić, na co ja się zapytałem czy woli z wstępnym rozbłyskiem czy bez...).
Dawno temu już robiłem monochromatyczne tabliczki led i napisałem do nich soft na AVR M32 oczywiście w asemblerze, jako że prócz ( a tfuuu..) Basica, był to jeden z pierwszych języków który począłem zgłębiać będąc podrostkiem, a więc jest mi on bliższym niż koszulka z napisem C cośtam .
W ledowizorze zastosowałem M1284; 1 nibel na pix=12kB ram, wiem, że gubię 1bit na pixel, ale odświeżanie treści zajmuje 40% czasu, a gdybym jeszcze robił jakieś rotacje bajtów to wogóle nie dam rady tego przesłać do wyświetlaczy, nie ma tu PMP, DMA i innych gadgetów, dopasoawłem bios i porty do złącza HUB75 i ledowizor zaczął świecić, a po kilkunastu dniach debugowania, nawet poprawnie wyświetlać treści. Mój ledowizor sterowany jest ze standardowej klawiatury ze złączem PS2, do której napisanie hendlera (12 lat wstecz, też ASM) zajęło mi zylion godzin (nie udało mi się wtedy wygooglać gotowca).
Mandelbrot kusił mnie już przy małej ledownicy, ale wtedy myślałem: mono 32x64 to mało, pozatym te floaty, si, za skomplikowane...
Później, gdy czyniłem pierwsze kontakty z Pic32 na Mp-labie, dzięki Lucio DI Jasio, procek narysował Mandelbrota, no ale tam, float-y są na dzień dobry.
Kod: Zaznacz cały
// Z lucio Di Jasio Pic32 Pseudocode:
x = x0;
y = y0;
k = 0;
// core iteration
do { x2 = x*x;
y2 = y*y;
y = 2*x*y+y0;
x = x2-y2+x0;
k++;
} while ( (x2 + y2 < 4) & & ( k < MAXIT));
// check if the point belongs to the Mandelbrot set
if ( k == MAXIT) plot( j, i);
// Mandelbrot Set graphic demo
// configuration bit settings, Fcy=72 MHz, Fpb=36 MHz
#pragma config POSCMOD=XT, FNOSC=PRIPLL
#pragma config FPLLIDIV=DIV_2, FPLLMUL=MUL_18, FPLLODIV=DIV_1
#pragma config FPBDIV=DIV_2, FWDTEN=OFF, CP=OFF, BWP=OFF
#include < p32xxxx.h >
#include < plib.h >
#include < explore.h >
#include < graphic.h >
#define SIZE VRES
#define MAXIT 64
void mandelbrot( float xx0, float yy0, float w)
{
float x, y, d, x0, y0, x2, y2;
int i, j, k;
// calculate increments
d = w/SIZE;
// repeat on each screen pixel
y0 = yy0;
for (i=0; i < SIZE; i++)
{
x0 = xx0;
for (j=0; j < SIZE; j++)
{
// initialization
x = x0;
y = y0;
k = 0;
// core iteration
do {
x2 = x*x;
y2 = y*y;
y = 2*x*y + y0;
x = x2-y2 + x0;
k++;
} while ( (x2 + y2 < 4) & & ( k < MAXIT));
// check if the point belongs to the Mandelbrot set
if ( k == MAXIT) plot( j, i); //if ( k & 2) plot( j, i);
// compute next point x0
x0 += d;
} // for j
// compute next y0
y0 += d;
} // for i
} // mandelbrot
Aż tu nagle kilka tygodni temu, net mi pokazał taki obrazek:
https://rbteam.wordpress.com/2011/09/11 ... andelbrot/
Przypomniałem sobie też, że kiedys gdzieś widziałem Mandela w Z80asm i był on na integerach.
Chwila rozmyślań i wniosek: przecież jeśli nie wymagać dokładności niezbędnej do zoomowania robala, to da się to zrobić na integerach traktując ich wartości stałopozycyjnie!
Założenie było takie: jedności ma rozdzielczość 6 bitów, czyli mając 192 punkty ekranu argument obliczeń mógł by przyjmować wartość od -2 do 2. Idąc dalej tą drogą wymyśliłem, że w takim razie za każdą iteracją kwadrat będzie przesuwał 'kropkę' o 6 bitów(albo o 12?), a więc aby być na bierząco w operacji porównania czy moduł |Z|>2 muszę też przesuwać wzorzec do porównań. Wyszło mi że dla maksymalnie 8 iteracji potrzebuję 48 bitowe słowa... Hmm zaczyna się robić z tego niezły makaron, ale nie martwiąc się co będzie dalej, przystąpiłem do kodowania mej świetlistej koncepcji.
Po wielu godzinach kombinowania, szukania błędów oraz dopatrzeniu się zasadniczej ułomności w samej koncepcji światłej mej, przełamałem się i zastosowałem google. No i znalazłem:
https://www.pouet.net/prod.php?which=53287
Edit: Dałem link bezpośredni, gdyż w codegolf wywalili ten assemblerowy.
Kolejna chwila (czytaj 60 minut) z dokładna listą instrukcji x86:
http://dsearls.org/courses/C391OrgSys/I ... n_set.html
doprowadziły do ujawnienia sprytu tego rozwiązania.
Edit: staram się tabularyzować wszystkie spacja, a nadal kolumny nie trzymają pinou...
Kod: Zaznacz cały
;===========================================================================
;
; "Microbrot" by Sir_Lagsalot
org 100h
mov al,13h
int 10h ;VGA 320x200 @ 256 colors, ah domyślnie 0
les ax,[bx] ;do rej ES:AX załadowana zawartość pam [DS:BX] pointer 4 bajty
;ES segment Vram, zkąd ładowany? DI=0
FillLoop:
cwd ;if high bit of AX = 1 then DX = 0xFFFF else 0,
;ale można by użyć xor dx,dx? byłoby jaśniej
mov ax,di ;DI licznik bajtów=adres w Vram 1bajt na pix dla 256 colorów
mov cx,320 ;CX = Maxiter oraz Xmax obrazu
div cx ;AX = (DX,AX)/CX; DX = remainder, czyli DX=xx AX=yy
sub ax,100 ;yy od -100 przesunięcie układu współrzędnych
dec dh ;xx od -256
xor bx,bx ;BX=reZ = 0; Z0=(0+i0)
xor si,si ;SI=imZ = 0
MandelLoop:
mov bp,si ;BP = imZ
imul si,bx ;(DX,SI) = SI * BX SI = reZ * imZ
add si,si ;SI = 2 * reZ * imZ
imul bx,bx ;(DX,BX) = BX^2 BX = ReZ^2
jo MandelBreak
imul bp,bp ;(DX,BP) = BP^2 BP = imZ^2
jo MandelBreak
add bx,bp ;bx = ReZ^2 + imZ^2
jo MandelBreak
sub bx,bp ;bx = reZ^2 bp = imZ^2
sub bx,bp ;bx = ReZ^2 - imZ^2
sar bx,6 ;BX/64 signed
add bx,dx ;ReZ = BX/64 + xx
sar si,6 ;SI/64 signed
add si,ax ;ImZ = SI/64 + yy
loop MandelLoop;CX=CX-1, loop until <>0
MandelBreak:
xchg ax,cx ;AX (320-ilosc iteracji) = kolor, obcinany z najwyzszego bitu
stosb ;[ES:DI] = AL if DF = 0 then DI = DI + 1; czyli adres Vram
jmp FillLoop
;DI-adres Vram, CX-Maxiter i Xmax, AX-imC=yy, DX-reC=xx, BX-reZ, SI-imZ, BP=t_imZ
;
;=========================================================================
Jak widać koncepcja podziału jedności na 64 i tu występuje. Sir_Lagsalot chyba lepiej ode mnie rozumie istotę systemów liczbowych i umie ją stosować...
Nie było co się dalej zastanawiać tylko czym prędzej przekodować to do AVR. Oczywiście nie obyło się bez kilku błędów, przeoczeń i literówek (kolejne 'chwile' wyjęte z pięknego życia), z których każda dawała śmieszne efekty. Choć z początku wcale nie dawała efektów bo albo było czarno albo system odjeżdżał z powodu rozjechania się stosu.
Podczas wielokrotnego czytania programu z monitora nie byłem w stanie zobaczyć ostatniego błędu, powodującego, że kolorowe plamy jakby chciały, ale z jakiegoś powodu nie potrafiły ułożyć zbioru M.
Przypomniałem sobie wówczas jak w zamierzchłej przeszłości bawiłem się debugiem pod dosem, tam polecenie r wyświetlało zawartość rejestrów i takiego czegoś właśnie potrzebowałem tutaj. Tak więc dostosowana do potrzeby procka Print_registers zagościła w kodzie. Możliwość pracy krokowej zapewniona jest przez odpowiednie zmienne sterowane z klawiatury. Rejestr B pokazuje krok pętli.
https://youtu.be/f8bzZb-RzoA
Prośba do czepialińskich: ja wiem, że styl poniższego kodu jest mało czytelny... (edit poprawiłem), niżej zamieściłem wersje elegancką. Aha: piszę w slangu angielsko-polskim, jako, że w takim systemie mnemonicznym szybciej układam myśli.
Wersja debugowa i efekt jej pracy:
Kod: Zaznacz cały
;=========================================================================
; M A N D E L B R O T AVR asm bez float, code by PeterB314 (C) 2019.
; pierwsza zdebugowana działa
; inspiracja: http://www.pouet.net/prod.php?which=53287
;
;instrukcje z x86: Imul, Sar są zastąpione przez Mul_1616s oraz odpowiednią ilość Asr R23 Ror R22
;Mul_1616s jest na koncu następnego listingu.
Mandelbrot_regen: .equ max_Mandel_Iter=8
Clr R30 ;Y loop
Next_YL_Mandel: ;sword R29:R28 = (yy - 64)
Ldi YH,0 Mov YL,R30
Ldi A,64 Sub R28,A
Ldi A,0 Sbc R29,A
Clr R31 ;X loop
Next_XL_Mandel: ;sword R27:R26 = (xx - 128)
Ldi XH,0 Mov XL,R31
Ldi A,128 Sub R26,A
Ldi A,0 Sbc R27,A
Push R30 Push R31 ;R30 R31 Używany do Mul_1616s
Ldi A,max_Mandel_Iter
Clr R22 Clr R23
Clr R24 Clr R25 ;Z(0) = (0+i0)
Ldi B,0 call Print_registers
LooP_Mandel_Iter:
Push R26 Push R27 ;xx
Push R24 Push R25 ;imZ
Push R22 Push R23 ;reZ
Ldi B,1 call Print_registers
MovW R30,R22
MovW R26,R24
Rcall MUL_1616s ;R2322 = reZ * imZ
Lsl R22 Rol R23
Movw R18,R22 ;R1918 = 2 * reZ * imZ [si]
Ldi B,2 call Print_registers
Pop R31 Pop R30
MovW R26,R30 ;reZ
Ldi B,3 call Print_registers
Rcall MUL_1616s ;[R25:R24:R23:R22]=Z[R31:R30] * X[R27:R26]
MovW R20,R22 ; R2120 = A = reZ^2; [BX]
Ldi B,4 call Print_registers
Pop R31 Pop R30
MovW R26,R30 ;imZ
Ldi B,5 call Print_registers
Rcall MUL_1616s ;R2322 = B = imZ^2; [BP]
Ldi B,6 call Print_registers
Add R20,R22 Adc R21,R23
Brsh Skip_003 Rjmp Break_Mandel_Iter
Skip_003:
Ldi B,7 call Print_registers
Sub R20,R22 Sbc R21,R23
Sub R20,R22 Sbc R21,R23
Movw R22,R20 ;reZ = ReZ^2 - imZ^2
Ldi B,8 call Print_registers
POP R27 POP R26 ;XH:XL
Ldi B,9 call Print_registers
Asr R23 Ror R22
Asr R23 Ror R22
Asr R23 Ror R22
Asr R23 Ror R22
Asr R23 Ror R22
Asr R23 Ror R22
Ldi B,10 call Print_registers
Add R22,R26
Adc R23,R27 ;ReZ = (ReZ^2 - imZ^2) / 64 + xx
Movw R24,R18
Ldi B,11 call Print_registers
Asr R25 Ror R24
Asr R25 Ror R24
Asr R25 Ror R24
Asr R25 Ror R24
Asr R25 Ror R24
Asr R25 Ror R24
Ldi B,12 call Print_registers
Add R24,R28 Adc R25,R29 ;ImZ = (2 * reZ * imZ) / 64 + yy
; Add R24,R28 Adc R24,R29 = tu był główny błąd, daje ciekawe artefakty :)
Ldi B,13 call Print_registers
Dec A Tst A
Breq End_Mandel_Iter
Rjmp LooP_Mandel_Iter
Break_Mandel_Iter: POP R27 POP R26
End_Mandel_Iter: POP R31 POP R30
Ldi B,14 call Print_registers
Sts Pixel_put_color,A
Mov XL,R31
Push YL
Mov YL,R30
SET
Call Set_Point_XY
POP YL ; był brak ochrony YL !
Ldi B,15 call Print_registers
Lds A,Cur_edit_cmd
Cpi A,0x40 Breq Exit_Mandel
Lds A,Mt_X Inc R31
Cp R31,A Brsh Next_Mandel_line
Rjmp Next_XL_Mandel
Next_Mandel_line:
Lds A,Mt_Y Inc R30
Cp R30,A Brsh Exit_Mandel
Rjmp Next_YL_Mandel
Exit_Mandel: Store Pixel_put_color,4
RET
;
;==========================================================================
Poniżej wersja elegancka z wycofanym wyświetlaniem rejestrów oraz szeregiem zmian porządkowych; jest inna dyspozycja rejestrów dla wyników tymczasowych, gdyż w toku dalszej rozbudowy, potrzebuję R18,19,20,21 do sterowania powiększaniem i innych efektów.
Kod: Zaznacz cały
;=========================================================================
;
; M A N D E L B R O T AVR asm bez float, code by PeterB314 (C) 2019.
; inspiracja: http://www.pouet.net/prod.php?which=53287
;
/* dla skrócenia listingu, czasem kilka instrukcji w jednym wierszu jeśli są logicznie powiązane
Funkcje zewnętrzne: Set_Point_XY, zaświeca pixel (XL,YL) w kolorze [Pixel_put_color]
Zmienne zewnętrzne: [Cur_edit_cmd] - kod ostatnio wydanego polecenia z klawiatury
[Mt_X],[Mt_Y] - rozmiar wyświetlacza
Z(n+1) = Z(n)^2 + C; Z(0)=(0+i0), dla C należcego do płaszczyzny Z (pole ekranu)
*/
.equ Init_max_Iter=32
Mandelbrot_regen:
Clr R30 ;licznik współrzęna ekranowa Y
Next_YL_Mandel:
Ldi YH,0
Mov YL,R30 ;YHYL - zmienna urojona imC
Ldi A,64 ;czyli yy=-1
Sub R28,A
Ldi A,0x00
Sbc R29,A // sword R29:R28 = (yy - 64)
Clr R31 ;licznik współrzęna ekranowa X
Next_XL_Mandel:
Ldi XH,0
Mov XL,R31 ;XHXL - zmienna rzeczywista reC
Ldi A,128 ;czyli xx=-2
Sub R26,A
Ldi A,0x00
Sbc R27,A // sword R27:R26 = (xx - 128)
Push R30 Push R31 // wskażnik piksela XY, R30 R31 Używany do Mul_1616s
Ldi A,Init_max_Iter ;A alias dla R16
movw R22,R26 ;Ponieważ pierwsza iteracja dla Z(0)=(0+i0) zakonczy się wynikiem (xx+iyy)
movw R24,R28 ;to odrazu Z(0) = (xx+iyy), zmniejszy to ilość iteracji o 1
;a nadto pozwala to na łatwe przejście do Julia set
;wtedy dalej, zamiast dodawać xx i yy należy dodać J(re+im)
LooP_Mandel_Iter:
;Jeśli w tym miejscu wykonać reZ=|reZ| oraz imZ=|imZ| uzyskamy obraz fraktala "Burning Ship"
// yy udało się zachować nienaruszany w pętli
Push R26 Push R27 // xx
Push R24 Push R25 // imZ
Push R22 Push R23 // reZ
MovW R30,R22 // reZ * imZ
MovW R26,R24
Rcall MUL_1616s ;[R25:R24:R23:R22]=Z[R31:R30] * X[R27:R26]
;o 1 mniej, gdyż tu reZ*imZ nie jest mnożone przez 2 skoro zaraz ma być dzielone przrz 64
Asr R23 ror R22
Asr R23 ror R22
Asr R23 ror R22
Asr R23 ror R22
Asr R23 ror R22
Movw R12,R22 // R1213 = (reZ * imZ)/64 [si]
Pop R31 Pop R30 // reZ
MovW R26,R30
Rcall MUL_1616s
;tu powinno być sprawdzenie Overflow tak jak w wersji z x86 ale brak flagi w procku oraz brak czasu na
; napisanie odpowiedniego kodu wykrywającego, spowodował że nie ma tego testu w tym miejscu, jak i poniżej.
MovW R14,R22 // R1415 = A = reZ^2; [BX]
Pop R31 Pop R30 // imZ
MovW R26,R30
Rcall MUL_1616s // R2322 = B = imZ^2; [BP]
Add R14,R22 Adc R15,R23 // break If (A+B) > 0xFFFF
Brlo Break_Mandel_Iter
Sub R14,R22 Sbc R15,R23
Sub R14,R22 Sbc R15,R23 //reZ = reZ^2 - imZ^2
;jeden Asr więcej powiększa, mniej oddala, trzeba też dostosować maxiter i ilość Lsr A koloru
Movw R22,R14
Asr R23 ror R22
Asr R23 ror R22
Asr R23 ror R22
Asr R23 ror R22
Asr R23 ror R22
Asr R23 ror R22
POP R27 POP R26 // xx
Add R22,R26 Adc R23,R27 //ReZ = (ReZ^2 - imZ^2) / 64 + xx
;dla Julia set zamiast xx dodać reJ
Movw R24,R12
Add R24,R28 Adc R25,R29 //ImZ = (2 * reZ * imZ) / 64 + yy
;dla Julia set zamiast yy dodać imJ
Dec A
Tst A
Breq End_Mandel_Iter
Rjmp LooP_Mandel_Iter ; w trakcie testowania czasem przekraczało 63 instrukcje
Break_Mandel_Iter:
POP R27 POP R26 // xx był zrzucony na początku pętli
End_Mandel_Iter:
POP R31 POP R30 // liczniki współrzędnych ekranu
lsr A Lsr A ; dopasowanie koloru Max_color=7
Sts Pixel_put_color,A ; no chyba samokomentująca się? ;)
mov XL,R31
push yl ;ochrona lokalnego yy
mov YL,R30
SET
call Set_Point_XY ;niszczy YL
pop yl
Lds A,Cur_edit_cmd
Cpi A,0x40 ;Shift+Esc = przerwij i wyjdź
Breq Exit_Mandel
Lds A,Mt_X
Inc R31
Cp R31,A
Brsh Next_Mandel_line
Rjmp Next_XL_Mandel
Next_Mandel_line:
Lds A,Mt_Y
Inc R30
Cp R30,A
Brsh Exit_Mandel
Rjmp Next_YL_Mandel
Exit_Mandel:
Store Pixel_put_color,4 ;macro
RET
;==========================================================================
; USAGE: [R25:R24:R23:R22]=Z[R31:R30] * X[R27:R26]
; DECRIPTION: Signed multiply of two 16bits numbers with 32bits result.
; STATISTICS: Cycles :31 + ret Words : 23 + ret
; Based on avr201.asm with different register usage.
Mul_1616s: Push A Push B
Clr R2
muls r31, r27
movw r25:r24, r1:r0
mul r30, r26
movw r23:r22, r1:r0
Mov A,R31
Mov B,R26
mulsu A,B
sbc r25, r2
add r23, r0
adc r24, r1
adc r25, r2
Mov A,R27
Mov B,R30
mulsu A,B
sbc r25, r2
add r23, r0
adc r24, r1
adc r25, r2
Pop B Pop A RET
;
; Integer Mandelbrot End
;
;===========================================================================
Następnie zwiększyłem Max_iter do 64 i zmieniłem kolorowanie,
oraz powiększanie zwiększając ilość rotacji w prawo.
W porównaniu do wersji dosowej, zostaje czarna linia pozioma dla xx=0, jeszcze nie znalazłem przyczyny.
Ciekawy materiał źródłowy - coding chelange:
https://www.youtube.com/watch?v=6z7GQewK-Ks
Na zakonczenie totalne szaleństwo obliczeniowe:
https://www.youtube.com/watch?v=u1pwtSBTnPU
Dziękuję i pozdrawiam!
Edit: w listingu powyżej odpisałem uwagę o możliwości uzyskania obrazu fraktala "Burning Ship".