ATtiny13A zmiana polaryzacji co 64 s na PB0 i PB1

Pytanie

Jak wyglądał by kod na Attiny 13A ,zmiana polaryzacji LOW 64 sekund , HIGH 64 sekund , okres 128 sekund , dutycycle 50% , między jakimi 2 pinami najlepiej ,tak aby zmiana polaryzacji była jak najdokładniejsza , bez zbędnych dodatków pogarszających dokładność.

Odpowiedź Sztucznej Inteligencji


Bezpośrednia odpowiedź na pytanie

Tak — na ATtiny13A da się to zrobić prosto i bardzo poprawnie.
Najlepszy wybór pinów: PB0 i PB1

  • PB0 = fizyczny pin 5
  • PB1 = fizyczny pin 6
    To są zwykłe piny I/O, a dodatkowo nie wchodzą w konflikt z pinem RESET. PB5 lepiej nie używać, a PB3 warto zostawić wolny, jeśli kiedyś zechcesz podać zewnętrzny dokładny zegar na wejście CLKI. (ww1.microchip.com)

Jeśli zależy Ci na najdokładniejszej zmianie polaryzacji bez zbędnych dodatków, to najlepsza metoda na samym ATtiny13A to:

  • Timer0 w trybie CTC
  • odliczanie sprzętowe
  • zmiana obu wyjść jednym zapisem, najlepiej przez zapis do PINB, bo w AVR zapis „1” do bitu PINx przełącza odpowiadający bit PORTx. (ww1.microchip.com)

Najkrótszy sensowny kod:

#define F_CPU 1200000UL
#include <avr/io.h>
#include <avr/interrupt.h>
volatile uint16_t cnt = 0;
ISR(TIM0_COMPA_vect)
{
    if (++cnt >= 4800) {                 // 64 s
        cnt = 0;
        PINB = (1 << PB0) | (1 << PB1);  // jednoczesne przełączenie PB0 i PB1
    }
}
int main(void)
{
    DDRB  = (1 << PB0) | (1 << PB1);     // PB0, PB1 jako wyjścia
    PORTB = (1 << PB0);                  // start: PB0=1, PB1=0
    GTCCR = (1 << PSR10);                // reset preskalera Timer0
    TCNT0 = 0;
    TCCR0A = (1 << WGM01);               // CTC
    TCCR0B = (1 << CS01) | (1 << CS00);  // preskaler 64
    OCR0A  = 249;                        // 1.2 MHz / 64 / 250 = 75 Hz
    TIMSK0 = (1 << OCIE0A);              // przerwanie Compare Match A
    sei();
    while (1) {
    }
}

Bezpośrednia odpowiedź na pytanie Szczegółowa analiza problemu

To rozwiązanie jest poprawne z dwóch powodów:

1. Zmiana polaryzacji między dwoma pinami

Obciążenie podłączasz tak:

PB0 ----[ OBCIĄŻENIE ]---- PB1

Stany pracy:

  • stan A: PB0=1, PB1=0
  • stan B: PB0=0, PB1=1

Czyli polaryzacja na obciążeniu odwraca się co 64 s. Piny PB0 i PB1 są do tego najwygodniejsze i najbardziej „czyste” funkcjonalnie. (ww1.microchip.com)

2. Dokładność czasowa

ATtiny13A jest fabrycznie ustawiany na wewnętrzny RC 9.6 MHz z włączonym CKDIV8, więc domyślnie CPU pracuje z częstotliwością:

\[
F_{CPU} = 9.6\ \text{MHz} / 8 = 1.2\ \text{MHz}
\]

To jest ustawienie domyślne po wyjęciu układu z pudełka. (ww1.microchip.com)

Dobieramy Timer0 tak, aby miał dokładny podział całkowitoliczbowy:

\[
f{ISR} = \frac{F{CPU}}{N \cdot (1 + OCR0A)}
\]

Dla:

  • \(F_{CPU} = 1\,200\,000\ \text{Hz}\)
  • \(N = 64\)
  • \(OCR0A = 249\)

otrzymujesz:

\[
f_{ISR} = \frac{1\,200\,000}{64 \cdot 250} = 75\ \text{Hz}
\]

A więc:

\[
64\ \text{s} \cdot 75 = 4800
\]

Po 4800 przerwaniach mija dokładnie 64 s — oczywiście pod warunkiem, że sam zegar procesora ma dokładną częstotliwość. Tryb CTC zeruje licznik sprzętowo przy zgodności z OCR0A, więc matematyka czasu jest czysta i nie ma błędu narastającego od pętli programowej. (ww1.microchip.com)

3. Dlaczego zapis do PINB, a nie dwa osobne polecenia?

W AVR zapis logicznej „1” do bitu PINxn powoduje przełączenie odpowiadającego bitu PORTxn. Dzięki temu można zmienić PB0 i PB1 równocześnie jednym zapisem:

PINB = (1 << PB0) | (1 << PB1);

To jest lepsze niż:

PORTB |=  (1 << PB0);
PORTB &= ~(1 << PB1);

bo w drugim przypadku stany zmieniają się sekwencyjnie, a nie jednym aktem aktualizacji portu. Jeśli zależy Ci na możliwie symetrycznej zmianie polaryzacji — zapis do PINB jest bardzo dobrym wyborem. (ww1.microchip.com)

4. Dlaczego nie PWM sprzętowy dla 64 s?

Jedna z przykładowych odpowiedzi online z Phase Correct PWM dla 64 s jest po prostu niepoprawna. Timer0 w ATtiny13A jest 8-bitowy, a sprzętowe PWM/toggle na tym timerze nie pozwala uzyskać bezpośrednio półokresu 64 s bez dodatkowego programowego liczenia. Z noty wynika, że Timer0 ma tylko preskalery do 1024 i licznik 8-bitowy, więc samym PWM nie wygenerujesz bezpośrednio 128 s okresu na wyjściu. (ww1.microchip.com)

5. Dlaczego nie Watchdog?

Watchdog ma osobny oscylator i maksymalny timeout 8.0 s, więc do 64 s trzeba by liczyć osiem wybudzeń. To działa, ale dla precyzyjnego odmierzania czasu nie traktowałbym tego jako lepszego rozwiązania niż Timer0, bo w datasheet dla tych timeoutów masz podane wartości typowe, a nie precyzyjny wzorzec czasu. To jest więc rozwiązanie bardziej „proste energetycznie” niż „najdokładniejsze”. (ww1.microchip.com)

