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
?
timers
programming
Facet z kapeluszem
źródło
źródło
Odpowiedzi:
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
TCCR1B
etc ... 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:
Tak więc, na przykład,
TIMSK1 |= (1 << TOIE1);
ustawia bitTOIE1
wTIMSK1
. Osiąga się to poprzez przesunięcie binarnego 1 (0b00000001
) w lewo oTOIE1
bity, przyTOIE1
czym w pliku nagłówkowym jest on zdefiniowany jako 0. To jest następnie bitowo ORedowane do bieżącej wartościTIMSK1
, która skutecznie ustawia ten jeden bit wysoko.Patrząc na dokumentację bitu 0
TIMSK1
, widzimy, że jest opisany jakoWszystkie 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);
lubTIMSK1 &= ~_BV(TOIE1);
. Ma to tę funkcję przeciwieństwemTIMSK1 |= _BV(TOIE1);
, w tym, że unsets bituTOIE1
wTIMSK1
. Osiąga się to poprzez pobranie maski bitowej wykonanej przez_BV(TOIE1)
wykonanie na niej bitowej operacji NOT (~
), a następnie ORAZTIMSK1
poprzez 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:
PORTC
jest rejestrem kontrolującym wartość pinów wyjściowych wPORTC
ATmega328P.PINC
jest rejestrem, w którym dostępne są wartości wejściowePORTC
. Zasadniczo takie rzeczy dzieją się wewnętrznie podczas korzystania z funkcjidigitalWrite
lubdigitalRead
. 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.źródło
TCCR1A
jest rejestrem sterującym timera / licznika 1 ATCCR1B
to rejestr sterujący timera / licznika 1 BTCNT1
jest wartością licznika timera / licznika 1CS12
jest trzecim bitem wyboru zegara dla timera / licznika 1TIMSK1
jest rejestrem maski przerwania licznika / licznika 1TOIE1
to zezwolenie na przerwanie w przepełnieniu timera / licznika 1Zatem 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)
źródło
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.
źródło