Czy ktoś mógłby wyjaśnić ten dziwnie wyglądający kod, używany do konfigurowania timerów?


10

Patrząc na szkice napisane przez innych ludzi, od czasu do czasu natrafiam na kod, który wygląda mniej więcej tak:

TCCR1A = 0;
TCCR1B = 0;

TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);

Wiem tylko, że ma to coś wspólnego z timingiem / timerami (tak myślę). Jak mogę rozszyfrować i stworzyć taki kod? Jakie są TCCR1A, TCCR1B, TCNT1, CS12, TIMSK1, i TOIE1?


Nie wiem wystarczająco dużo, aby odpowiedzieć, ale: electronics.stackexchange.com/questions/92350/... , forum.arduino.cc/index.php?topic=134602.0 i stackoverflow.com/questions/9475482/... . Nie wiem, czy już je widziałeś.
Anonimowy pingwin

1
Pobierz „Kompletny” arkusz danych dla swojego urządzenia ze strony internetowej Atmel i przeczytaj rozdziały na temat timerów. Arkusz danych jest moim zdaniem zaskakująco dobry do przeczytania.
jippie

Odpowiedzi:


15

To nie jest dziwnie wyglądające. Tak naprawdę wygląda normalny kod MCU.

Masz tutaj przykład koncepcji urządzeń peryferyjnych mapowanych w pamięć . Zasadniczo sprzęt MCU ma przypisane specjalne miejsca w przestrzeni adresowej SRAM MCU. Jeśli piszesz na te adresy, bity bajtu zapisane na adres n kontrolują zachowanie peryferyjnego m .

Zasadniczo niektóre banki pamięci dosłownie mają małe przewody biegnące od komórki SRAM do sprzętu. Jeśli zapiszesz „1” do tego bitu w tym bajcie, ustawia on tę komórkę SRAM na logiczną wartość wysoką, która następnie włącza pewną część sprzętu.

Jeśli zajrzysz do nagłówków MCU, znajdziesz świetne duże tabele mapowań słów kluczowych <->. Oto jak rzeczy takie jak TCCR1Betc ... są rozwiązywane w czasie kompilacji.

Ten mechanizm mapowania pamięci jest niezwykle szeroko stosowany w MCU. Wykorzystują go MCU ATmega w arduino, podobnie jak MCU serii PIC, ARM, MSP430, STM32 i STM8, a także wiele MCU, których od razu nie znam.


Kod Arduino to dziwna rzecz, z funkcjami, które pośrednio uzyskują dostęp do rejestrów kontrolnych MCU. Chociaż jest to nieco „ładniejsze”, jest również znacznie wolniejsze i zajmuje dużo więcej miejsca na program.

Tajemnicze stałe są szczegółowo opisane w arkuszu danych ATmega328P , który naprawdę powinieneś przeczytać, jeśli chcesz zrobić coś więcej niż okazjonalnie przełączać szpilki na arduino.

Wybierz fragmenty z arkusza danych połączonego powyżej:

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Tak więc, na przykład, TIMSK1 |= (1 << TOIE1);ustawia bit TOIE1w TIMSK1. Osiąga się to poprzez przesunięcie binarnego 1 ( 0b00000001) w lewo o TOIE1bity, przy TOIE1czym w pliku nagłówkowym jest on zdefiniowany jako 0. To jest następnie bitowo ORedowane do bieżącej wartości TIMSK1, która skutecznie ustawia ten jeden bit wysoko.

Patrząc na dokumentację bitu 0 TIMSK1, widzimy, że jest opisany jako

Gdy ten bit zostanie zapisany na jeden i ustawiona zostanie flaga I w Rejestrze statusu (przerwania włączone globalnie), przerwanie Przelewu Licznika / Licznika 1 jest włączone. Odpowiedni wektor przerwań (patrz „Przerwania” na stronie 57) jest wykonywany, gdy ustawiona jest flaga TOV1, znajdująca się w TIFR1.

Wszystkie pozostałe wiersze należy interpretować w ten sam sposób.