Aktualne informacje i trendy

W praktyce największe ograniczenie nie leży tu w kodzie, tylko w źródle zegara.
ATtiny13A z wewnętrznym oscylatorem RC ma według datasheet:

  • fabryczną dokładność ±10% dla 4.8/9.6 MHz przy określonym punkcie kalibracji,
  • po kalibracji użytkownika można zejść do około ±2% przy stałym napięciu i temperaturze. (ww1.microchip.com)

To oznacza, że:

  • sam algorytm liczy idealnie
  • ale rzeczywiste 64 s będą tak dokładne, jak dokładny jest zegar mikrokontrolera. (ww1.microchip.com)

Jeżeli kiedyś będziesz chciał uzyskać znacznie lepszą dokładność długookresową, ATtiny13A może pracować z zewnętrznym zegarem na pinie PB3/CLKI. To już jednak jest dodatkowy element zewnętrzny. (ww1.microchip.com)

Wspierające wyjaśnienia i detale

Jeżeli zostawiasz domyślne fuse, ten kod jest wygodny, bo nie wymaga zmian konfiguracyjnych. Układ fabrycznie startuje z 9.6 MHz i preskalerem /8, więc F_CPU = 1200000UL jest poprawne dla nowego układu. (ww1.microchip.com)

Gdybyś wcześniej zmienił fuse i procesor pracował pełnym 9.6 MHz, trzeba przeliczyć timer. Dla 9.6 MHz też da się uzyskać równe 75 Hz, np. przez preskaler 1024 i OCR0A = 124, ale to już inny wariant konfiguracji. Wynika to bezpośrednio z tabeli preskalerów Timer0 i wzoru dla CTC. (ww1.microchip.com)

Aspekty etyczne i prawne

Od strony bezpieczeństwa najważniejsze jest nieprzeciążanie pinów I/O. W nocie katalogowej 40 mA na pin i 200 mA łącznie przez VCC/GND to wartości z działu Absolute Maximum Ratings, czyli granice uszkodzeniowe, a nie zalecany punkt pracy ciągłej. Nie powinno się projektować układu „na styk” pod te liczby. (ww1.microchip.com)

Jeśli obciążenie to:

  • cewka,
  • przekaźnik bistabilny,
  • silnik,
  • element o większym prądzie,

to nie należy podłączać go bezpośrednio między PB0 i PB1. Wtedy trzeba użyć zewnętrznego stopnia mocy. (ww1.microchip.com)

Praktyczne wskazówki

Najlepsza praktyka dla Twojego przypadku

  • użyj PB0 i PB1
  • nie używaj delay()
  • nie przełączaj pinów osobno
  • użyj Timer0 CTC
  • przełączaj oba bity jednym zapisem do PINB (ww1.microchip.com)

Jeśli chcesz jeszcze lepiej

  1. Skalibruj OSCCAL dla swojego egzemplarza i swojego napięcia/temperatury pracy.
  2. Zasilaj układ stabilnym napięciem.
  3. Jeśli potrzebna jest naprawdę dobra dokładność długoterminowa, przejdź na zewnętrzny generator zegara na PB3/CLKI. (ww1.microchip.com)

Jak sprawdzić poprawność

  • podepnij oscyloskop między PB0 i PB1,
  • sprawdź, czy stan odwraca się co 64 s,
  • sprawdź, czy poziomy są komplementarne,
  • porównaj czas z zewnętrznym wzorcem.
    Jeżeli błąd czasu jest stały, to winny będzie głównie oscylator, nie logika timera. (ww1.microchip.com)

Ewentualne zastrzeżenia lub uwagi dodatkowe

Najważniejsze zastrzeżenie:
bez zewnętrznego wzorca czasu nie uzyskasz „laboratoryjnie dokładnych” 64.000 s, nawet jeśli kod jest idealny. Ograniczeniem jest oscylator RC, nie Timer0. (ww1.microchip.com)

Drugie zastrzeżenie:
odpowiedź z przykładowym PWM 64 s na Timer0 należy odrzucić jako błędną koncepcyjnie dla ATtiny13A. To nie wynika z opinii, tylko z parametrów 8-bitowego Timer0 i dostępnych podziałów zegara. (ww1.microchip.com)

Sugestie dalszych badań

Jeżeli chcesz ten układ dopracować dalej, warto sprawdzić:

  • kalibrację OSCCAL pod konkretny egzemplarz,
  • wpływ temperatury i napięcia na błąd 64 s,
  • wariant z zewnętrznym zegarem na PB3/CLKI,
  • ewentualną migrację na układ z 16-bitowym timerem lub z RTC, jeśli czas ma być naprawdę precyzyjny. (ww1.microchip.com)

Krótkie podsumowanie

Najlepsza odpowiedź na Twoje pytanie jest taka:

  • piny: PB0 i PB1
  • sposób: obciążenie między tymi dwoma pinami
  • czas: Timer0 w CTC
  • przełączanie: jednym zapisem do PINB
  • kod: ten podany wyżej jest właściwym, prostym rozwiązaniem
  • realne ograniczenie dokładności: wewnętrzny oscylator RC, nie sam kod. (ww1.microchip.com)

Jeśli chcesz, mogę od razu podać jeszcze:

  1. wersję w czystym asemblerze AVR,
  2. wersję pod avr-gcc + avrdude,
  3. dokładne fuse bity dla tego wariantu.

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.

Pytanie

wersję w czystym asemblerze AVR, wersję pod avr-gcc + avrdude, dokładne fuse bity dla tego wariantu.

Odpowiedź Sztucznej Inteligencji


Bezpośrednia odpowiedź na pytanie

Dla wariantu ATtiny13A, w którym PB0 i PB1 zmieniają polaryzację co 64 s, poprawna konfiguracja jest następująca:

  • wersja w czystym asemblerze AVR – poniżej,
  • wersja pod avr-gcc + avrdude – poniżej,
  • dokładne fuse bity:
    • LFUSE = 0x6A
    • HFUSE = 0xFF
    • EFUSE: brak w ATtiny13A.
      To są ustawienia zgodne z fabrycznym wariantem: wewnętrzny RC 9.6 MHz, CKDIV8 zaprogramowany, więc startowo dostajesz F_CPU = 1.2 MHz. Odpowiedź z LFUSE=0x62 jest dla tego układu po prostu błędna. W ATtiny13A mapa fuse’ów jest inna niż w wielu większych AVR: SPIEN siedzi w low fuse, nie ma tu klasycznego CKOUT, a stan fabryczny to CKSEL=10, SUT=10, CKDIV8=0. (ww1.microchip.com)

