Skocz do zawartości

[Asm] Jak napisaś w asemblerze funkcje, która bądzie sprawdzała, czy podany rok jest przestępny.


tadeko

Pomocna odpowiedź

Ogólnie wiadomo, że dany rok jest przestępny, gdy liczba lat dzieli się bez reszty przez 4, ale nie dzieli się przez 100, pod warunkiem, że nie dzieli się przez 400.

W Bascom'ie ten problem wygląda prosto:

- If Liczba_lat Mod 4 = 0 And Liczba_lat Mod 100 <> 0 Or Liczba_lat Mod 400 = 0

Ale jak taki problem rozgryźć w asemblerze dla mikrokontrolera ATtiny2313 ???

Może ktoś już zajmował się takim problemem lub potrafi napisać odpowiednią funkcję.

Oczekuję pomocy.

Z góry dziękuję.

Link do komentarza
Share on other sites

Jeżeli już wiesz jaki jest algorytm, to jesteś jedną nogą u celu 🙂

Zastanów się jaka operacja jest tu kluczowa. No oczywiście: modulo, czyli reszta z dzielenia. Procesor AVR nie ma takiej instrukcji na swojej liście. Nie ma też dzielenia więc jest oczywiste, że musisz napisać/wyszukać odpowiednią procedurę. Tak się składa, że typowe asemblerowe funkcje dzielenia obliczają za jednym zamachem wynik (iloraz) i resztę. Teraz wystarczy tylko określić zakresy liczb, bo od długości argumentów zależy postać tego kawałka kodu. Ponieważ numer roku będzie na pewno większy od 255 i raczej mniejszy od 65535 to jasne, że jako dzielnik wystarczy liczba 16-bitowa. Dzielna też musi być tego samego typu, bo masz dzielenie przez 4, 100 i 400. Tak więc musisz napisać lub znaleźć w sieci procedurę napisaną w asemblerze AVR dzielenia dwóch liczb 16-bitowych bez znaku. Jeśli sobie nie poradzisz, możemy coś takiego napisać razem 🙂 Potem jest już łatwo, bo dostajesz trzy reszty z dzielenia i wystarczy sprawdzić czy spełniają podane warunki.

To bardzo pouczający eksperyment (jeśli ktoś czytał Mikołajka, to wie o co chodzi).

Pisałeś coś w asemblerze AVR?

EDIT: Jeżeli nie czujesz się na siłach pisać prawdziwego dzielenia z przesuwaniem, możesz spróbować zrobić to na zwykłych odejmowaniach. Wystarczy próbować odejmować dopóki się da, czyli dopóki dzielna jest większa niż dzielnik. Wtedy to co zostanie, to reszta 🙂 Dla dużych liczb wejściowych i malej dzielnej takich odejmowań możesz zrobić dużo, ale akurat w przypadku dzielenia przez 4 nic robić nie musisz, bo resztą z dzielenia przez 4 są dwa najmłodsze bity liczby. W pozostałych przypadkach podana procedura nie będzie istotnie wolniejsza od "normalnego" dzielenia. Jego kanoniczna, 16-bitowa wersja potrzebuje zawsze 16 odejmowań i przesunięć a w Twoim przypadku obliczenie reszty z dzielenia 2016/100 będzie wymagało 20 udanych odejmowań (a na końcu zostanie 16). Dla podziału przez 400 jest jeszcze lepiej (5 odejmowań i także zostaje 16 reszty) i nawet będzie to szybsze niż dzielenie.

Link do komentarza
Share on other sites

Dziękuję koledze "marek1707" za uwagi. Problem w tym, że w asemblerze dopiero startuję. Udało mi się napisać program zegara, który po naciśnięciu jednego z 2-ch przycisków pokazuje dodatkowo stan licznika sekund. Teraz chciałem napisać program zegara, który będzie sam zmieniał czas z letniego na zimowy i na odwrót.

Choć w asemblerze dla ATtiny 2313 jest instrukcja "eor", która jest odpowiednikiem dzielenia MOD, to jednak działa ona na 2-ch rejestrach 8-bitowych, a rok (4 cyfry) wymaga 16 bitów - i tu nie umiem sobie poradzić.

Rok mogę rozdzielić na 2 sąsiednie rejestry, ale nie wiem jak spiąć je podczas wykonywania operacji arytmetycznych (dzielenie modulo lub odejmowanie) i w tym cały problem.

Link do komentarza
Share on other sites

