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 bit TOIE1
w TIMSK1
. Osiąga się to poprzez przesunięcie binarnego 1 ( 0b00000001
) w lewo o TOIE1
bity, przy TOIE1
czym 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 TOIE1
w TIMSK1
. Osiąga się to poprzez pobranie maski bitowej wykonanej przez _BV(TOIE1)
wykonanie na niej bitowej operacji NOT ( ~
), a następnie ORAZ TIMSK1
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:
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;
}
PORTC
jest rejestrem kontrolującym wartość pinów wyjściowych w PORTC
ATmega328P. PINC
jest rejestrem, w którym dostępne są wartości wejściowePORTC
. Zasadniczo takie rzeczy dzieją się wewnętrznie podczas korzystania z funkcji digitalWrite
lub 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.