Dla tego taktowania ustawienie Timer0 w CTC, preskaler 64, OCR0A = 249 daje:
[
f_{ISR}=\frac{1200000}{64\cdot(249+1)}=75\ \text{Hz}
]
a więc 4800 przerwań = 64.000 s nominalnie. Matematycznie podział jest idealny; praktyczny błąd czasu wynika już głównie z tolerancji wewnętrznego oscylatora RC. (ww1.microchip.com)


Bezpośrednia odpowiedź na pytanie Szczegółowa analiza problemu

Zakładam dokładnie ten wariant funkcjonalny:

  • mikrokontroler: ATtiny13A,
  • obciążenie podłączone między PB0 i PB1,
  • stan A: PB0=1, PB1=0,
  • stan B: PB0=0, PB1=1,
  • zmiana stanu co 64 s,
  • taktowanie bez zmiany fuse’ów fabrycznych, czyli 1.2 MHz efektywnie. (ww1.microchip.com)

Bardzo dobrym trikiem sprzętowo-programowym jest tu przełączanie obu linii przez zapis do PINB. W ATtiny13A zapis logicznej jedynki do bitu rejestru PINxn powoduje przełączenie odpowiadającego bitu PORTxn, więc jednym zapisem możesz równocześnie odwrócić PB0 i PB1 bez chwilowego stanu pośredniego. (ww1.microchip.com)

Najważniejsza korekta względem błędnych odpowiedzi

Najwięcej zamieszania było przy fuse bitach. Dla ATtiny13A poprawne jest:

Byte Wartość Bity Znaczenie
LFUSE 0x6A 0110 1010 SPIEN=0, EESAVE=1, WDTON=1, CKDIV8=0, SUT=10, CKSEL=10
HFUSE 0xFF 1111 1111 SELFPRGEN=1, DWEN=1, BODLEVEL=11, RSTDISBL=1
EFUSE brak w tym układzie

Czyli:

  • 0x6A jest poprawne,
  • 0x62 jest niepoprawne dla tego wariantu,
  • odpowiedzi opisujące CKOUT albo przerzucające SPIEN do HFUSE mieszają fuse mapę z innych AVR-ów, nie z ATtiny13A. (ww1.microchip.com)

Wersja w czystym asemblerze AVR

Poniższy kod jest przygotowany pod składnię AVRASM2 / avra. Jeżeli Twój pakiet include używa nazwy tn13Adef.inc zamiast tn13def.inc, zmień tylko linię .include.

; ============================================================
; ATtiny13A
; Zmiana polaryzacji PB0 <-> PB1 co 64 s
; Timer0 CTC, F_CPU = 1.2 MHz (9.6 MHz / 8)
; LFUSE = 0x6A, HFUSE = 0xFF
; ============================================================
.nolist
.include "tn13def.inc"
.list
.equ OCR0A_VAL = 249
.equ CNT_TARGET = 4800
.dseg
cnt_lo: .byte 1
cnt_hi: .byte 1
.cseg
.org 0x0000
    rjmp reset           ; 0x0000 RESET
    reti                 ; 0x0001 INT0
    reti                 ; 0x0002 PCINT0
    reti                 ; 0x0003 TIM0_OVF
    reti                 ; 0x0004 EE_RDY
    reti                 ; 0x0005 ANA_COMP
    rjmp tim0_compa_isr  ; 0x0006 TIM0_COMPA
    reti                 ; 0x0007 TIM0_COMPB
    reti                 ; 0x0008 WDT
    reti                 ; 0x0009 ADC
tim0_compa_isr:
    push r16
    in   r16, SREG
    push r16
    push r24
    push r25
    ; cnt++
    lds  r24, cnt_lo
    lds  r25, cnt_hi
    adiw r24, 1
    sts  cnt_lo, r24
    sts  cnt_hi, r25
    ; if (cnt == 4800)
    cpi  r24, low(CNT_TARGET)
    ldi  r16, high(CNT_TARGET)
    cpc  r25, r16
    brne isr_done
    ; cnt = 0
    clr  r24
    clr  r25
    sts  cnt_lo, r24
    sts  cnt_hi, r25
    ; jednoczesne przełączenie PB0 i PB1
    ldi  r16, (1<<PB0) | (1<<PB1)
    out  PINB, r16
isr_done:
    pop  r25
    pop  r24
    pop  r16
    out  SREG, r16
    pop  r16
    reti
reset:
    ; stos
    ldi  r16, RAMEND
    out  SPL, r16
    ; licznik = 0
    clr  r24
    clr  r25
    sts  cnt_lo, r24
    sts  cnt_hi, r25
    ; PB0, PB1 jako wyjścia
    ldi  r16, (1<<PB0) | (1<<PB1)
    out  DDRB, r16
    ; stan początkowy: PB0=1, PB1=0
    ldi  r16, (1<<PB0)
    out  PORTB, r16
    ; reset preskalera Timer0
    ldi  r16, (1<<PSR10)
    out  GTCCR, r16
    clr  r16
    out  TCNT0, r16
    ; Timer0 CTC
    ldi  r16, (1<<WGM01)
    out  TCCR0A, r16
    ; OCR0A = 249
    ldi  r16, OCR0A_VAL
    out  OCR0A, r16
    ; włącz przerwanie COMPA
    ldi  r16, (1<<OCIE0A)
    out  TIMSK0, r16
    ; preskaler 64
    ldi  r16, (1<<CS01) | (1<<CS00)
    out  TCCR0B, r16
    ; sleep idle
    ldi  r16, (1<<SE)
    out  MCUCR, r16
    sei
main_loop:
    sleep
    rjmp main_loop

Ten układ wektorów odpowiada tabeli przerwań ATtiny13A, a użycie PINB do przełączania wyjść jest zgodne z dokumentacją portu I/O. (ww1.microchip.com)

Przykładowa kompilacja przez avra:

