Dokładność kodu AVR ATtiny2313A 64 s i 128 s

Pytanie

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

Odpowiedź Sztucznej Inteligencji


Bezpośrednia odpowiedź na pytanie

Ten kod jest dobrze policzonym programowym generatorem opóźnienia dla AVR i przy rzeczywistym taktowaniu 8 MHz daje prawie idealne:

  • stan 1: około 64 s
  • stan 2: około 64 s
  • okres: około 128 s

Ale jest jedna ważna uwaga:

  • nie jest idealnie symetryczny, bo po drugim del_64s masz jeszcze rjmp main, czyli dodatkowe 2 cykle.

Przy liczeniu między zmianami stanu na porcie dostajesz:

  • pierwszy stan: 64.000000000 s
  • drugi stan: 64.000000250 s
  • pełny okres: 128.000000250 s

Czyli błąd samego kodu jest praktycznie pomijalny: 250 ns na 128 s.

Najważniejsze punkty:

  • kod jest napisany pod ATtiny2313A, nie pod ATmega32
  • dokładność programu jest bardzo wysoka
  • rzeczywista dokładność zależy głównie od:
    • faktycznego zegara CPU,
    • fuse bitów, zwłaszcza CKDIV8,
    • rodzaju źródła taktowania: kwarc / rezonator / RC,
    • ewentualnych przerwań

Szczegółowa analiza problemu

Co ten kod robi

Program po resecie:

  1. ustawia stos,
  2. ustawia PORTD jako wyjście,
  3. ładuje stałe do rejestrów,
  4. w pętli głównej przełącza PORTD między:
    • 0b10101010 = 0xAA
    • 0b01010101 = 0x55
  5. po każdej zmianie wykonuje del_64s.

To daje naprzemienny wzór na porcie, czyli dla poszczególnych bitów masz przebieg prostokątny.


Najważniejsza korekta interpretacji kodu

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 = 1953

Analogicznie:

MOVW R30, R18

daje:

  • R31:R30 = R19:R18 = 0xFFFF = 65535

To właśnie dlatego procedura daje około 64 s, a nie milisekundy.


Analiza cykli instrukcyjnych

Dla AVR przyjmujemy:

  • LDI = 1 cykl
  • OUT = 1 cykl
  • NOP = 1 cykl
  • MOVW = 1 cykl
  • SBIW = 2 cykle
  • BRNE = 2 cykle, gdy skok wykonany; 1 cykl, gdy nie
  • RCALL = 3 cykle
  • RET = 4 cykle
  • RJMP = 2 cykle

Przy \( f_{CPU} = 8\,\text{MHz} \):

\[
T_{clk} = \frac{1}{8\,000\,000} = 125\,\text{ns}
\]


Pętla wewnętrzna Loop

Kod:

Loop:
    SBIW R30, 0x01
    BRNE Loop

Start:

\[
R31:R30 = 0xFFFF = 65535
\]

Liczba wykonań:

  • SBIW: 65535 razy
  • BRNE skacze: 65534 razy
  • BRNE bez skoku: 1 raz

Liczba cykli:

\[
N_{Loop} = 65534 \cdot (2+2) + (2+1)
\]

\[
N_{Loop} = 65534 \cdot 4 + 3 = 262139
\]


Pętla zewnętrzna Coarse

Kod:

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 = 1
  • pętla Loop = 262139
  • SBIW R24,1 = 2
  • BRNE Coarse = 2

Razem:

\[
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
\]


Pętla dostrajająca Fine

Kod:

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 = 1
  • ldi r29 = 1
  • pętla = 32755
  • ret = 4

\[
N_{Fine} = 1 + 1 + 32755 + 4 = 32761
\]


Całkowity czas procedury del_64s

Bez RCALL:

\[
N{del_64s} = N{Coarse} + N_{Fine}
\]

\[
N_{del_64s} = 511967232 + 32761 = 511999993
\]

Z wywołaniem RCALL:

\[
N_{call+del} = 3 + 511999993 = 511999996
\]


