Gdzie w tym kodzie dodać NOP, .nolist
.include "tn2313adef.inc"
;.include "m32def.inc"
.list
.cseg
.equ fosc = 8 ;czestotliwosc kwarcu w MHz
.org 0x0000
rjmp ResetProcessor
ResetProcessor:
; ldi r16, high(RAMEND) ; init stack pointer
; out SPH, r16
ldi r16, low (RAMEND) ; init stack pointer
out SPL, r16
ldi r16, 0xff
out ddrd, r16
out portd, r16
ldi r18, 0xff
ldi r19, 0xff
ldi r20, 0xa1
ldi r21, 0x07
main:
ldi r16, 0b1010_1010
out portd, r16
nop
nop
rcall del_64s
ldi r16, 0b0101_0101
out portd, r16
rcall del_64s
rjmp main
del_64s:
MOVW R24, R20
Coarse:
MOVW R30, R18 ;
Loop:
SBIW R30, 0x01 ;
BRNE Loop ;
SBIW R24, 0x01 ;
BRNE Coarse ;
ldi r28, 0xfd
ldi r29, 0x1f
Fine:
SBIW R28, 0x01 ;
BRNE Fine
ret
.exit
Najkrótsza odpowiedź brzmi: to zależy, jaki efekt chcesz uzyskać.
W Twoim kodzie NOP można dodać w trzech sensownych miejscach:
main — jeśli chcesz skorygować czas trwania tylko jednego stanu na PORTD,ret w del_64s — jeśli chcesz dodać bardzo małe opóźnienie do każdego wywołania procedury,Fine — jeśli chcesz dodać opóźnienie rzędu milisekund.Natomiast nie zalecam dodawania NOP do pętli Loop, bo tam pojedynczy NOP da bardzo duży przyrost czasu.
Najbardziej praktyczna odpowiedź dla Twojego kodu:
del_64s:
MOVW R24, R20
Coarse:
MOVW R30, R18
Loop:
SBIW R30, 0x01
BRNE Loop
SBIW R24, 0x01
BRNE Coarse
ldi r28, 0xfd
ldi r29, 0x1f
Fine:
SBIW R28, 0x01
BRNE Fine
nop ; najlepsze miejsce do drobnej korekty czasu
ret
Kluczowe punkty:
NOP = 1 cykl zegara.fosc = 8 MHz jeden cykl to:NOP w main i one najprawdopodobniej są tam celowo.Program przełącza PORTD naprzemiennie między:
0b1010_1010 = 0xAA0b0101_0101 = 0x55a między tymi zmianami wywołuje procedurę opóźniającą del_64s.
Fragment główny:
main:
ldi r16, 0b1010_1010
out portd, r16
nop
nop
rcall del_64s
ldi r16, 0b0101_0101
out portd, r16
rcall del_64s
rjmp main
NOP już tam sąTo nie wygląda na przypadek. Te dwa NOP bardzo prawdopodobnie wyrównują czas obu połówek przebiegu.
Porównanie ścieżek czasowych:
0xAAPo out portd, r16 wykonywane są:
nop = 1 cyklnop = 1 cyklrcall del_64s = 3 cykleRazem: 5 cykli przed wejściem do procedury.
0x55Po out portd, r16 wykonywane są:
rcall del_64s = 3 cyklerjmp main = 2 cykleRazem również: 5 cykli.
Wniosek:
NOP kompensują rjmp main,0xAA i 0x55 jest symetryczny,To ważne: jeśli usuniesz lub dodasz NOP tylko po jednej stronie, zaburzysz symetrię przebiegu.
NOP, zależnie od celuNajlepsze miejsce:
Fine:
SBIW R28, 0x01
BRNE Fine
nop
ret
Dlaczego to jest dobre miejsce:
NOP wykona się raz na każde wywołanie del_64s,Jeżeli dodasz 2 NOP, dostaniesz:
\[
2 \cdot 125\ \text{ns} = 250\ \text{ns}
\]
To jest najlepsze miejsce do „kosmetycznego” strojenia.
Wtedy dodajesz NOP w main, przy odpowiedniej gałęzi.
Przykład: wydłużenie stanu 0xAA:
main:
ldi r16, 0b1010_1010
out portd, r16
nop
nop
nop ; dodatkowy NOP
rcall del_64s
To spowoduje, że 0xAA będzie trwało o 1 cykl dłużej niż 0x55.
Jeżeli chcesz zachować symetrię, musisz dodać taki sam ekwiwalent czasowy po drugiej stronie. Na przykład:
main:
ldi r16, 0b1010_1010
out portd, r16
nop
nop
nop
rcall del_64s
ldi r16, 0b0101_0101
out portd, r16
nop
rcall del_64s
nop
rjmp main
ale to robi się mało eleganckie. Lepsza jest korekta w del_64s.
Możesz dodać NOP do pętli Fine:
Fine:
nop
SBIW R28, 0x01
BRNE Fine
Tu efekt już się mnoży przez liczbę iteracji.
Masz:
ldi r28, 0xfd
ldi r29, 0x1f
czyli:
\[
R29:R28 = 0x1FFD = 8189
\]
Zatem jeden NOP w Fine da:
\[
8189 \cdot 125\ \text{ns} \approx 1.024\ \text{ms}
\]
To już jest sensowne miejsce do korekty w skali milisekund.
NOP do LoopKod:
Loop:
SBIW R30, 0x01
BRNE Loop
Rejestry są ładowane z:
ldi r18, 0xff
ldi r19, 0xff
czyli:
\[
R19:R18 = 0xFFFF = 65535
\]
A pętla zewnętrzna ma:
ldi r20, 0xa1
ldi r21, 0x07
czyli:
\[
R21:R20 = 0x07A1 = 1953
\]
Liczba wykonań pętli Loop łącznie:
\[
65535 \cdot 1953 = 127\,993\,855
\]
Jeden NOP dodany do Loop da więc:
\[
127\,993\,855 \cdot 125\ \text{ns} \approx 15.999\ \text{s}
\]
To bardzo ważny wniosek:
NOP w Loop ≈ +16 sekundZ punktu widzenia praktyki inżynierskiej: to zwykle zły pomysł.
Jeśli pytasz ogólnie: „gdzie dodać NOP?”, to praktycznie są trzy poziomy strojenia:
Miejsce dodania NOP |
Efekt | Zalecenie |
|---|---|---|
przed ret w del_64s |
+125 ns / wywołanie | najlepsze do drobnej korekty |
w pętli Fine |
około +1.024 ms / NOP |
dobre do strojenia w ms |
w pętli Loop |
około +16 s / NOP |
zwykle nie dodawać |
Zatem:
ret,Fine,NOP w Loop.Jeżeli chcesz tylko bezpiecznie wydłużyć opóźnienie o bardzo małą wartość, zrób tak:
del_64s:
MOVW R24, R20
Coarse:
MOVW R30, R18
Loop:
SBIW R30, 0x01
BRNE Loop
SBIW R24, 0x01
BRNE Coarse
ldi r28, 0xfd
ldi r29, 0x1f
Fine:
SBIW R28, 0x01
BRNE Fine
nop
nop
ret
Jeżeli chcesz korektę rzędu około 1 ms:
Fine:
nop
SBIW R28, 0x01
BRNE Fine
W praktyce współczesnego projektowania systemów wbudowanych:
busy wait stosuje się głównie do:W Twoim przypadku nazwa del_64s sugeruje opóźnienie rzędu dziesiątek sekund. Taki czas realizowany pętlą programową:
Lepszy trend projektowy:
PORTD w ISR lub przez flagę w pętli głównej.NOPDla fosc = 8 MHz:
\[
T_{clk} = \frac{1}{8\ \text{MHz}} = 125\ \text{ns}
\]
Instrukcja NOP trwa 1 cykl, więc:
\[
t_{NOP} = 125\ \text{ns}
\]
NOP w pętli tak mocno działaTo jest klasyczny efekt mnożenia przez liczbę iteracji.
NOP poza pętlą: działa raz,NOP w Fine: działa 8189 razy,NOP w Loop: działa prawie 128 milionów razy.To można porównać do rezystora w układzie RC:
W tym konkretnym problemie aspekty prawne są praktycznie nieistotne, ale technicznie warto zaznaczyć:
W systemach przemysłowych i automotive nadmierne poleganie na NOP i opóźnieniach programowych może prowadzić do problemów z deterministyką.
NOP w main, jeśli chcesz zachować symetrię przebiegu.NOP przed ret.NOP do Fine.r18:r19r20:r21r28:r29Najlepiej sprawdzić wynik na:
NOP w odpowiednim poziomie pętli.NOP nie jest dobrym narzędziem do dużych opóźnień.fosc = 8 sama w sobie niczego nie przelicza automatycznie — to tylko symbol w asemblerze, chyba że użyjesz go w obliczeniach makr.SPH jest poprawnie wyłączony, bo ten układ ma tylko SPL.Jeżeli chcesz rozwinąć ten program poprawnie inżyniersko, warto przeanalizować:
busy wait a sterowaniem zdarzeniowym.Dobrym kolejnym krokiem byłoby przepisanie del_64s na timer sprzętowy.
Najlepsza odpowiedź na Twoje pytanie jest taka:
NOP w dobrym miejscu w main — najpewniej do wyrównania czasów obu stanów.NOP tuż przed ret w del_64s.NOP w pętli Fine.NOP do Loop, chyba że świadomie chcesz wydłużyć opóźnienie o około 16 sekund na każdy NOP.Jeśli chcesz, mogę Ci w kolejnym kroku dokładnie policzyć czas działania całej procedury del_64s i powiedzieć, ile rzeczywiście wynosi opóźnienie dla 8 MHz.