Bisakah seseorang menjelaskan kode yang terlihat aneh ini, yang digunakan untuk mengatur timer?

10

Sambil melihat sketsa yang ditulis orang lain, saya terkadang menemukan kode yang terlihat seperti ini:

TCCR1A = 0;
TCCR1B = 0;

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

Yang saya tahu adalah bahwa ada sesuatu dengan timing / timer (saya pikir). Bagaimana saya bisa menguraikan — dan membuat — kode seperti ini? Apa TCCR1A, TCCR1B, TCNT1, CS12, TIMSK1, dan TOIE1?

The Guy with The Hat
sumber
Tidak cukup tahu untuk menjawab tetapi: electronics.stackexchange.com/questions/92350/… , forum.arduino.cc/index.php?topic=134602.0 , dan stackoverflow.com/questions/9475482/… . Tidak tahu apakah Anda sudah melihatnya.
Penguin Anonim
1
Unduh lembar data "Lengkap" untuk perangkat Anda dari situs web Atmel dan baca bab tentang penghitung waktu. Lembar data secara mengejutkan baik untuk dibaca menurut saya.
jippie

Jawaban:

15

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 TCCR1Bdll ... 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:

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Jadi, misalnya, TIMSK1 |= (1 << TOIE1);menetapkan bit TOIE1di TIMSK1. Ini dicapai dengan menggeser biner 1 ( 0b00000001) ke kiri oleh TOIE1bit, dengan TOIE1didefinisikan 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 TOIE1di TIMSK1. Ini dicapai dengan mengambil bit-mask yang dihasilkan oleh _BV(TOIE1), melakukan operasi bitwise NOT di atasnya ( ~), dan kemudian TIMSK1ANDing 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;
}

PORTCadalah register yang mengontrol nilai pin output dalam PORTCATmega328P. PINCadalah register di mana masukan nilai-nilai PORTCyang tersedia. Pada dasarnya, hal-hal seperti ini adalah apa yang terjadi secara internal ketika Anda menggunakan digitalWriteatau digitalReadfungsinya. 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.

Connor Wolf
sumber
Perhatikan bahwa kode ini juga harus bekerja dengan Atmega32u4 (digunakan dalam Leonardo) karena mengandung lebih banyak timer daripada ATmega328P.
jfpoilpret
1
@ Ricardo - Mungkin 90% + kode tertanam MCU kecil menggunakan manipulasi register langsung. Melakukan hal-hal dengan fungsi utilitas tidak langsung bukanlah mode yang umum untuk memanipulasi IO / peripheral. Ada beberapa toolkit untuk mengabstraksi kontrol perangkat keras (The Atmel ASF, misalnya), tetapi itu umumnya ditulis untuk mengkompilasi sebanyak mungkin untuk mengurangi overhead runtime, dan hampir selalu membutuhkan pemahaman periferal dengan membaca lembar data.
Connor Wolf
1
Pada dasarnya, hal Arduino, dengan mengatakan "di sini adalah fungsi yang melakukan X", tanpa benar-benar repot untuk referensi dokumentasi yang sebenarnya atau bagaimana perangkat keras melakukan hal-hal yang dilakukannya, sangat tidak normal. Saya mengerti nilainya sebagai alat pengantar, tetapi kecuali untuk prototyping cepat, itu tidak pernah dilakukan dalam lingkungan profesional yang sebenarnya.
Connor Wolf
1
Untuk menjadi jelas, hal yang membuat kode Arduino tidak biasa untuk firmware MCU tertanam tidak unik untuk kode Arduino, itu adalah fungsi dari pendekatan keseluruhan. Pada dasarnya, setelah Anda memiliki pemahaman yang layak tentang MCU yang sebenarnya , melakukan hal-hal dengan benar (mis. Menggunakan register perangkat keras secara langsung) tidak membutuhkan banyak waktu. Karena itu, jika Anda ingin mempelajari dev MCU yang sebenarnya, lebih baik duduk dan memahami apa yang sebenarnya dilakukan MCU Anda , daripada mengandalkan abstraksi orang lain , yang cenderung bocor.
Connor Wolf
1
Perhatikan bahwa saya mungkin sedikit sinis di sini, tetapi banyak perilaku yang saya lihat di komunitas arduino memprogram anti-pola. Saya melihat banyak pemrograman "copy-paste", memperlakukan perpustakaan sebagai kotak hitam, dan hanya praktik desain umum yang buruk di masyarakat luas. Tentu saja, saya cukup aktif di EE.stackexchange, jadi saya mungkin memiliki pandangan yang agak miring, karena saya memiliki beberapa alat moderator, dan karena itu melihat banyak pertanyaan tertutup. Pasti ada bias dalam pertanyaan Arduino yang saya lihat di sana terhadap "katakan padaku apa yang harus diperbaiki C&P", alih-alih "mengapa ini tidak berhasil".
Connor Wolf
3

TCCR1A adalah timer / penghitung 1 register kontrol A

TCCR1B adalah timer / penghitung 1 register kontrol B

TCNT1 adalah nilai penghitung waktu / penghitung 1

CS12 adalah bit pilih jam ke-3 untuk timer / penghitung 1

TIMSK1 adalah register topeng interupsi timer / counter 1

TOIE1 apakah timer / counter 1 overflow interrupt diaktifkan

Jadi, kode ini mengaktifkan timer / penghitung 1 pada 62,5 kHz dan menetapkan nilai ke 34286. Kemudian memungkinkan overflow interupsi sehingga ketika mencapai 65535, itu akan memicu fungsi interupsi, kemungkinan besar dilabeli sebagai ISR(timer0_overflow_vect)

Dokter
sumber
1

CS12 memiliki nilai 2 karena mewakili bit 2 dari register TCCR1B.

(1 << CS12) mengambil nilai 1 (0b00000001) dan menggesernya 2 kali untuk mendapatkan (0b00000100). Urutan operasi menentukan bahwa hal-hal dalam () terjadi terlebih dahulu, jadi ini dilakukan sebelum "| =" dievaluasi.

(1 << CS10) mengambil nilai 1 (0b00000001) dan menggesernya 0 kali untuk mendapatkan (0b00000001). Urutan operasi menentukan bahwa hal-hal dalam () terjadi terlebih dahulu, jadi ini dilakukan sebelum "| =" dievaluasi.

Jadi sekarang kita mendapatkan TCCR1B | = 0b00000101, yang sama dengan TCCR1B = TCCR1B | 0b00000101.

Sejak "|" adalah "ATAU", semua bit selain CS12 di TCCR1B tidak terpengaruh.

VENKATESAN
sumber