Nic podobnego. Operację XOR można uznać za sumę modulo 2 (tak niektórzy to nazywają) tylko przy argumentach 1-bitowych czyli operacjach logicznych. Nie ma to nic wspólnego z arytmetyką i z tym co chcesz zrobić, o XOR zapomnij.

Czy rozumiesz co napisałem wcześniej? Możesz:

1. Napisać pełną funkcję normalnego dzielenia dwóch liczb 16-bitowych, odrzucać wynik a zajmować się resztą z dzielenia którą i tak dostajesz.

2. Napisać funkcję liczącą tylko resztę z dzielenia a samo dzielenie zastąpić wielokrotnym odejmowaniem bez przesuwania. To jest prostsze pojęciowo i jeśli nie masz doświadczenia w asemblerze - dużo łatwiejsze do ogarnięcia. Jedyną tutaj trudnością jest arytmetyka 16-bitowa, ale takie operacje jak odejmowanie czy dodawanie wielokrotnej precyzji w asemblerze są bardzo proste. Korzystasz z bitu stanu C, który w przypadku dodawania jest przeniesieniem wygenerowanym z 8 bitu. W przypadku dodawania kolejnych bajtów uwzględniasz przeniesienie z młodszych pozycji tak jakbyś dodawał "pod kreską" na papierze. W przypadku odejmowania bit C jest pożyczką, ale zasada jest ta sama. Weź do ręki listę instrukcji i zobacz czym różnią się od siebie instrukcje ADD i ADC oraz SUB i SBC. Od razu zrozumiesz.

Tak więc Twój algorytm liczenia modulo opierający się na odejmowaniu powinien wyglądać mniej więcej tak:

while (dzielna >= dzielnik)
  dzielna -= dzielnik;
// Wynik, czyli reszta z dzielenia został w zmiennej dzielna.

Już prościej się nie da. Jedyne rzeczy jakie tu musisz zrobić to porównanie i odejmowanie. Akurat masz prosto, bo operację porównania dwóch liczb całkowitych dodatnich robi się przez odejmowanie i sprawdzenie znaku wyniku. Wystarczy więc, że zrobisz tylko odejmowanie 16-bitowe. Jeśli się dobrze zastanowisz to wyjdzie, że są to raptem 2 instrukcje asemblerowe 🙂 Do roboty.

EDIT: Zaraz, to jeszcze napisz czy może aby nie napisałeś swojego zegara w arytmetyce BCD. To całkowicie zmienia postać rzeczy.

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Jak pisałem, jestem całkiem początkujący i dlatego naprawdę nie wiem jak odejmować do np. 2000 liczbę 400. Obydwie w systemie dwójkowym nie mogą być zapisane w 1 bajcie, a takie są rejestry w mikrokontrolerze ATtiny2313 i tu zaczynają się dla mnie schody. Nie wiem jak od liczby 2000 zapisanej w 2-ch rejestrach odjąć liczbę 400 też zapisaną w 2-ch rejestrach. Polecenia asemblerowe działają na jednym rejestrze i stałej lub na dwóch rejestrach, a tutaj mam w sumie 4 rejestry.

A propos mojego programu zegara, to korzysta on z oddzielnych rejestrów dla dziesiątek godzin, jednostek godzin, dziesiątek minut, jednostek minut, dziesiątek sekund i jednostek sekund. Dodatkowo używam jeden rejestr do obsługi kropki dziesiętnej wyświetlacza jednostek godzin (miga w takt sekund) oraz 2 rejestry dla potrzeb przycisków i wyświetlaczy czasu. Oczywiście korzystam z Timera1 do odliczania czasu oraz z Timera0 do do obsługi klawiatury (2 przyciski) i wyświetlaczy led (4 szt). Program po skompilowaniu wgrałem do ATtiny2313 zamontowanego w płytce testowej dla mikrokontrolerów AVR - ZL11. Zegar działa tak jak zamierzałem.

Link do komentarza
Share on other sites

No dobrze, a umiesz dodawać lub odejmować liczby dziesiętne na papierze? Zacznijmy od dodawania, masz taki "słupek":

   45
+27
------
 ??

Jak sobie z tym poradzisz, to i program asemblerowy napiszesz. Opisz dokładnie co robisz po kolei, może być w punktach.

A potem to samo dla operacji odejmowania 83-38=??

Link do komentarza
Share on other sites

Wreszcie znalazłem sposób jak rozwiązać problem.

