Podczas pracy z prekalkerem zegara 64 na ATmega328, jeden z moich timerów przyspiesza z nieznanych przyczyn w określonym czasie wykonywania.
Używam dwóch timerów na ATmega328, aby wygenerować taktowanie potrzebne TLC5940 (patrz poniżej dlaczego; to nie ma znaczenia dla pytania). TIMER0
generuje sygnał zegarowy przy włączonym szybkim PWM OC0B
i jest konfigurowany w następujący sposób:
TCCR0A = 0
|(0<<COM0A1) // Bits 7:6 – COM0A1:0: Compare Match Output A Mode
|(0<<COM0A0) //
|(1<<COM0B1) // Bits 5:4 – COM0B1:0: Compare Match Output B Mode
|(0<<COM0B0)
|(1<<WGM01) // Bits 1:0 – WGM01:0: Waveform Generation Mode
|(1<<WGM00)
;
TCCR0B = 0
|(0<<FOC0A) // Force Output Compare A
|(0<<FOC0B) // Force Output Compare B
|(1<<WGM02) // Bit 3 – WGM02: Waveform Generation Mode
|(0<<CS02) // Bits 2:0 – CS02:0: Clock Select
|(1<<CS01)
|(0<<CS00) // 010 = clock/8
;
OCR0A = 8;
OCR0B = 4;
TIMSK0 = 0;
TIMER2
zmienia linię danych w celu generowania impulsu wygaszania co 256 TIMER0
cykli i jest konfigurowany w następujący sposób:
ASSR = 0;
TCCR2A = 0
|(0<<COM2A1) // Bits 7:6 – COM0A1:0: Compare Match Output A Mode
|(0<<COM2A0) //
|(0<<COM2B1) // Bits 5:4 – COM0B1:0: Compare Match Output B Mode
|(0<<COM2B0)
|(0<<WGM21) // Bits 1:0 – WGM01:0: Waveform Generation Mode
|(0<<WGM20)
;
TCCR2B = 0
|(0<<FOC2A) // Force Output Compare A
|(0<<FOC2B) // Force Output Compare B
|(0<<WGM22) // Bit 3 – WGM02: Waveform Generation Mode
|(1<<CS22) // Bits 2:0 – CS02:0: Clock Select
|(0<<CS21)
|(0<<CS20) // 100 = 64
;
OCR2A = 255;
OCR2B = 255;
TIMSK2 = 0
|(1<<TOIE2); // Timer/Counter0 Overflow Interrupt Enable
TIMER2
wywołuje ISR przy przepełnieniu (co 256 cykli). ISR ręcznie generuje impuls wygaszający i impuls blokujący, jeśli to konieczne:
volatile uint8_t fLatch;
ISR(TIMER2_OVF_vect) {
if (fLatch) {
fLatch = 0;
TLC5940_XLAT_PORT |= (1<<TLC5940_XLAT_BIT); // XLAT -> high
for (int i=0;i<10;i++)
nop();
TLC5940_XLAT_PORT &= ~(1<<TLC5940_XLAT_BIT); // XLAT -> high
}
// Blank
TLC5940_BLANK_PORT |= (1<<TLC5940_BLANK_BIT);
for (int i=0;i<10;i++)
nop();
TLC5940_BLANK_PORT &= ~(1<<TLC5940_BLANK_BIT);
}
nop()
Opóźnienie ten kod jest tylko, aby impuls bardziej widoczne na zapisie analizatora logicznego. Oto, main()
jak wygląda pętla w funkcji: wyślij trochę danych szeregowych, poczekaj, aż ISR zajmie się zatrzaśnięciem, a następnie zrób to ponownie:
for (;;) {
if (!fLatch) {
sendSerial();
fLatch = 1;
_delay_ms(1);
}
nop();
}
sendSerial()
wysyła niektóre SPI ( kod dla pastebin ze względu na zwięzłość ). Mój problem polega na tym, że po sendSerial()
zakończeniu, podczas oczekiwania na fLatch
ustawienie niskiej (przetworzonej) wartości zegara taktowania przyspiesza. Oto ślad analizatora logicznego (wyciąłem obszary, w których ten sam sygnał nadal zmniejsza grafikę):
Po lewej stronie kanały 0 i 1 pokazują koniec wysyłanych danych SPI. Również po lewej stronie na kanale 4 widać puls wygaszający. Na kanale 2 impuls taktowania ściska się zgodnie z oczekiwaniami. Tuż gdzie różnica w obrazie jest, fLatch
jest ustawiony 1
wewnątrz main()
rutyny. Niedługo potem TIMER0
przyspiesza około czterokrotnie. W końcu wykonywany jest impuls wygaszania i impuls blokujący (kanały 3 i 4, prawa jedna trzecia obrazu), a teraz impuls taktowania wznawia swoją regularną częstotliwość, a dane szeregowe są Wysłany ponownie. Próbowałem wyjąć delay_ms(1);
linię main()
, ale te same wyniki zostały uzyskane. Co się dzieje? Powinienem zauważyć, że ATmega jest taktowana zegarem o częstotliwości 20 MHz, a następnie zwolniona o 64x przy użyciu następującego kodu:
CLKPR = 1<<CLKPCE;
CLKPR = (0<<CLKPS3)|(1<<CLKPS2)|(1<<CLKPS1)|(0<<CLKPS0);
Do czego to służy: eksperymentuję ze sterowaniem sterownikiem LED TLC5940 : układy te wymagają zewnętrznego zegara i resetu na końcu cyklu taktowania.
sendSerial()
to mój kod, który wysyła dane przez SPI: nie dotyka TCCR
rejestrów (kontroli timera).