Jak wygląda kod na Atmega 328p,sprzętowy , PWM , zmiana polaryzacji LOW 64 sekund ,
HIGH 64 sekund , okres 128 sekund , na najlepszych do tego 2 pinach,chyba są to piny asynchroniczne, z kwarcem zegarkowym 32768
kHz,Wedłu poniższych wskazówek : " Cały czas mowa o precyzyjnych
częstotliwościach, współczynniku wypełnienia a uczepiłaś się najgorszego z możliwych
rozwiązań mając sprzętowe sterowanie GPIO, gdzie trzeba liczyć każdy cykl maszynowy jak w
latach 80.
W Generatorze DDS z EP 6/2018 jest sprzętowe sterowanie wyjściem:
void Timer1_Start(uint8_t dzielnik)
{
// OCR1B=dzielnik;
OCR1B=dzielnik;
//Output compare toggles OC1A pin
// TCCR1A |= 0x40; // COM1A = 1
TCCR1A |= 0x10; // COM1B = 1
//start timer without prescaler
TCCR1B = 0b00001001; // WGM 12=1, CS=1
DDRB |= _BV(PB4);
}
Uzyskasz DOKŁADNIE 50% bez liczenia cykli. Nie muszą być włączone przerwania ale mogą.
Program główny może robić co chce a nie wpłynie to na generowany przebieg bez względu na
to, czy przerwania będą włączone czy nie. Max częstotliwość to Fclk/2, uzyskaj taką przez
delay. Taki program jest krótszy niż z delay. Jeśli częstotliwość jest zbyt duża należy timery
połączyć w kaskadę.Można także użyć timera w trybie PWM. Używając OCRxA można ustawić
próg zliczania a co za tym idzie, precyzyjnie regulować częstotliwość.Głupotą jest, mając
możliwości sprzętowe, rozwiązywać problem w sposób programowy."
Tak — najlepsze, w pełni sprzętowe rozwiązanie dla ATmega328P przy kwarcu zegarkowym 32,768 kHz to nie „programowy PWM”, tylko kaskada dwóch timerów:
Masz rację, że zgodnie z cytowaną wskazówką należy iść w stronę sprzętowego przełączania wyjścia przez timer, a nie w delay(), pętle i liczenie cykli CPU. W ATmega328P są tu dwa istotne fakty:
To prowadzi do najlepszego układu blokowego:
| Funkcja | Timer / pin | Rola |
|---|---|---|
| Kwarc 32,768 kHz | PB6/TOSC1, PB7/TOSC2 | precyzyjna baza czasu |
| Generator 1 Hz | Timer2, wyjście PB3/OC2A | sygnał odniesienia |
| Wejście licznika | PD5/T1 | zliczanie impulsów z Timer2 |
| Wyjścia końcowe | PB1/OC1A, PB2/OC1B | przebieg 128 s, 50% |
To rozwiązanie jest zgodne z ideą: program główny może robić cokolwiek, a czasowanie wyjść pozostaje sprzętowe. (ww1.microchip.com)
Timer2 w ATmega328P jest 8-bitowy. Przy zegarze 32,768 kHz i preskalerze /1024 dostajesz zegar licznika równy:
\[
f_{T2}=\frac{32768}{1024}=32\ \text{Hz}
\]
czyli jeden krok co:
\[
T_{tick}=31{,}25\ \text{ms}
\]
Pełne 256 zliczeń to:
\[
256 \cdot 31{,}25\ \text{ms}=8\ \text{s}
\]
Zatem z jednego 8-bitowego Timer2 dostajesz naturalnie tylko przedziały rzędu 8 s do przepełnienia/porównania, a nie 64 s półokresu. W trybie toggle daje to maksymalnie 16 s okresu sygnału na pinie, więc do 128 s brakuje jeszcze podziału przez 8. (ww1.microchip.com)
Dlatego jedna z przykładowych odpowiedzi online, która próbowała upchnąć wartości rzędu milionów do Timer2 albo Timer1, jest po prostu błędna: Timer2 jest 8-bitowy, a Timer1 oraz OCR1A/OCR1B/ICR1 są 16-bitowe. (ww1.microchip.com)
Najprościej zrobić tak:
W trybie CTC + toggle OC2A obowiązuje zależność:
\[
f{OC2A}=\frac{f{clk}}{2 \cdot N \cdot (1+OCR2A)}
\]
Przy:
dostajesz:
\[
f_{OC2A}=\frac{32768}{2 \cdot 128 \cdot 128}=1\ \text{Hz}
\]
Czyli na PB3/OC2A powstaje idealny prostokąt 1 Hz. Toggle na wyjściu compare w trybach nie-PWM jest sprzętowo wspierany przez Timer2. (ww1.microchip.com)
Timer1 może być taktowany zewnętrznym źródłem zegara na pinie T1, a tryb CS12:CS10 = 110 oznacza zliczanie na zboczu opadającym. Ponieważ sygnał 1 Hz ma jedno zbocze opadające na sekundę, Timer1 dostaje dokładnie 1 impuls/s. (ww1.microchip.com)
W trybie CTC z:
OCR1A = 63OCR1B = 63COM1A0 = 1COM1B0 = 1Timer1 wygeneruje compare-match co:
\[
63+1=64
\]
impulsy wejściowe. Ponieważ wejście ma 1 impuls/s, przełączenie następuje co:
\[
64 \cdot 1\ \text{s}=64\ \text{s}
\]
A ponieważ wyjście jest toggle on compare match, dostajesz:
Poniżej masz wersję w pełni sprzętową, bez ISR, bez delay(), bez udziału pętli głównej w generacji przebiegu:
#include <avr/io.h>
static inline void timer2_wait_update(void)
{
while (ASSR & (_BV(TCN2UB) | _BV(OCR2AUB) | _BV(TCR2AUB) | _BV(TCR2BUB)))
{
;
}
}
void hw_128s_init(void)
{
/* -----------------------------
* 1. Piny końcowe: OC1A = PB1, OC1B = PB2
* Start od stanu LOW
* ----------------------------- */
DDRB |= _BV(DDB1) | _BV(DDB2) | _BV(DDB3); // PB1, PB2, PB3 jako wyjścia
PORTB &= ~(_BV(PORTB1) | _BV(PORTB2) | _BV(PORTB3));
/* T1 = PD5 jako wejście */
DDRD &= ~_BV(DDD5);
PORTD &= ~_BV(PORTD5); // bez pull-up, bo sygnał przyjdzie z PB3
/* -----------------------------
* 2. Timer1:
* CTC, OC1A/OC1B toggle on compare
* Na razie STOP - uruchomimy go po Timer2
* ----------------------------- */
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = 63; // 64 impulsy = 64 s
OCR1B = 63; // drugi pin identycznie
TCCR1A = _BV(COM1A0) | _BV(COM1B0); // toggle OC1A i OC1B
TCCR1B = _BV(WGM12); // CTC, ale jeszcze bez zegara
/* -----------------------------
* 3. Timer2 asynchroniczny:
* kwarc 32.768 kHz na TOSC1/TOSC2
* CTC + toggle OC2A = 1 Hz na PB3
* ----------------------------- */
TCCR2A = 0;
TCCR2B = 0;
ASSR |= _BV(AS2); // Timer2 z oscylatora TOSC1/TOSC2
TCNT2 = 0;
OCR2A = 127; // 32768 / (2*128*(127+1)) = 1 Hz
TCCR2A = _BV(WGM21) | _BV(COM2A0); // CTC, toggle OC2A
TCCR2B = _BV(CS22) | _BV(CS20); // preskaler /128
timer2_wait_update();
/* -----------------------------
* 4. Start Timer1:
* zewnętrzny zegar na T1, zbocze opadające
* PB3 (OC2A) trzeba fizycznie połączyć z PD5 (T1)
* ----------------------------- */
TCCR1B = _BV(WGM12) | _BV(CS12) | _BV(CS11); // CTC + ext clk T1, falling edge
}
int main(void)
{
hw_128s_init();
while (1)
{
/* Kod główny może robić cokolwiek.
Generacja przebiegu jest sprzętowa. */
}
}
Ten kod bazuje dokładnie na możliwościach opisanych w dokumentacji:
Aktualna dokumentacja Microchip dla ATmega328P nadal jednoznacznie potwierdza, że:
clkI/O. (ww1.microchip.com)Dla Twojego przypadku celem nie jest „modulacja szerokości impulsu” w klasycznym sensie, tylko bardzo wolny przebieg prostokątny 50%. Dlatego najlepszy jest tryb:
a nie typowy Fast PWM. Właśnie wtedy hardware sam gwarantuje idealną symetrię bez liczenia cykli procesora. (ww1.microchip.com)
Bo to są dwa sprzętowe wyjścia jednego 16-bitowego timera:
Możesz na obu mieć ten sam przebieg równocześnie, z tej samej bazy czasu. To lepsze niż próba użycia dwóch różnych timerów do końcowego wyjścia. (ww1.microchip.com)
Najważniejsze korekty:
OC2A nie jest na PD6.
Na ATmega328P:
Wartości typu OCR2A = 4194303 są błędne, bo Timer2 jest 8-bitowy, a OCR2A ma 8 bitów. Analogicznie ICR1 = 4194303 też jest błędne, bo ICR1/OCR1A/OCR1B w Timer1 są 16-bitowe. (ww1.microchip.com)
W samym generatorze przebiegu nie ma szczególnych zagadnień etycznych. Z praktycznego punktu widzenia:
To są standardowe zasady projektowe dla elektroniki wbudowanej.
Minimalny schemat połączeń:
Ponieważ PB6/PB7 są jednocześnie związane z funkcjami XTAL1/XTAL2 oraz TOSC1/TOSC2, w praktyce nie możesz równocześnie używać tych samych pinów dla zwykłego rezonatora systemowego i dla asynchronicznego zegara Timer2. Wniosek praktyczny: jeśli chcesz używać kwarcu zegarkowego dla Timer2, ustaw MCU na wewnętrzny RC jako zegar systemowy. (ww1.microchip.com)
Na typowej płytce UNO/Nano te piny są zwykle zajęte przez rezonator/kwarc systemowy 16 MHz, więc na gotowej płytce bez modyfikacji to rozwiązanie zwykle nie jest wygodne. Najlepiej robić to na „gołym” ATmega328P albo własnej płytce.
Jeśli chcesz zacząć od:
PORTB1 i PORTB2 wyzerowane przed startem timerów,To zmienia tylko fazę początkową, nie zmienia okresu ani 50% wypełnienia.
Najprostsza weryfikacja:
Jeśli PB3 nie działa, najpierw diagnozuj:
ASSR,TCN2UB/OCR2AUB/TCR2AUB/TCR2BUB. (ww1.microchip.com)Jeśli chcesz, mogę Ci od razu przygotować jedną z trzech wersji:
millis() i delay(),Mogę też rozpisać:
Najlepsze rozwiązanie na ATmega328P dla LOW 64 s / HIGH 64 s / okres 128 s / dokładne 50% to:
Jeśli chcesz, w następnym kroku mogę podać:
wersję kodu z komentarzem do każdej linijki.
Tak — poniżej masz poprawioną i kompletną wersję kodu w C dla ATmega328P, z komentarzem praktycznie do każdej linijki.
Kluczowe punkty:
_BW(...) na poprawne _BV(...),toggle: dla wyjść compare jest to COM1x1:0 = 01, a nie 10.Poniżej wersja kodu z komentarzem do każdej istotnej linijki.
#include <avr/io.h> // Definicje rejestrów i bitów dla mikrokontrolerów AVR, w tym ATmega328P
static inline void timer2_wait_update(void) // Funkcja pomocnicza: czeka, aż Timer2 zakończy synchronizację rejestrów
{
while (ASSR & ( // Sprawdzaj rejestr ASSR tak długo, jak którykolwiek bit "update busy" jest ustawiony
_BV(TCN2UB) | // TCNT2 Update Busy - licznik Timer2 jeszcze się synchronizuje
_BV(OCR2AUB) | // OCR2A Update Busy - rejestr porównania A jeszcze się synchronizuje
_BV(TCR2AUB) | // TCCR2A Update Busy - rejestr sterujący A jeszcze się synchronizuje
_BV(TCR2BUB) // TCCR2B Update Busy - rejestr sterujący B jeszcze się synchronizuje
))
{
; // Pusta pętla - aktywne oczekiwanie na zakończenie synchronizacji
}
}
void hw_128s_init(void) // Funkcja inicjalizująca cały układ generacji przebiegu 128 s
{
DDRB |= _BV(DDB1) | _BV(DDB2) | _BV(DDB3); // Ustaw PB1=OC1A, PB2=OC1B i PB3=OC2A jako wyjścia
DDRD &= ~_BV(DDD5); // Ustaw PD5=T1 jako wejście, bo Timer1 ma liczyć impulsy zewnętrzne na tym pinie
PORTD &= ~_BV(PORTD5); // Wyłącz pull-up na PD5, ponieważ sygnał będzie dostarczany bezpośrednio z PB3
PORTB &= ~(_BV(PORTB1) | _BV(PORTB2) | _BV(PORTB3)); // Ustaw stan początkowy LOW na PB1, PB2 i PB3
TCCR1A = 0; // Wyzeruj rejestr sterujący A Timera1, aby zacząć od znanej konfiguracji
TCCR1B = 0; // Wyzeruj rejestr sterujący B Timera1 i zatrzymaj licznik
TCNT1 = 0; // Wyzeruj licznik Timera1
OCR1A = 63; // Ustaw TOP dla Timer1 na 63, co daje 64 zliczenia: 0..63
OCR1B = 63; // Ustaw drugi kanał compare na tę samą wartość, aby OC1B przełączał się równocześnie
TCCR1A = _BV(COM1A0) | _BV(COM1B0); // Ustaw toggle na OC1A i OC1B przy compare match; COM1x1:0 = 01
TCCR1B = _BV(WGM12); // Ustaw Timer1 w tryb CTC: licznik zeruje się po osiągnięciu OCR1A
TCCR2A = 0; // Wyzeruj rejestr sterujący A Timera2 przed konfiguracją
TCCR2B = 0; // Wyzeruj rejestr sterujący B Timera2 i zatrzymaj licznik
ASSR |= _BV(AS2); // Włącz tryb asynchroniczny Timera2, czyli źródło zegara z TOSC1/TOSC2
TCNT2 = 0; // Wyzeruj licznik Timera2
OCR2A = 127; // Ustaw wartość compare dla Timer2: 32768/(2*128*(127+1)) = 1 Hz na OC2A
TCCR2A = _BV(WGM21) | _BV(COM2A0); // Timer2 w CTC oraz toggle na wyjściu OC2A przy compare match
TCCR2B = _BV(CS22) | _BV(CS20); // Ustaw preskaler Timera2 na 128 i uruchom licznik
timer2_wait_update(); // Poczekaj, aż wszystkie wpisy do Timer2 zsynchronizują się z zegarem 32,768 kHz
TCCR1B |= _BV(CS12) | _BV(CS11); // Włącz Timer1: zewnętrzne taktowanie na T1, zbocze opadające, tryb CTC pozostaje aktywny
}
int main(void) // Główna funkcja programu
{
hw_128s_init(); // Jednorazowo skonfiguruj timery i piny
while (1) // Pętla nieskończona - program główny może wykonywać inne zadania
{
; // Brak kodu - generacja przebiegu odbywa się całkowicie sprzętowo
}
}
Timer2 pracuje asynchronicznie z kwarcem zegarkowym 32,768 kHz.
Dla ustawień:
OCR2A = 127,częstotliwość na PB3 wynosi:
[
f_{OC2A} = \frac{32768}{2 \cdot 128 \cdot (127 + 1)} = 1\ \text{Hz}
]
To oznacza:
Timer1 nie jest taktowany zegarem CPU, tylko impulsem podanym na PD5/T1.
Jeżeli:
OCR1A = 63,to Timer1 wykona compare match co 64 impulsy:
[
63 + 1 = 64
]
Ponieważ zliczane jest jedno zbocze na sekundę, przełączenie następuje co 64 s.
Dla:
COM1A0 = 1,COM1B0 = 1,wyjścia PB1/OC1A i PB2/OC1B są przełączane sprzętowo przy compare match.
Skutek:
Przy stanie startowym LOW na PB3:
Jeżeli Timer1 liczy zbocza opadające, to pierwsze zliczenie wypada dokładnie po 1 s, co daje wygodniejszą interpretację czasową całego układu.
W kontekście praktyki inżynierskiej ten sposób realizacji nadal jest bardzo poprawny i profesjonalny, mimo że mikrokontroler jest prosty.
Najważniejsze obserwacje:
delay() lub intensywnego użycia ISR,Istotna uwaga praktyczna:
Musisz mieć fizycznie:
PB6 (TOSC1) ----\
>---- kwarc 32,768 kHz
PB7 (TOSC2) ----/
PB3 (OC2A) -------- PD5 (T1)
PB1 (OC1A) -------- wyjście 128 s
PB2 (OC1B) -------- wyjście 128 s
Jeżeli używasz ATmega328P z klasycznym kwarcem systemowym na PB6/PB7, to tego rozwiązania nie da się zastosować bez zmiany konfiguracji zegara systemowego, ponieważ:
W praktyce oznacza to:
delay()?Bo delay():
W tym przypadku aspekty etyczne i prawne są minimalne, ale technicznie warto pamiętać o:
Najprostsza procedura:
ASSR,Poprawki techniczne:
DDRD &= ~_BW(DDD5); było błędne — musi być:DDRD &= ~_BV(DDD5);
COM1A0=1 i COM1B0=1 oznacza toggle,COM1x1:0 = 01,10.To ważne, bo 10 oznaczałoby inny tryb pracy wyjścia compare.
Jeżeli chcesz rozwinąć ten projekt, kolejne sensowne kierunki to:
Mogę też przygotować:
Najlepsza wersja kodu to ta oparta na:
To rozwiązanie jest:
Jeżeli chcesz, mogę teraz przygotować drugą wersję tego samego kodu, gdzie komentarz będzie dosłownie przy każdej pojedynczej linijce kodu, bez żadnych bloków opisowych.