Niektóre uwagi:

Możesz także zobaczyć takie rzeczy TIMSK1 |= _BV(TOIE1);. _BV()jest często używanym makrem pochodzącym z implementacji AVR libc . _BV(TOIE1)jest funkcjonalnie identyczny z (1 << TOIE1), z korzyścią lepszej czytelności.

Możesz także zobaczyć linie takie jak: TIMSK1 &= ~(1 << TOIE1);lub TIMSK1 &= ~_BV(TOIE1);. Ma to tę funkcję przeciwieństwem TIMSK1 |= _BV(TOIE1);, w tym, że unsets bitu TOIE1w TIMSK1. Osiąga się to poprzez pobranie maski bitowej wykonanej przez _BV(TOIE1)wykonanie na niej bitowej operacji NOT ( ~), a następnie ORAZ TIMSK1poprzez tę UWAGOWANĄ wartość (która jest 0b11111110).

Zauważ, że we wszystkich tych przypadkach wartość rzeczy takich jak (1 << TOIE1)lub _BV(TOIE1)jest w pełni ustalona w czasie kompilacji , więc funkcjonalnie zmniejszają się do prostej stałej, a zatem nie wymagają czasu wykonania na obliczenie w czasie wykonywania.


Prawidłowo napisany kod będzie zazwyczaj zawierał komentarze zgodne z kodem, które szczegółowo określają, do czego mają zostać przypisane rejestry. Oto dość prosta procedura soft-SPI, którą ostatnio napisałem:

uint8_t transactByteADC(uint8_t outByte)
{
    // Transfers one byte to the ADC, and receives one byte at the same time
    // does nothing with the chip-select
    // MSB first, data clocked on the rising edge

    uint8_t loopCnt;
    uint8_t retDat = 0;

    for (loopCnt = 0; loopCnt < 8; loopCnt++)
    {
        if (outByte & 0x80)         // if current bit is high
            PORTC |= _BV(ADC_MOSI);     // set data line
        else
            PORTC &= ~(_BV(ADC_MOSI));  // else unset it

        outByte <<= 1;              // and shift the output data over for the next iteration
        retDat <<= 1;               // shift over the data read back

        PORTC |= _BV(ADC_SCK);          // Set the clock high

        if (PINC & _BV(ADC_MISO))       // sample the input line
            retDat |= 0x01;         // and set the bit in the retval if the input is high

        PORTC &= ~(_BV(ADC_SCK));       // set clock low
    }
    return retDat;
}

PORTCjest rejestrem kontrolującym wartość pinów wyjściowych w PORTCATmega328P. PINCjest rejestrem, w którym dostępne są wartości wejściowePORTC . Zasadniczo takie rzeczy dzieją się wewnętrznie podczas korzystania z funkcji digitalWritelub digitalRead. Istnieje jednak operacja wyszukiwania, która przekształca arduino „numery pinów” w rzeczywiste numery pinów sprzętowych, co zabiera gdzieś w sferę 50 cykli zegara. Jak zapewne można się domyślić, jeśli próbujesz iść szybko, marnowanie 50 cykli zegara na operację, która powinna wymagać tylko 1, jest nieco niedorzeczne.

Powyższa funkcja prawdopodobnie przenosi 8 bitów gdzieś w dziedzinie 100-200 cykli zegara. Oznacza to 24 zapisy pinowe i 8 odczytów. Jest to wiele, wiele razy szybsze niż korzystanie z digital{stuff}funkcji.


Pamiętaj, że ten kod powinien również działać z Atmega32u4 (używanym w Leonardo), ponieważ zawiera więcej timerów niż ATmega328P.
jfpoilpret