Dokładny czas stanów na porcie

Stan 0xAA do 0x55

Po ustawieniu 0xAA wykonuje się:

  • nop = 1
  • nop = 1
  • rcall del_64s = 511999996
  • ldi r16, 0x55 = 1
  • out portd, r16 = 1

Razem:

\[
N_{AA} = 1 + 1 + 511999996 + 1 + 1 = 512000000
\]

\[
T_{AA} = \frac{512000000}{8000000} = 64.000000000\,\text{s}
\]

Stan 0x55 do następnego 0xAA

Po ustawieniu 0x55 wykonuje się:

  • nop = 1
  • nop = 1
  • rcall del_64s = 511999996
  • rjmp main = 2
  • ldi r16, 0xAA = 1
  • out portd, r16 = 1

Razem:

\[
N_{55} = 1 + 1 + 511999996 + 2 + 1 + 1 = 512000002
\]

\[
T_{55} = \frac{512000002}{8000000} = 64.000000250\,\text{s}
\]

Pełny okres

\[
N_{PER} = 512000000 + 512000002 = 1024000002
\]

\[
T_{PER} = \frac{1024000002}{8000000} = 128.000000250\,\text{s}
\]


Wniosek z analizy

Kod jest:

  • bardzo dokładny cyklowo
  • nieznacznie niesymetryczny
  • asymetria wynosi:

\[
2 \text{ cykle} = 250\,\text{ns}
\]

To jest wartość praktycznie zerowa w kontekście 64 s.


Ocena dostarczonych przykładowych odpowiedzi

  • Jedna odpowiedź offline z wynikiem około 64.004499 s zawiera błąd rachunkowy.
  • Odpowiedź online z wynikiem około 18.5 ms jest jednoznacznie błędna, bo błędnie interpretuje MOVW i liczniki 16-bitowe.
  • Stwierdzenie, że ten kod ma „niską dokładność”, też jest mylące. Kod ma wysoką dokładność cyklową, natomiast układ może mieć niską dokładność absolutną, jeśli zegar nie jest stabilny.

To rozróżnienie jest kluczowe:

  • dokładność programu — bardzo dobra,
  • dokładność rzeczywista urządzenia — zależy od źródła taktowania.

Aktualne informacje i trendy

W praktyce współczesnego embedded odchodzi się od tak długich opóźnień realizowanych pętlą programową, ponieważ:

  • blokują CPU w 100%,
  • utrudniają rozbudowę programu,
  • źle współpracują z przerwaniami i energooszczędnością.

Obecnie preferuje się:

  • Timer1 w trybie CTC,
  • generowanie bazowego taktu, np. 1 ms, 10 ms, 1 s,
  • zliczanie sekund programowo,
  • tryby uśpienia między przerwaniami.

Dla długich okresów jest to rozwiązanie:

  • bardziej skalowalne,
  • bardziej energooszczędne,
  • prostsze w utrzymaniu.

Wspierające wyjaśnienia i detale

Co najbardziej wpływa na dokładność rzeczywistą

1. Fuse CKDIV8

Jeżeli CKDIV8 jest włączony, CPU pracuje z częstotliwością podzieloną przez 8.

Dla realnego \(1\,\text{MHz}\) zamiast \(8\,\text{MHz}\):

  • 64 s zrobi się około 512 s
  • cały okres około 1024 s

To najczęstsza przyczyna „złego czasu” w AVR.

2. Faktyczne źródło zegara

.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}}
\]

3. Kwarc vs RC

Jeżeli używasz:

  • zewnętrznego kwarcu 8 MHz — dokładność będzie dobra,
  • wewnętrznego RC — błąd może być zauważalny.

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.

4. Przerwania

Jeśli włączysz przerwania, każda obsługa ISR wydłuży opóźnienie. Wtedy obliczona dokładność cyklowa przestaje obowiązywać.


Aspekty etyczne i prawne

W typowych zastosowaniach amatorskich nie ma problemu, ale w systemach:

  • bezpieczeństwa,
  • sterowania procesami,
  • automatyki przemysłowej,
  • urządzeń objętych wymaganiami funkcjonalnymi