Oto mój algorytm:

ldi r16, low(2001)

ldi r17, high(2001)

ldi r18, low(400)

ldi r19, high(400)

Petla:

sub r16, r18 ;odejmowanie (bez przeniesienia C) r16=r16-r18

sbc r17, r19 ;odejmowanie (z przeniesieniem C) r17=r17-r19-C

cpi r17, 0 ;porównaj r17 z 0

breq P1 ;jeśli r17=0, to skocz do P1

rjmp Petla ;jeśli nie, to skocz do Petla

P1:

cpi r16, 0 ;porównaj r16 z 0

breq P2 ;jeśli r16=0, to skocz do P2

rjmp P3 ;jeśli nie, to skocz do P3

P2:

nop ;podany rok dzieli się przez 400 bez reszty, więc rok jest przestępny

rjmp End

P3:

nop ;podany rok nie dzieli się przez 400 bez reszty, więc rok nie jest przestępny

End:

ret

Teraz zamiast nop wystarczy wstawić odpowiednie polecenia. W każdym bądź razie działa.

Przy założeniu, że zegar będzie pracował w latach 2000 do 2099 można nie sprawdzać podzielności przez 400, ani przez 100, a jedynie sprawdzić podzielność przez 4, a to już jest banalna sprawa, bo wystarczy sprawdzić 2 najmłodsze bity roku.

Link do komentarza
Share on other sites

Twój program jest kompletnie bez sensu i nie ma prawa działać. Wygląda mi na to, że założyłeś sobie iż procesor trzyma w młodszym rejestrze dwie najmłodsze cyfry liczby dziesiętnej a w starszym pozostałe dwie. Gdyby tak było, to rzeczywiście pętla wykonywałaby się dopóki starszy rejestr się wyzeruje a potem wystarczyłoby sprawdzić "zerowość" młodszego. Niestety to tak nie działa. W młodszym jest 8 młodszych bitów a w starszym 8 starszych.

Proszę bardzo, sprawdźmy: niech wejściem będzie liczba 1134 - ewidentnie nie jest podzielna przez 400. Zatem w parze R17:R16 mamy kolejno:

1134 czyli w rejestrach, odpowiednio w zapisie szesnastkowym: 0x04, 0x6e

734: 0x02, 0xde

334: 0x01, 0x4e

Co teraz zrobi Twój program? Bo poprawnie tu powinien się zatrzymać, a 334 to właśnie reszta z dzielenia. Nie wolno więcej odejmować, bo 334<400. Ponieważ jednak rejestr R17 nie jest zerowy, program odejmie raz jeszcze 400 i mamy:

65470: 0xff, 0xbe <- Tak działa arytmetyka modulo 2^16 🙁

65070: 0xfe, 0x2e itd..

a "Petla" wykonuje się jeszcze bardzo długo aż w końcu - przy którymś obrocie - uzyska wreszcie R17=0 i sprawdzi R16, ale wtedy nie będzie to już miało żadnego sensu. Algorytm musi przed odjęciem sprawdzać szansę jego wykonania bez niedomiaru, czyli de facto porównywać obie liczby. Masz do tego specjalne instrukcje: CP i jego odpowiednik dłuższej precyzji CPC. Może spróbuj jednak napisać to poprawnie - to fajne ćwiczenie.

Cieszę się, że z całego wyrażenia zostało Ci w kodzie tylko sprawdzanie dwóch najmłodszych bitów (pisałem o tym wcześniej), ale kod testujący podzielność jest do poprawki. Musisz się jeszcze wiele nauczyć - także jak widać testowania programów. Polecam zacząć od czegoś o arytmetyce komputerów, bo tu masz wyraźne braki a w asemblerze to podstawa. Dopóki nie nabierzesz biegłości w manipulacjach liczbami i formatami, dopóki będziesz tworzył takie smutne przypadki jak powyżej.

Link do komentarza
Share on other sites

Dziękuję za zwrócenie mi uwagi. Faktycznie program ten działa poprawnie jedynie dla lat, na których się skupiłem, czyli 2000 do 2100. Nie założyłem, że w rejestrach mam odpowiednio 2 młodsze i 2 starsze cyfry liczby lat, o czym świadczą 4 pierwsze linijki programu. Wiedziałem, że w rejestrach są odpowiednio młodsze i starsze bajty 16-to bitowego zapisu liczby lat. Niestety nie sprawdziłem działania dla innych zakresów lat niż podane wyżej. Muszę nad tym jeszcze popracować. Mam nadzieję, że uda mi się ten problem rozwiązać, jest okazja do dalszych ćwiczeń.

