Przyspieszenie timera AVR na ATmega328

9

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). TIMER0generuje sygnał zegarowy przy włączonym szybkim PWM OC0Bi 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;

TIMER2zmienia linię danych w celu generowania impulsu wygaszania co 256 TIMER0cykli 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

TIMER2wywoł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 fLatchustawienie niskiej (przetworzonej) wartości zegara taktowania przyspiesza. Oto ślad analizatora logicznego (wyciąłem obszary, w których ten sam sygnał nadal zmniejsza grafikę):

wprowadź opis zdjęcia tutaj

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, fLatchjest ustawiony 1wewnątrz main()rutyny. Niedługo potem TIMER0przyspiesza 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.

angelatlarge
źródło
Jeśli masz debugger, spróbuj zatrzymać kod, gdy timer jest zbyt szybki i ponownie przeczytaj rejestr konfiguracji tego timera. Gdy znajdziesz ten, który ma niewłaściwą wartość, uruchom punkt przerwania dla tej zmiany rejestru i sprawdź, która część kodu działa nieprawidłowo. Wydaje mi się, że problem znajduje się w bibliotece zewnętrznej, z której możesz skorzystać i która używa tego timera do wewnętrznych czynności, takich jak opóźnienia.
Blup1980
Dwa problemy: a) Nie mam programatora JTAG, więc nie mam możliwości debugowania układu. B) Nigdy nie zmieniam wartości rejestru timera po konfiguracji pokazanej powyżej, więc nie oczekuję, że wartości rejestru timera faktycznie się zmieniają. Czy to naiwne?
angelatlarge 11.04.13
1
Właściwie jedna używana biblioteka może zmienić ustawienia UART. Widzę, że używasz funkcji sendSerial (). Czy to część twojego kodu, czy biblioteka zewnętrzna? Być może to nie Ty zmieniasz ustawienia, ale fragment kodu w nazwie biblioteki. Sugeruję użycie portu szeregowego do wyprowadzenia parametrów konfiguracyjnych i spróbowanie dowiedzieć się, co się zmieniło. Możesz także spojrzeć na źródło używanych bibliotek (jeśli istnieją) i upewnić się, że nie używają również tego timera.
Blup1980
1
Oprócz tego, co @ Blup1980 zasugerował, inną rzeczą, którą warto spróbować, jest usunięcie TLC5940, aby upewnić się, że nie robi nic dziwnego z linią zegara.
PeterJ
@ Blup1980 Nie jestem pewien, czy dostrzegam znaczenie UART: Nie używam USART dla SPI, tylko „zwykłe” funkcje SPI. sendSerial()to mój kod, który wysyła dane przez SPI: nie dotyka TCCRrejestrów (kontroli timera).
angelatlarge 11.04.13

Odpowiedzi:

1

W celu szybkiego debugowania spróbowałbym zrobić to samo za pomocą Biblioteki Arduino dla TLC5940 i sprawdzić, czy robi się szybko, czy nie. Jeśli działa z biblioteką, możesz sprawdzić jej źródło i porównać z własną. Ponieważ znasz AVR, powinieneś łatwo przekonwertować źródło Arduino na natywny AVR.

Na wypadek, gdybyś nie wiedział, jak przesłać skompilowane szkice Arduino do AVR: Po skompilowaniu szkic tworzy plik heksadecymalny (dokładną lokalizację pliku można zobaczyć, włączając tryb szczegółowy w ustawieniach). Możesz przesłać ten heks do AVR za pomocą swojego ulubionego programisty.

Mam nadzieję, że to pomoże

superkeci
źródło