nie należy opierać odmierzania długiego czasu wyłącznie na pętli typu busy-wait.

Powody:

  • brak odporności na przerwania,
  • brak diagnostyki błędu zegara,
  • brak nadzoru czasowego,
  • słaba skalowalność przy rozbudowie systemu.

W systemach formalnie ocenianych stosuje się raczej:

  • sprzętowe timery,
  • watchdog,
  • kontrolę poprawności zegara,
  • architekturę zdarzeniową.

Praktyczne wskazówki

Jeśli chcesz sprawdzić ten kod w praktyce

  1. Upewnij się, że mikrokontroler to faktycznie ATtiny2313A.
  2. Sprawdź fuse bity:
    • źródło zegara,
    • CKDIV8.
  3. Zweryfikuj, czy rzeczywiście masz 8 MHz.
  4. Zmierz sygnał:
    • oscyloskopem,
    • analizatorem logicznym,
    • albo licznikem częstotliwości z długim oknem pomiarowym.

Jeśli chcesz poprawić symetrię

Obecnie drugi półokres jest dłuższy o rjmp main.

Masz trzy sensowne drogi:

  • zaakceptować różnicę 250 ns, bo jest praktycznie zerowa,
  • wyrównać obie połówki przez sztuczne dodanie 2 cykli w pierwszej połówce,
  • przepisać rozwiązanie na timer sprzętowy.

Najlepsza praktyka inżynierska

Dla długich opóźnień:

  • użyj timera sprzętowego,
  • generuj krótszy takt bazowy,
  • licz sekundy programowo.

To jest bardziej profesjonalne niż wielka pętla opóźniająca.


Ewentualne zastrzeżenia lub uwagi dodatkowe

  • 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.
  • Kod zużywa CPU w 100%.
  • fosc = 8 nie ma wpływu na assemblerowy czas wykonania; to nie jest automatyczna konfiguracja układu.
  • Jeśli kiedyś przejdziesz na m32def.inc, trzeba będzie inaczej podejść do inicjalizacji stosu i peryferiów.

Sugestie dalszych badań

Jeżeli chcesz pójść krok dalej, warto porównać trzy warianty:

  1. programowa pętla opóźniająca — jak tutaj,
  2. Timer1 + przerwanie CTC,
  3. zewnętrzny RTC lub zegar 32.768 kHz.

Dobrze też przeanalizować:

  • wpływ tolerancji kwarcu na błąd długookresowy,
  • wpływ temperatury,
  • wpływ fuse bitów,
  • wpływ przerwań i watchdog.

Jeśli chcesz, mogę Ci od razu przepisać ten program na:

  • wersję z Timer1,
  • wersję z dokładnie symetrycznym 64 s / 64 s,
  • albo policzyć go dla innej częstotliwości, np. 1 MHz, 4 MHz, 16 MHz.

Krótkie podsumowanie

Najkrócej:

  • kod jest poprawny
  • procedura del_64s jest bardzo precyzyjnie dobrana
  • przy 8 MHz daje praktycznie:
    • 64.000000000 s
    • 64.000000250 s
    • okres 128.000000250 s
  • różnica wynika z dodatkowego rjmp main
  • realny błąd w praktyce będzie zależał przede wszystkim od zegara i fuse bitów, a nie od samego kodu

Jeżeli chcesz, w następnym kroku mogę rozpisać ten kod linia po linii, albo przygotować wersję timerową znacznie lepszą inżyniersko.

Oceń odpowiedź:
Zastrzeżenie: Odpowiedzi udzielone przez sztuczną inteligencję (model językowy) mogą być niedokładne i wprowadzające w błąd. Elektroda nie ponosi odpowiedzialności za dokładność, rzetelność ani kompletność prezentowanych informacji. Wszystkie odpowiedzi powinny być zweryfikowane przez użytkownika.

Zadaj dodatkowe pytanie Sztucznej Inteligencji

Czekaj (2min)...