avra polaryzacja.asm

Po tym zwykle dostajesz plik .hex, który można programować przez avrdude.

Wersja pod avr-gcc + avrdude

Poniżej wersja w C pod klasyczny toolchain AVR-GCC:

#define F_CPU 1200000UL
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
volatile uint16_t tick75 = 0;
ISR(TIM0_COMPA_vect)
{
    if (++tick75 >= 4800) {
        tick75 = 0;
        PINB = _BV(PB0) | _BV(PB1);   // jednoczesne przełączenie PB0 i PB1
    }
}
int main(void)
{
    DDRB  = _BV(DDB0) | _BV(DDB1);   // PB0, PB1 jako wyjścia
    PORTB = _BV(PB0);                // start: PB0=1, PB1=0
    GTCCR  = _BV(PSR10);             // reset preskalera Timer0
    TCNT0  = 0;
    TCCR0A = _BV(WGM01);             // CTC
    OCR0A  = 249;
    TIMSK0 = _BV(OCIE0A);            // przerwanie COMPA
    TCCR0B = _BV(CS01) | _BV(CS00);  // preskaler 64
    set_sleep_mode(SLEEP_MODE_IDLE);
    sleep_enable();
    sei();
    for (;;) {
        sleep_mode();
    }
}

Kompilacja:

avr-gcc -std=c11 -Wall -Wextra -Os -mmcu=attiny13a -DF_CPU=1200000UL -o polaryzacja.elf polaryzacja.c
avr-objcopy -O ihex -R .eeprom polaryzacja.elf polaryzacja.hex

Programowanie FLASH:

avrdude -c usbasp -p attiny13a -U flash:w:polaryzacja.hex:i

Jeśli Twoja wersja avrdude nie przyjmuje attiny13a, użyj zwyczajowego aliasu:

avrdude -c usbasp -p t13 -U flash:w:polaryzacja.hex:i

Składnia -U memtype:op:filename:format jest standardowa w dokumentacji avrdude, a nowsze wersje rozpoznają zarówno identyfikatory części, jak i oficjalne nazwy z bazy urządzeń. (avrdudes.github.io)

Dokładne fuse bity dla tego wariantu

Odczyt:

avrdude -c usbasp -p attiny13a -U lfuse:r:-:h -U hfuse:r:-:h

Zapis:

avrdude -c usbasp -p attiny13a -U lfuse:w:0x6A:m -U hfuse:w:0xFF:m

W wersji z aliasem:

avrdude -c usbasp -p t13 -U lfuse:w:0x6A:m -U hfuse:w:0xFF:m

Jeszcze raz: dla tego konkretnego wariantu poprawne jest LFUSE=0x6A, nie 0x62. Dokumentacja Microchip wprost podaje, że ATtiny13A ma tylko dwa bajty fuse, a ustawienie fabryczne odpowiada CKSEL=10, SUT=10, CKDIV8 zaprogramowany. (ww1.microchip.com)


Aktualne informacje i trendy

Aktualna dokumentacja Microchip dla ATtiny13A nadal potwierdza klasyczny model fuse’ów tego układu: 2 fuse bytes, wewnętrzny oscylator 4.8/9.6 MHz, start fabryczny z 9.6 MHz + CKDIV8, czyli efektywnie 1.2 MHz. To nie jest obszar, w którym zaszły ostatnio zmiany funkcjonalne w samym układzie. (ww1.microchip.com)

Z praktycznego punktu widzenia istotniejsze są zmiany w narzędziach: współczesne wydania avrdude nadal obsługują rodzinę ATtiny13/13A, a instrukcja pokazuje aktualną składnię -U do odczytu i zapisu pamięci oraz fuse’ów. (avrdudes.github.io)


Wspierające wyjaśnienia i detale

Najważniejszy detal techniczny: 64.000 s jest “idealne” tylko od strony arytmetyki timera. Sam timer dzieli częstotliwość bez błędu całkowitoliczbowego, ale wewnętrzny RC ma ograniczoną dokładność. Microchip podaje dla kalibracji fabrycznej ±10% przy 3 V i 25°C, a po ręcznej kalibracji można zejść do około ±2% przy stałym napięciu i temperaturze. Jeżeli więc potrzebujesz rzeczywistego czasu “zegarkowego”, sam Timer0 nie wystarczy — trzeba jeszcze kalibrować źródło zegara albo użyć zewnętrznego odniesienia. (ww1.microchip.com)

Drugi detal: zapis do PINB jest tu lepszy niż ręczne ustawianie PORTB na dwa osobne stany, bo zapewnia przełączenie obu linii jednym zapisem. To zmniejsza ryzyko chwilowego zwarcia lub chwilowej niesymetrii polaryzacji. (ww1.microchip.com)


Aspekty etyczne i prawne

Tu nie ma szczególnych aspektów prawnych, ale są aspekty bezpieczeństwa technicznego. Nie należy bezpośrednio obciążać PB0/PB1 dużym prądem ani sterować nimi bezpośrednio elementów indukcyjnych bez zabezpieczeń. W nocie katalogowej jest zaznaczone, że suma prądów wyjściowych portów nie powinna przekraczać 60 mA. (ww1.microchip.com)

Jeżeli kiedyś rozważysz użycie pinu RESET jako zwykłego I/O, pamiętaj, że zaprogramowanie RSTDISBL wymaga później programowania wysokonapięciowego do odzyskania normalnego dostępu programującego. W tym wariancie celowo tego nie robimy. (ww1.microchip.com)


Praktyczne wskazówki

  • Zostaw fuse’y fabryczne albo ustaw dokładnie:
    • lfuse = 0x6A
    • hfuse = 0xFF
  • Najpierw odczytaj fuse’y, dopiero potem je zapisuj.
  • Daj przy zasilaniu 100 nF możliwie blisko VCC-GND.
  • Jeśli obciążenie między PB0 i PB1 bierze więcej niż kilka mA, zastosuj stopień pośredni, nie obciążaj bezpośrednio portów.
  • Jeśli wymagasz lepszej dokładności czasu niż daje RC, rozważ:
    • kalibrację OSCCAL,
    • zewnętrzne źródło zegara,
    • albo synchronizację okresową z zewnętrznym wzorcem. (ww1.microchip.com)

Ewentualne zastrzeżenia lub uwagi dodatkowe