1
@ Ricardo - Prawdopodobnie ponad 90% + kodu osadzonego na małym MCU wykorzystuje bezpośrednią manipulację rejestrem. Robienie rzeczy z pośrednimi funkcjami narzędziowymi nie jest powszechnym trybem manipulacji IO / urządzeniami peryferyjnymi. Istnieje kilka zestawów narzędzi do wyodrębniania kontroli sprzętu (na przykład Atmel ASF), ale ogólnie jest to napisane, aby skompilować tak dużo, jak to możliwe, aby zmniejszyć obciążenie środowiska wykonawczego, i prawie niezmiennie wymaga faktycznego zrozumienia urządzeń peryferyjnych poprzez czytanie arkuszy danych.
Connor Wolf

1
Zasadniczo, arduino, mówiąc „tutaj są funkcje, które wykonują X”, bez większego trudu odwoływania się do faktycznej dokumentacji lub tego, jak sprzęt robi to, co robi, nie jest normalne. Rozumiem, że jest to wartość narzędzia wprowadzającego, ale poza szybkim prototypowaniem nigdy tak naprawdę nie odbywa się w profesjonalnych środowiskach.
Connor Wolf

1
Żeby było jasne, to, co sprawia, że ​​kod arduino jest niezwykły dla wbudowanego oprogramowania MCU, nie jest unikalny dla kodu arduino, jest to funkcja ogólnego podejścia. Zasadniczo, gdy już dobrze rozumiesz rzeczywisty MCU, robienie rzeczy właściwie (np. Bezpośrednie używanie rejestrów sprzętowych) zajmuje niewiele lub nie ma dodatkowego czasu. Jako takie, jeśli chcesz dowiedzieć się prawdziwy MCU dev, to o wiele lepiej po prostu usiąść i zrozumieć, co MCU jest faktycznie robi, a następnie opierając się na kogoś innego abstrakcji, która wydaje się być nieszczelne.
Connor Wolf

1
Zauważ, że mogę być trochę cyniczny, ale wiele zachowań, które widzę w społeczności arduino, programuje anty-wzorce. Widzę wiele programów do kopiowania i wklejania, traktowania bibliotek jako czarnych skrzynek i po prostu ogólnych złych praktyk projektowych w całej społeczności. Oczywiście jestem dość aktywny na EE.stackexchange, więc mogę mieć nieco pochylony widok, ponieważ mam narzędzia moderatora i jako takie widzę wiele zamkniętych pytań. W arduino pytania, które tam widziałem, jest zdecydowanie stronnicze w stosunku do „powiedz mi, co C&P naprawić”, a nie „dlaczego to nie działa”.
Connor Wolf

3

TCCR1A jest rejestrem sterującym timera / licznika 1 A

TCCR1B to rejestr sterujący timera / licznika 1 B

TCNT1 jest wartością licznika timera / licznika 1

CS12 jest trzecim bitem wyboru zegara dla timera / licznika 1

TIMSK1 jest rejestrem maski przerwania licznika / licznika 1

TOIE1 to zezwolenie na przerwanie w przepełnieniu timera / licznika 1

Zatem kod włącza timer / licznik 1 przy 62,5 kHz i ustawia wartość na 34286. Następnie włącza przerwanie przepełnienia, więc gdy osiągnie 65535, uruchomi funkcję przerwania, najprawdopodobniej oznaczoną jako ISR(timer0_overflow_vect)


1

CS12 ma wartość 2, ponieważ reprezentuje bit 2 rejestru TCCR1B.

(1 << CS12) przyjmuje wartość 1 (0b00000001) i przesuwa ją 2 razy w lewo, aby uzyskać (0b00000100). Kolejność operacji narzuca, że ​​rzeczy w () zdarzają się najpierw, więc dzieje się to przed obliczeniem „| =”.

(1 << CS10) przyjmuje wartość 1 (0b00000001) i przesuwa ją w lewo 0 razy, aby uzyskać (0b00000001). Kolejność operacji narzuca, że ​​rzeczy w () zdarzają się najpierw, więc dzieje się to przed obliczeniem „| =”.

Więc teraz otrzymujemy TCCR1B | = 0b00000101, co jest takie samo jak TCCR1B = TCCR1B | 0b00000101.

Ponieważ „|” oznacza „LUB”, wszystkie bity inne niż CS12 w TCCR1B pozostają nienaruszone.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.