Ini tidak terlihat aneh. Seperti apa sebenarnya kode MCU yang sebenarnya.
Apa yang Anda miliki di sini adalah contoh konsep perangkat yang dipetakan dengan memori . Pada dasarnya, perangkat keras MCU memiliki lokasi khusus di ruang alamat SRAM MCU yang ditetapkan untuknya. Jika Anda menulis ke alamat-alamat ini, bit-bit byte yang ditulis untuk mengatasi dan mengontrol perilaku periferal m .
Pada dasarnya, bank memori tertentu secara harfiah memiliki sedikit kabel yang berjalan dari sel SRAM ke perangkat keras. Jika Anda menulis "1" untuk bit ini dalam byte itu, itu mengatur sel SRAM ini ke logis tinggi, yang kemudian menghidupkan beberapa bagian dari perangkat keras.
Jika Anda melihat ke header untuk MCU, ada tabel besar kata kunci <-> pemetaan alamat. Ini adalah bagaimana hal-hal seperti TCCR1B
dll ... diselesaikan pada waktu kompilasi.
Mekanisme pemetaan memori ini sangat luas digunakan dalam MCU. ATmega MCU di arduino menggunakannya, seperti halnya PIC, ARM, MSP430, STM32 dan STM8 seri MCU, serta banyak MCU yang tidak segera saya kenal.
Kode Arduino adalah hal yang aneh, dengan fungsi yang mengakses register kontrol MCU secara tidak langsung. Walaupun ini terlihat "lebih bagus", tetapi juga lebih lambat, dan menggunakan lebih banyak ruang program.
Konstanta misterius semuanya dijelaskan dengan sangat rinci dalam lembar data ATmega328P , yang harus Anda baca jika Anda tertarik untuk melakukan sesuatu lebih dari sesekali mengaktifkan pin pada arduino.
Pilih kutipan dari lembar data yang ditautkan di atas:
Jadi, misalnya, TIMSK1 |= (1 << TOIE1);
menetapkan bit TOIE1
di TIMSK1
. Ini dicapai dengan menggeser biner 1 ( 0b00000001
) ke kiri oleh TOIE1
bit, dengan TOIE1
didefinisikan dalam file header sebagai 0. Ini kemudian bitwise ORed ke nilai saat ini TIMSK1
, yang secara efektif mengatur bit ini tinggi.
Melihat dokumentasi untuk bit 0 of TIMSK1
, kita dapat melihatnya digambarkan sebagai
Ketika bit ini ditulis ke satu, dan bendera-I di Status Register diatur (interupsi diaktifkan secara global), interupsi Timer / Counter1 Overflow diaktifkan. Vektor Interupsi yang sesuai (Lihat "Interupsi" pada halaman 57) dieksekusi ketika Bendera TOV1, yang terletak di TIFR1, diatur.
Semua baris lainnya harus ditafsirkan dengan cara yang sama.
Beberapa catatan:
Anda mungkin juga melihat hal-hal seperti TIMSK1 |= _BV(TOIE1);
. _BV()
adalah makro yang umum digunakan berasal dari implementasi libc AVR . _BV(TOIE1)
secara fungsional identik dengan (1 << TOIE1)
, dengan manfaat keterbacaan yang lebih baik.
Anda juga dapat melihat garis-garis seperti: TIMSK1 &= ~(1 << TOIE1);
atau TIMSK1 &= ~_BV(TOIE1);
. Ini memiliki fungsi kebalikan dari TIMSK1 |= _BV(TOIE1);
, dalam hal ini unsets sedikit TOIE1
di TIMSK1
. Ini dicapai dengan mengambil bit-mask yang dihasilkan oleh _BV(TOIE1)
, melakukan operasi bitwise NOT di atasnya ( ~
), dan kemudian TIMSK1
ANDing dengan nilai NOTed ini (yaitu 0b11111110).
Perhatikan bahwa dalam semua kasus ini, nilai hal-hal seperti (1 << TOIE1)
atau _BV(TOIE1)
sepenuhnya diselesaikan pada waktu kompilasi , sehingga secara fungsional berkurang menjadi konstanta sederhana, dan oleh karena itu tidak memerlukan waktu eksekusi untuk menghitung pada saat runtime.
Kode yang ditulis dengan benar umumnya akan memiliki komentar sesuai dengan kode yang merinci apa yang ditugaskan register untuk dilakukan. Ini adalah soft-SPI rutin yang cukup sederhana yang saya tulis baru-baru ini:
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
adalah register yang mengontrol nilai pin output dalam PORTC
ATmega328P. PINC
adalah register di mana masukan nilai-nilai PORTC
yang tersedia. Pada dasarnya, hal-hal seperti ini adalah apa yang terjadi secara internal ketika Anda menggunakan digitalWrite
atau digitalRead
fungsinya. Namun, ada operasi pencarian yang mengubah "nomor pin" Arduino menjadi nomor pin perangkat keras yang sebenarnya, yang mengambil tempat dalam ranah siklus 50 jam. Seperti yang mungkin bisa Anda tebak, jika Anda mencoba melaju kencang, membuang 50 siklus clock pada operasi yang hanya membutuhkan 1 adalah sedikit konyol.
Fungsi di atas mungkin membutuhkan tempat di ranah siklus 100-200 jam untuk mentransfer 8 bit. Ini mencakup 24 pin-menulis, dan 8 membaca. Ini banyak, berkali-kali lebih cepat daripada menggunakan digital{stuff}
fungsi.