Jedyna istotna niepewność wynika z tego, że w Twoim pytaniu widzę tylko fragment kontekstu. Odpowiedź zbudowałem dla wariantu wynikającego z przykładów: ATtiny13A, PB0/PB1, zmiana polaryzacji co 64 s. Jeżeli wcześniej zakładałeś inny pinout, inny zegar lub inny sposób odliczania, to trzeba przeliczyć OCR0A, preskaler i ewentualnie fuse’y.

Druga uwaga: odpowiedzi przykładowe były niespójne. W szczególności wersja z LFUSE=0x62 myli fuse mapę ATtiny13A z innymi AVR. W tym punkcie poprawka jest jednoznaczna: właściwa wartość to 0x6A. (ww1.microchip.com)


Sugestie dalszych badań

Jeżeli chcesz ten projekt dopracować inżyniersko, kolejne sensowne kroki to:

  • wersja z kalibracją OSCCAL pod konkretnym VCC i temperaturą,
  • wersja ultra-low-power z głębszym sleep i innym źródłem odliczania,
  • wersja z buforem tranzystorowym lub małym mostkiem H dla większego obciążenia,
  • pomiar rzeczywistego okresu na oscyloskopie i korekcja software’owa. (ww1.microchip.com)

Krótkie podsumowanie

Najkrócej:

  • Tak, da się to zrobić prosto na ATtiny13A.
  • Poprawne fuse bity dla tego wariantu to: LFUSE=0x6A, HFUSE=0xFF.
  • 0x62 jest błędne dla ATtiny13A.
  • Kod w asemblerze i w C podałem powyżej.
  • Timer0: CTC, preskaler 64, OCR0A=249, 4800 przerwań = 64 s nominalnie. (ww1.microchip.com)

Jeżeli chcesz, w następnym kroku mogę od razu przygotować jeszcze:

  1. gotowy Makefile,
  2. wersję pod avr-as/GNU assembler .S,
  3. wariant z kalibracją OSCCAL,
  4. schemat podłączenia z tranzystorami dla większego obciążenia.

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.

Pytanie

Proszę przygotować ,gotowy Makefile, wersję pod avr-as/GNU assembler .S, wariant z kalibracją OSCCAL, schemat podłączenia z tranzystorami dla większego obciążenia. dla tego kodu , #define F_CPU 1200000UL #include &lt;stdint.h&gt; #include &lt;avr/io.h&gt; #include &lt;avr/interrupt.h&gt; #include &lt;avr/sleep.h&gt; volatile uint16_t tick75 = 0; ISR(TIM0_COMPA_vect) { if (++tick75 &gt;= 4800) { tick75 = 0; PINB = _BV(PB0) | _BV(PB1); // jednoczesne przełączenie PB0 i PB1 } } int main(void) { DDRB = _BV(DDB0) | _BV(DDB1); // PB0, PB1 jako wyjścia PORTB = _BV(PB0); // start: PB0=1, PB1=0 GTCCR = _BV(PSR10); // reset preskalera Timer0 TCNT0 = 0; TCCR0A = _BV(WGM01); // CTC OCR0A = 249; TIMSK0 = _BV(OCIE0A); // przerwanie COMPA TCCR0B = _BV(CS01) | _BV(CS00); // preskaler 64 set_sleep_mode(SLEEP_MODE_IDLE); sleep_enable(); sei(); for (;;) { sleep_mode(); } }

Odpowiedź Sztucznej Inteligencji


Bezpośrednia odpowiedź na pytanie

Poniżej przygotowałem komplet dla ATtiny13A:

  • gotowy Makefile,
  • działającą wersję GNU assembler .S,
  • wariant z kalibracją OSCCAL,
  • schemat podłączenia tranzystorowego dla większego obciążenia.

Najpierw istotna korekta do przykładowych odpowiedzi: dla tego projektu poprawnym MCU jest ATtiny13A, nie atmega328p; na ATtiny13A nie ma SPH ani rejestru SMPR; przerwanie od compare A Timer0 to TIM0_COMPA / TIM0_COMPA_vect, a w GCC plik .S powinien być kompilowany przez avr-gcc, bo rozszerzenie .S oznacza assembler z preprocesorem. Dodatkowo układ z samymi dwoma tranzystorami low-side nie odwraca polaryzacji obciążenia — do tego potrzebny jest mostek H. (ww1.microchip.com)

Dla ATtiny13A ustawienie F_CPU = 1200000UL jest zgodne z ustawieniami fabrycznymi: układ startuje z wewnętrznego RC 9,6 MHz i zaprogramowanym CKDIV8, więc systemowo daje 1,2 MHz. Po resecie sprzęt automatycznie ładuje fabryczną kalibrację oscylatora do OSCCAL. Tryb Idle pozostawia Timer0 i system przerwań aktywne, a zapis logicznej jedynki do bitu PINxn przełącza odpowiadający bit PORTxn, więc zapis do PINB jest poprawnym sposobem na jednoczesne przełączenie PB0 i PB1. (ww1.microchip.com)


Bezpośrednia odpowiedź na pytanie Szczegółowa analiza problemu

1. Założenia projektu

Pański kod realizuje:

  • Timer0 w trybie CTC,
  • przerwanie co:
    [
    f_{ISR} = \frac{1200000}{64 \cdot (249+1)} = 75\ \text{Hz}
    ]
  • przełączenie PB0 i PB1 po 4800 przerwaniach:
    [
    \frac{4800}{75} = 64\ \text{s}
    ]

Zatem:

  • zmiana stanu następuje co 64 s,
  • pełny okres wynosi 128 s,
  • wypełnienie jest 50%.

To arytmetycznie jest dobrane prawidłowo. Problem praktyczny nie leży w timerze, tylko w dokładności RC oscylatora, stąd sens wariantu z OSCCAL. OSCCAL stroi wewnętrzny oscylator, ale dokumentacja zaznacza, że kalibracja dotyczy bazowo oscylatora 9,6/4,8 MHz, a zmiany wartości należy wykonywać ostrożnie i małymi krokami. Microchip nadal wskazuje notę AVR053 jako właściwe odniesienie do kalibracji RC. (ww1.microchip.com)

2. Gotowy Makefile

Poniższy Makefile obsługuje zarówno plik C, jak i plik .S. Domyślnie buduje assembler polaryzacja.S, ale można podać inny plik przez SRC=....