Pociesz mnie tylko pewne stare stwierdzenie - nie programów bezbłędnych, są tylko niedostatecznie przetestowane - choć to marna pociecha.

Jeszcze raz dzięki za uwagę.

[ Dodano: 24-05-2016, 21:03 ]

Zmodyfikowałem ten program i teraz działa również dla roku 1134 i mam nadzieję, że dla wszystkich innych lat, lecz na razie powyżej 399 roku. Dla lat 1 do 399, w ramach ćwiczenia będę musiał wprowadzić kolejne poprawki.

Obecnie mój program wygląda tak:

ldi r16, low(1134)

ldi r17, high(1134)

ldi r18, low(400) ;0x90

ldi r19, high(400) ;0x01

Petla:

sub r16, r18 ;odejmowanie (bez przeniesienia C) r16=r16-r18

sbc r17, r19 ;odejmowanie (z przeniesieniem C) r17=r17-r19-C

cpi r17, 0 ;porównaj r17 z 0

breq P1 ;jeśli r17=0, to skocz do P1

cp r16, r18 ;porównaj r16 z r18 (C=1 gdy r18 > r16)

brcs P0 ;jeśli C=1, to skocz do P0

cp r17, r19 ;porównaj r17 z r19 (C=1 gdy r19 > r17)

brcs P1 ;jeśli C=1, to skocz do P1

rjmp Petla ;jeśli nie, to skocz do Petla

P0:

clr r20

clr r21

add r20, r17 ;kopia r17

add r21, r19 ;kopia r19

dec r20 ;odejmij pożyczkę (C=1)

cp r20, r21 ;porównaj r20 z r21, czyli r17-1 z r19

brcc Petla

P1:

cpi r16, 0 ;porównaj r16 z 0

breq P2 ;jeśli r16=0, to skocz do P2

rjmp P3 ;jeśli nie, to skocz do P3

P2:

nop ;dzieli się bez reszty, więc rok jest przestępny

rjmp End

P3:

nop ;nie dzieli się bez reszty, więc rok nie jest przestępny

End:

ret

[ Dodano: 24-05-2016, 22:45 ]

To jest poprawiony program, działa dla lat 1 do ...

ldi r16, low(2001) ;rok

ldi r17, high(2001) ;rok

ldi r18, low(400) ;0x90

ldi r19, high(400) ;0x01

Petla:

cpi r17, 0 ;porównaj r17 z 0

breq P1 ;jeśli r17=0, to skocz do P1 (rok < 256)

cp r16, r18 ;porównaj r16 z r18 (C=1 gdy r16 < r18)

brcs P0 ;jeśli C=1, to skocz do P0

cp r17, r19 ;porównaj r17 z r19 (C=1 gdy r17 < r19)

brcs P1 ;jeśli C=1, to skocz do P1

sub r16, r18 ;odejmowanie (bez przeniesienia C) r16=r16-r18

sbc r17, r19 ;odejmowanie (z przeniesieniem C) r17=r17-r19-C

rjmp Petla ;jeśli nie, to skocz do Petla

P0: ;r16 < r18 wiec sprawdzamy r17 i r19

clr r20

clr r21

add r20, r17 ;kopia r17

add r21, r19 ;kopia r19

dec r20 ;odejmij pożyczkę (C=1)

cp r20, r21 ;porównaj r20 z r21, czyli r17-1 z r19

brcs P1 ;jeśli C=0, to skocz do Petla

sub r16, r18 ;odejmowanie (bez przeniesienia C) r16=r16-r18

sbc r17, r19 ;odejmowanie (z przeniesieniem C) r17=r17-r19-C

rjmp Petla ;jeśli nie, to skocz do Petla

P1:

cpi r16, 0 ;porównaj r16 z 0

breq P2 ;jeśli r16=0, to skocz do P2

rjmp P3 ;jeśli nie, to skocz do P3

P2:

nop ;dzieli się bez reszty, więc rok jest przestępny

rjmp End

P3:

nop ;nie dzieli się bez reszty, więc rok nie jest przestępny

End:

ret

Proszę o krytyczne uwagi dotyczące powyższego algorytmu. Zdaję sobie sprawę, że na pewno nie jest on idealny i wielu doświadczonych programistów asemblera uzna, że wiele w tym programie można poprawić na lepsze, dlatego proszę o uwagi.

