Co napiszesz o tym kodzie , jaka jest jego dokładność ,LAW 64 sekund ,HIGH 64 sekund ,okres 128 sekund , .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
nop
nop
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
Ten kod jest dobrze policzonym programowym generatorem opóźnienia dla AVR i przy rzeczywistym taktowaniu 8 MHz daje prawie idealne:
Ale jest jedna ważna uwaga:
del_64s masz jeszcze rjmp main, czyli dodatkowe 2 cykle.Przy liczeniu między zmianami stanu na porcie dostajesz:
Czyli błąd samego kodu jest praktycznie pomijalny: 250 ns na 128 s.
Najważniejsze punkty:
CKDIV8,Program po resecie:
PORTD jako wyjście,PORTD między:0b10101010 = 0xAA0b01010101 = 0x55del_64s.To daje naprzemienny wzór na porcie, czyli dla poszczególnych bitów masz przebieg prostokątny.
W dostarczonych odpowiedziach pojawił się typowy błąd: potraktowanie liczników jako 8-bitowych. To jest nieprawidłowe.
Instrukcja:
MOVW R24, R20
kopiuje parę rejestrów, czyli:
R25:R24 = R21:R20 = 0x07A1 = 1953Analogicznie:
MOVW R30, R18
daje:
R31:R30 = R19:R18 = 0xFFFF = 65535To właśnie dlatego procedura daje około 64 s, a nie milisekundy.
Dla AVR przyjmujemy:
LDI = 1 cyklOUT = 1 cyklNOP = 1 cyklMOVW = 1 cyklSBIW = 2 cykleBRNE = 2 cykle, gdy skok wykonany; 1 cykl, gdy nieRCALL = 3 cykleRET = 4 cykleRJMP = 2 cyklePrzy \( f_{CPU} = 8\,\text{MHz} \):
\[
T_{clk} = \frac{1}{8\,000\,000} = 125\,\text{ns}
\]
LoopKod:
Loop:
SBIW R30, 0x01
BRNE Loop
Start:
\[
R31:R30 = 0xFFFF = 65535
\]
Liczba wykonań:
SBIW: 65535 razyBRNE skacze: 65534 razyBRNE bez skoku: 1 razLiczba cykli:
\[
N_{Loop} = 65534 \cdot (2+2) + (2+1)
\]
\[
N_{Loop} = 65534 \cdot 4 + 3 = 262139
\]
CoarseKod:
del_64s:
MOVW R24, R20
Coarse:
MOVW R30, R18
Loop:
SBIW R30, 0x01
BRNE Loop
SBIW R24, 0x01
BRNE Coarse
Start:
\[
R25:R24 = 0x07A1 = 1953
\]
Jedna iteracja zewnętrzna, z wyjątkiem ostatniej:
MOVW R30,R18 = 1Loop = 262139SBIW R24,1 = 2BRNE Coarse = 2Razem:
\[
1 + 262139 + 2 + 2 = 262144
\]
Ostatnia iteracja ma o 1 cykl mniej na BRNE, ale pierwszy MOVW R24,R20 na wejściu to kompensuje. W efekcie całość wychodzi bardzo elegancko:
\[
N_{Coarse} = 1953 \cdot 262144 = 511967232
\]
FineKod:
ldi r28, 0xfd
ldi r29, 0x1f
Fine:
SBIW R28, 0x01
BRNE Fine
Start:
\[
R29:R28 = 0x1FFD = 8189
\]
Sama pętla:
\[
N_{FineLoop} = 8188 \cdot (2+2) + (2+1)
\]
\[
N_{FineLoop} = 8188 \cdot 4 + 3 = 32755
\]
Z inicjalizacją i powrotem:
ldi r28 = 1ldi r29 = 1ret = 4\[
N_{Fine} = 1 + 1 + 32755 + 4 = 32761
\]
del_64sBez RCALL:
\[
N{del_64s} = N{Coarse} + N_{Fine}
\]
\[
N_{del_64s} = 511967232 + 32761 = 511999993
\]
Z wywołaniem RCALL:
\[
N_{call+del} = 3 + 511999993 = 511999996
\]
0xAA do 0x55Po ustawieniu 0xAA wykonuje się:
nop = 1nop = 1rcall del_64s = 511999996ldi r16, 0x55 = 1out portd, r16 = 1Razem:
\[
N_{AA} = 1 + 1 + 511999996 + 1 + 1 = 512000000
\]
\[
T_{AA} = \frac{512000000}{8000000} = 64.000000000\,\text{s}
\]
0x55 do następnego 0xAAPo ustawieniu 0x55 wykonuje się:
nop = 1nop = 1rcall del_64s = 511999996rjmp main = 2ldi r16, 0xAA = 1out portd, r16 = 1Razem:
\[
N_{55} = 1 + 1 + 511999996 + 2 + 1 + 1 = 512000002
\]
\[
T_{55} = \frac{512000002}{8000000} = 64.000000250\,\text{s}
\]
\[
N_{PER} = 512000000 + 512000002 = 1024000002
\]
\[
T_{PER} = \frac{1024000002}{8000000} = 128.000000250\,\text{s}
\]
Kod jest:
\[
2 \text{ cykle} = 250\,\text{ns}
\]
To jest wartość praktycznie zerowa w kontekście 64 s.
MOVW i liczniki 16-bitowe.To rozróżnienie jest kluczowe:
W praktyce współczesnego embedded odchodzi się od tak długich opóźnień realizowanych pętlą programową, ponieważ:
Obecnie preferuje się:
Dla długich okresów jest to rozwiązanie:
CKDIV8Jeżeli CKDIV8 jest włączony, CPU pracuje z częstotliwością podzieloną przez 8.
Dla realnego \(1\,\text{MHz}\) zamiast \(8\,\text{MHz}\):
To najczęstsza przyczyna „złego czasu” w AVR.
.equ fosc = 8 w tym kodzie jest tylko komentarzem logicznym. To nie ustawia zegara mikrokontrolera.
Jeżeli rzeczywiste \(F_{CPU}\) jest inne, to czas skaluje się proporcjonalnie:
\[
T{real} = T{calc} \cdot \frac{8\,\text{MHz}}{F_{CPU,real}}
\]
Jeżeli używasz:
Przykład dla kwarcu 20 ppm:
\[
64\,\text{s} \cdot 20 \cdot 10^{-6} = 1.28\,\text{ms}
\]
Dla 50 ppm:
\[
64\,\text{s} \cdot 50 \cdot 10^{-6} = 3.2\,\text{ms}
\]
Czyli błąd oscylatora jest zwykle wiele tysięcy razy większy niż błąd samego kodu.
Jeśli włączysz przerwania, każda obsługa ISR wydłuży opóźnienie. Wtedy obliczona dokładność cyklowa przestaje obowiązywać.
W typowych zastosowaniach amatorskich nie ma problemu, ale w systemach:
nie należy opierać odmierzania długiego czasu wyłącznie na pętli typu busy-wait.
Powody:
W systemach formalnie ocenianych stosuje się raczej:
CKDIV8.Obecnie drugi półokres jest dłuższy o rjmp main.
Masz trzy sensowne drogi:
Dla długich opóźnień:
To jest bardziej profesjonalne niż wielka pętla opóźniająca.
PORTD = 0xFF na starcie powoduje krótki stan początkowy różny od pracy ustalonej.DDRD = 0xFF na ATtiny2313A jest bezpieczne, choć nie wszystkie bity portu mogą być fizycznie wyprowadzone.fosc = 8 nie ma wpływu na assemblerowy czas wykonania; to nie jest automatyczna konfiguracja układu.m32def.inc, trzeba będzie inaczej podejść do inicjalizacji stosu i peryferiów.Jeżeli chcesz pójść krok dalej, warto porównać trzy warianty:
Dobrze też przeanalizować:
Jeśli chcesz, mogę Ci od razu przepisać ten program na:
Najkrócej:
del_64s jest bardzo precyzyjnie dobranarjmp mainJeżeli chcesz, w następnym kroku mogę rozpisać ten kod linia po linii, albo przygotować wersję timerową znacznie lepszą inżyniersko.