# ============================================================
# ATtiny13A / AVR-GCC / GNU assembler (.S)
# Domyślnie buduje polaryzacja.S
# Przykłady:
#   make
#   make SRC=polaryzacja_osccal.c TARGET=polaryzacja_osccal
#   make flash
#   make fuse
# ============================================================
MCU        ?= attiny13a
F_CPU      ?= 1200000UL
TARGET     ?= polaryzacja
SRC        ?= polaryzacja.S
PROGRAMMER ?= usbasp
PORT       ?= usb
LFUSE      ?= 0x6A
HFUSE      ?= 0xFF
CC         := avr-gcc
OBJCOPY    := avr-objcopy
OBJDUMP    := avr-objdump
SIZE       := avr-size
AVRDUDE    := avrdude
OBJ        := $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SRC)))
CFLAGS     := -mmcu=$(MCU) -DF_CPU=$(F_CPU)
CFLAGS     += -Os -std=gnu11 -Wall -Wextra
CFLAGS     += -ffunction-sections -fdata-sections
ASFLAGS    := -mmcu=$(MCU) -x assembler-with-cpp
ASFLAGS    += -Wa,-adhlns=$(TARGET).lst
LDFLAGS    := -mmcu=$(MCU) -Wl,--gc-sections
.PHONY: all flash fuse verify readfuses size clean
all: $(TARGET).hex $(TARGET).lss size
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@
%.o: %.S
    $(CC) $(ASFLAGS) -c $< -o $@
$(TARGET).elf: $(OBJ)
    $(CC) $(LDFLAGS) $^ -o $@
$(TARGET).hex: $(TARGET).elf
    $(OBJCOPY) -O ihex -R .eeprom $< $@
$(TARGET).lss: $(TARGET).elf
    $(OBJDUMP) -d -S $< > $@
size: $(TARGET).elf
    $(SIZE) -C --mcu=$(MCU) $<
flash: $(TARGET).hex
    $(AVRDUDE) -c $(PROGRAMMER) -P $(PORT) -p $(MCU) \
        -U flash:w:$<:i
verify: $(TARGET).hex
    $(AVRDUDE) -c $(PROGRAMMER) -P $(PORT) -p $(MCU) \
        -U flash:v:$<:i
fuse:
    $(AVRDUDE) -c $(PROGRAMMER) -P $(PORT) -p $(MCU) \
        -U lfuse:w:$(LFUSE):m \
        -U hfuse:w:$(HFUSE):m
readfuses:
    $(AVRDUDE) -c $(PROGRAMMER) -P $(PORT) -p $(MCU) \
        -U lfuse:r:-:h \
        -U hfuse:r:-:h
clean:
    rm -f *.o *.elf *.hex *.lss *.lst

Ten Makefile jest celowo zrobiony pod avr-gcc także dla .S, ponieważ .S ma być wcześniej przepuszczone przez preprocesor. To jest poprawna metoda dla kodu używającego #include <avr/io.h>. (gcc.gnu.org)

3. Wersja GNU assembler .S

Plik: polaryzacja.S

/* ============================================================
 * ATtiny13A
 * PB0/PB1 przełączane jednocześnie co 64 s
 * F_CPU = 1.2 MHz (9.6 MHz / 8)
 * Timer0 CTC, preskaler 64, OCR0A = 249
 * ISR = 75 Hz, 4800 przerwań = 64 s
 * ============================================================ */
#define __SFR_OFFSET 0
#include <avr/io.h>
.equ OCR0A_VALUE, 249
.equ TICK_TARGET, 4800
.section .bss
    .global tick75
tick75:
    .space 2
.section .text
    .global __vector_6
    .type __vector_6, @function
/* TIM0_COMPA = vector 0x0006 na ATtiny13A */
__vector_6:
    push r16
    in   r16, SREG
    push r16
    push r24
    push r25
    /* tick75++ */
    lds  r24, tick75
    lds  r25, tick75+1
    adiw r24, 1
    /* if (tick75 >= 4800) */
    cpi  r24, lo8(TICK_TARGET)
    ldi  r16, hi8(TICK_TARGET)
    cpc  r25, r16
    brlo 1f
    /* tick75 = 0 */
    clr  r24
    clr  r25
    sts  tick75, r24
    sts  tick75+1, r25
    /* jednoczesne przełączenie PB0 i PB1 */
    ldi  r16, (1<<PB0) | (1<<PB1)
    out  PINB, r16
    rjmp 2f
1:
    sts  tick75, r24
    sts  tick75+1, r25
2:
    pop  r25
    pop  r24
    pop  r16
    out  SREG, r16
    pop  r16
    reti
    .global main
    .type main, @function
main:
    /* PB0, PB1 jako wyjścia */
    ldi  r16, (1<<DDB0) | (1<<DDB1)
    out  DDRB, r16
    /* stan początkowy: PB0=1, PB1=0 */
    ldi  r16, (1<<PB0)
    out  PORTB, r16
    /* tick75 = 0 */
    clr  r16
    sts  tick75, r16
    sts  tick75+1, r16
    /* reset preskalera Timer0 */
    ldi  r16, (1<<PSR10)
    out  GTCCR, r16
    /* TCNT0 = 0 */
    clr  r16
    out  TCNT0, r16
    /* CTC */
    ldi  r16, (1<<WGM01)
    out  TCCR0A, r16
    /* OCR0A = 249 */
    ldi  r16, OCR0A_VALUE
    out  OCR0A, r16
    /* przerwanie COMPA */
    ldi  r16, (1<<OCIE0A)
    out  TIMSK0, r16
    /* preskaler 64 */
    ldi  r16, (1<<CS01) | (1<<CS00)
    out  TCCR0B, r16
    /* sleep: Idle (SM1=0, SM0=0), SE=1 */
    ldi  r16, (1<<SE)
    out  MCUCR, r16
    sei
loop:
    sleep
    rjmp loop

Ta wersja jest poprawna dla ATtiny13A, bo:

  • TIM0_COMPA ma wektor programu pod adresem 0x0006,
  • rejestr MCUCR zawiera bity SE, SM1, SM0,
  • w podsumowaniu rejestrów ATtiny13A występuje tylko SPL, a nie SPH,
  • TIM0_COMPA_vect jest nazwą znaną w avr-libc dla tego układu. (ww1.microchip.com)