Link do komentarza
Share on other sites

Masz jeszcze kłopoty z arytmetyką dłuższych liczb. Zobacz, żeby porównać dwie liczby 16-bitowe wystarczy napisać:

CP R16,R18

CPC R17,R19

a następnie sprawdzić flagi odpowiednim skokiem.

Propagacja przeniesień/pożyczek - dzięki instrukcjom je uwzględniającym - robi się sama.

W sumie to nie rozumiem dlaczego nie skorzystałeś z kodu pokazanego wcześniej tylko męczysz niemiłosiernie pokrętny algorytm:

while (dzielna >= dzielnik) 
  dzielna -= dzielnik;

Przecież zabawa w kompilator i napisanie na tej podstawie programu wydaje się dość proste:

PETLA:
CP R16,R18
CPC R17,R19
BRLO ZROBIONE
SUB R16,R18
SBC R17,R19
RJMP PETLA
ZROBIONE:
OR R16,R17
BREQ RESZTA_ZERO
......

🙂 ?

Link do komentarza
Share on other sites

Serdecznie dziękuję za uwagi. Widać od razu, że Ty jesteś doświadczony i biegasz, a raczej śmigasz po asemblerze, a ja niestety dopiero raczkuję.

Z kodu

while (dzielna >= dzielnik)

dzielna -= dzielnik;

nie umiałem skorzystać, bo nie potrafiłem przełożyć go na asebler. Gdybym pisał program w bascomie, to taką sekwencję bym zastosował. W baskomie napisałem program zegara, który sam zmienia czas na letni i zimowy, ale program ten jest tak duży, że zamiast ATtiny2313 musiałem zastosować ATtiny4313.

Jeszcze raz dziękuję. Przetestowałem Twój program i stwierdziłem, że nie tylko jest znacznie krótszy, ale i szybszy.

Pozdrawiam.

Link do komentarza
Share on other sites

To może - skoro już wiemy o Twojej konstrukcji - wrzuć jakieś kody tego zegara. Może co ciekawsze procedury plus ogólny opis zasady działania? Być może da się to jakoś naprostować i sprawić, by bardziej zwięzły kod mieścił się w malutkim tiny2313? Trochę nie do wiary, by te kilka funkcji zajmowało więcej jak 1000 słów pamięci FLASH. Myślę, że prosty zegar napisany nawet w C zająłby mniej... Aż spróbuję, z ciekawości 🙂

Długi i pogmatwany program jest trudny nie tylko w uruchamianiu i testowaniu, ale także ciężko się zmusić by go jakoś rozwijać. Po prostu po tygodniu (w przypadku asemblera) zapominasz jak to właściwie działało i dlaczego zrobiłeś coś tak a nie inaczej.

No i pytanie podstawowe: dlaczego zdecydowałeś się na pisanie w asemblerze? Rozumiem, że wyszedłeś z Bascoma i... lęk przed C czy świadomy wybór ? Niewielu ludzi próbuje pisania w ten sposób choćby ze względu na szybkość powstawania poprawnego kodu i dostępność bibliotek. U Ciebie to sport czy chęć wyciśnięcia z AVR ostatnich soków? Ale to wtedy wziąłbyś się raczej za cyfrowy syntezator audio, oscyloskop, grę TV a nie zegarek..

Link do komentarza
Share on other sites

marek1707, Bascom umożliwia wstawianie do swojego kodu wstawek w asemblerze. Sam tak mam w moich lampkach choinkowych gdzie sekwencje wyświetlania są w Bascomie a kod sterujący diodami WS2812B a raczej ich klonami jest napisany właśnie w asemblerze (nie jestem twórcą tej części kodu).

Link do komentarza
Share on other sites

Jeśli dobrze zrozumiałem Kolega napisał swój zegarek w całości w asemblerze. Mimo, że Bascom umożliwia wstawki (C także), to jednak nie jest to "naturalna droga rozwoju". Dlatego pytałem o przyczyny.

Link do komentarza
Share on other sites

marek1707, mam Bascoma już od tylu lat a pierwsze słyszę, że można dać wstawki w C, bo wstawki w asemblerze kompilator skompiluje. A zegarek to nie tylko liczenie dat, to również sterowanie wyświetlaczem i podejrzewam, że ta część jest w Bascomie.

Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.