4. Wariant z kalibracją OSCCAL

Najpraktyczniejszy wariant to korekcja względem wartości fabrycznej, bo po resecie sprzęt i tak ładuje kalibrację 9,6 MHz do OSCCAL. Zamiast wpisywać „sztywną” liczbę absolutną, lepiej dodać lub odjąć mały offset. (ww1.microchip.com)

Plik: polaryzacja_osccal.c

#define F_CPU 1200000UL
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
volatile uint16_t tick75 = 0;
/*
 * Korekta względem fabrycznej wartości OSCCAL załadowanej przy resecie.
 * Dodatnia wartość -> szybszy zegar
 * Ujemna wartość  -> wolniejszy zegar
 *
 * Zalecenie praktyczne: stroić małymi krokami, np. od -8 do +8.
 */
#define OSCCAL_DELTA   0
static void osccal_apply_delta(void)
{
    int16_t cal = (int16_t)OSCCAL + OSCCAL_DELTA;
    if (cal < 0) {
        cal = 0;
    }
    if (cal > 0x7F) {
        cal = 0x7F;
    }
    OSCCAL = (uint8_t)cal;
}
ISR(TIM0_COMPA_vect)
{
    if (++tick75 >= 4800) {
        tick75 = 0;
        PINB = _BV(PB0) | _BV(PB1);
    }
}
int main(void)
{
    osccal_apply_delta();
    DDRB  = _BV(DDB0) | _BV(DDB1);
    PORTB = _BV(PB0);
    GTCCR  = _BV(PSR10);
    TCNT0  = 0;
    TCCR0A = _BV(WGM01);
    OCR0A  = 249;
    TIMSK0 = _BV(OCIE0A);
    TCCR0B = _BV(CS01) | _BV(CS00);
    /* opcjonalnie dla mniejszego poboru w Idle:
       ACSR   = _BV(ACD);
       ADCSRA = 0;
       PRR    = _BV(PRADC);
    */
    set_sleep_mode(SLEEP_MODE_IDLE);
    sleep_enable();
    sei();
    for (;;) {
        sleep_mode();
    }
}

Praktycznie:

  • jeśli czas rzeczywisty jest za długi: zwiększyć OSCCAL_DELTA,
  • jeśli czas rzeczywisty jest za krótki: zmniejszyć OSCCAL_DELTA.

Do strojenia nie polecam mierzyć od razu pełnych 128 s. Lepiej tymczasowo ustawić próg na 75 zamiast 4800, wtedy przełączenie nastąpi co 1 s i strojenie będzie znacznie szybsze. Jest to poprawne inżyniersko, bo OSCCAL skaluje cały zegar systemowy, więc po zestrojeniu krótkiego odcinka przeskaluje się także odcinek 64 s. To jest wniosek wynikający bezpośrednio z tego, że OSCCAL stroi wewnętrzny oscylator używany przez zegar systemowy, a Timer0 korzysta z tego samego zegara synchronicznego. (ww1.microchip.com)


Aktualne informacje i trendy

Z punktu widzenia dokumentacji producenta nadal aktualne są dwie rzeczy:

  • AVR053 pozostaje właściwą notą aplikacyjną do kalibracji wewnętrznego RC,
  • dla mostków i większych obciążeń praktyka rynkowa przesunęła się w stronę MOSFET-ów logic-level albo gotowych driverów/mostków H z funkcjami typu anti-shoot-through i dead-time. (onlinedocs.microchip.com)

Wniosek praktyczny: jeśli obciążenie jest małe i zasilane z tego samego napięcia co ATtiny13A, można zrobić prosty mostek dyskretny. Jeśli jednak:

  • prąd jest większy,
  • napięcie obciążenia jest wyższe niż VCC mikrokontrolera,
  • przewody są długie,
  • obciążenie jest silnie indukcyjne,

to lepiej przejść na dedykowany driver/mostek H niż walczyć z dyskretną topologią. To nie jest kwestia „czy zadziała”, tylko stabilności, odporności i bezpieczeństwa projektu. (developerhelp.microchip.com)


Wspierające wyjaśnienia i detale

1. Dlaczego PINB = _BV(PB0) | _BV(PB1) jest dobre

W ATtiny13A zapis logicznej jedynki do PINxn przełącza PORTxn. Dzięki temu oba bity można przełączyć jednym zapisem magistralowym, co minimalizuje asymetrię czasową między PB0 i PB1. (ww1.microchip.com)

2. Dlaczego SLEEP_MODE_IDLE

W trybie Idle procesor zatrzymuje clkCPU, ale Timer/Counter i przerwania dalej działają. To dokładnie pasuje do Pańskiego zastosowania: CPU śpi, a Timer0 wybudza go tylko na obsługę ISR. (ww1.microchip.com)

3. Dlaczego nie dwa tranzystory low-side

Jeżeli celem jest odwracanie polaryzacji na obciążeniu, potrzebne są cztery przełączniki w topologii H-bridge. Dokumentacja Microchipa dla aplikacji BDC wyraźnie rozróżnia:

  • pojedynczy low-side switch — tylko jeden kierunek,
  • H-bridge — kierunek „forward” i „reverse” przez odpowiednie przekątne mostka. (microchip.com)

Aspekty etyczne i prawne

W tym projekcie nie ma szczególnych aspektów prywatności, ale są ważne aspekty bezpieczeństwa technicznego:

  • przy obciążeniach indukcyjnych trzeba przewidzieć BEMF i ochronę tranzystorów,
  • przy mostku H należy unikać chwilowego zwarcia gałęzi górnej i dolnej,
  • przy zmianie fuse należy zachować ostrożność — ATtiny13A ma tylko dwa bajty fuse i ich błędna konfiguracja może utrudnić dalsze programowanie. (microchip.com)

Jeżeli urządzenie ma trafić do produktu komercyjnego, dochodzą jeszcze kwestie:

  • EMC,
  • odporności na przepięcia,
  • temperatury pracy,
  • ochrony przed odwrotną polaryzacją zasilania.

Praktyczne wskazówki

1. Schemat tranzystorowy dla większego obciążenia — poprawny wariant

Najprostszy poprawny wariant dla odwracania polaryzacji to mostek H. Dla bezpośredniego sterowania z PB0/PB1 proponuję wersję z MOSFET logic-level, ale tylko gdy:

[
V{LOAD} = V{CC_MCU}
]

czyli np. mikrokontroler i obciążenie pracują z tego samego 5 V.

Schemat ideowy

                 +VLOAD = +VCC MCU (np. 5 V)
                         |
                  .------+------.
                  |             |
               Q1 P-MOS      Q3 P-MOS
                  |             |
      węzeł A ----+---- OBCIĄŻENIE ----+---- węzeł B
                  |                    |
               Q2 N-MOS             Q4 N-MOS
                  |                    |
                 GND                  GND

Sterowanie lewej połówki:
PB0 ---[100Ω]--- bramka Q1
PB0 ---[100Ω]--- bramka Q2
bramka Q1 ---[100k]--- +VLOAD
bramka Q2 ---[100k]--- GND
Sterowanie prawej połówki:
PB1 ---[100Ω]--- bramka Q3
PB1 ---[100Ω]--- bramka Q4
bramka Q3 ---[100k]--- +VLOAD
bramka Q4 ---[100k]--- GND

Zasada działania

  • PB0 = 1, PB1 = 0

    • lewa strona: Q1 OFF, Q2 ON,
    • prawa strona: Q3 ON, Q4 OFF,
    • prąd płynie z prawej do lewej przez obciążenie.
  • PB0 = 0, PB1 = 1

    • lewa strona: Q1 ON, Q2 OFF,
    • prawa strona: Q3 OFF, Q4 ON,
    • prąd płynie z lewej do prawej.

To odpowiada dokładnie idei mostka H: cztery przełączniki, dwa kierunki pracy i stany przekątnych. Microchip wprost opisuje dla H-bridge tryby „Forward” i „Reverse” jako załączenie odpowiednich przekątnych. (microchip.com)

Elementy zalecane

  • Q1, Q3: P-MOSFET logic-level,
  • Q2, Q4: N-MOSFET logic-level,
  • rezystory bramkowe: 100 Ω,
  • rezystory podciągające / ściągające: 100 kΩ,
  • kondensator odsprzęgający przy MCU: 100 nF,
  • kondensator przy mostku: 100 nF + 10 µF,
  • przy indukcyjności i długich przewodach: TVS lub dodatkowe szybkie diody / snubbery.

Ważna uwaga: dla klasycznego pojedynczego low-side switcha daje się jedną diodę równolegle do obciążenia, ale dla H-bridge to podejście nie wystarcza; Microchip pokazuje dla H-bridge zabezpieczenia związane z każdą gałęzią mostka. (microchip.com)

2. Kiedy nie robić tego „na piechotę”

Nie polecam bezpośredniego dyskretnego mostka z PB0/PB1, jeśli:

  • VLOAD jest większe niż VCC mikrokontrolera,
  • prąd przekracza kilkaset mA,
  • obciążenie jest silnie indukcyjne,
  • układ ma pracować długo i niezawodnie w trudnym środowisku.

Wtedy lepiej użyć dedykowanego mostka/drivera z ochroną przed shoot-through i kontrolą dead-time. Microchip wprost wskazuje takie rozwiązania w rodzinach swoich driverów. (developerhelp.microchip.com)

3. Jak testować

Polecam kolejność uruchomienia:

  1. Najpierw uruchomić sam mikrokontroler bez tranzystorów.
  2. Sprawdzić na oscyloskopie:
    • PB0,
    • PB1,
    • czy są komplementarne.
  3. Tymczasowo zmienić próg 4800 na 75, aby mieć 1 s.
  4. Dobrać OSCCAL_DELTA.
  5. Dopiero potem podłączyć mostek i obciążenie.
  6. Na końcu sprawdzić przebiegi na węzłach A i B mostka.

Ewentualne zastrzeżenia lub uwagi dodatkowe

  • Wersja dyskretna bez dead-time jest akceptowalna dla bardzo wolnego przełączania, ale nie jest rozwiązaniem „przemysłowo eleganckim”.
  • Jeżeli zależy Panu na maksymalnym bezpieczeństwie mostka, warto zmienić algorytm przełączania z „toggle obu pinów naraz” na sekwencję:
    1. oba wyjścia w stan wyłączenia,
    2. krótka przerwa,
    3. załączenie przeciwnej przekątnej.
  • To jednak zmienia prostotę obecnego programu i wprowadza krótką przerwę między polaryzacjami.

Dodatkowa korekta względem przykładowych odpowiedzi: gotowy kod assemblera nie powinien używać rejestrów i konstrukcji nieobecnych w ATtiny13A, takich jak SPH czy SMPR; poprawne odniesienia wynikają wprost z mapy rejestrów tego układu. (ww1.microchip.com)


Sugestie dalszych badań

Jeżeli będzie Pan chciał rozwinąć projekt, sensowne kierunki to:

  • zapis kalibracji OSCCAL do EEPROM,
  • autokalibracja względem zewnętrznego wzorca,
  • przejście z dyskretnego mostka na dedykowany driver,
  • wyłączenie ADC/komparatora dla dalszej redukcji poboru mocy w Idle,
  • ewentualne przejście na watchdog lub licznik asynchroniczny, jeśli priorytetem jest ultra-low-power.

Do kalibracji RC najlepszym dalszym materiałem jest AVR053, a do samego ATtiny13A podstawą pozostaje datasheet urządzenia. (onlinedocs.microchip.com)


Krótkie podsumowanie

Najkrócej:

  • tak — da się to zrobić poprawnie na ATtiny13A,
  • poprawny Makefile powinien budować .S przez avr-gcc,
  • poprawna wersja assemblera dla ATtiny13A używa __vector_6 dla TIM0_COMPA,
  • wariant z OSCCAL najlepiej zrobić jako małą korekcję względem wartości fabrycznej,
  • dla większego obciążenia i odwracania polaryzacji trzeba zastosować mostek H, nie dwa zwykłe tranzystory low-side. (gcc.gnu.org)

Jeżeli Pan chce, w następnym kroku mogę przygotować jeszcze:

  1. wersję całkowicie w assemblerze także z kalibracją OSCCAL,
  2. schemat w formie „rysunku połączeń pin-po-pin” dla DIP-8 ATtiny13A,
  3. wariant z dedykowanym mostkiem H zamiast dyskretnych tranzystorów.

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.