Saya mengembangkan penganalisis logika kecil dengan 7 input. Perangkat target saya adalah ATmega168
dengan clock rate 20MHz. Untuk mendeteksi perubahan logika saya menggunakan interupsi perubahan pin. Sekarang saya mencoba mencari tahu laju sampel terendah yang dapat saya deteksi perubahan pin ini. Saya menentukan nilai minimum 5,6 μs (178,5 kHz). Setiap sinyal di bawah angka ini saya tidak bisa menangkap dengan benar.
Kode saya ditulis dalam C (avr-gcc). Rutinitas saya terlihat seperti:
ISR()
{
pinc = PINC; // char
timestamp_ll = TCNT1L; // char
timestamp_lh = TCNT1H; // char
timestamp_h = timerh; // 2 byte integer
stack_counter++;
}
Perubahan sinyal yang saya tangkap terletak di pinc
. Untuk melokalkannya saya memiliki nilai cap waktu yang panjang 4 byte.
Dalam datasheet saya membaca layanan rutin interupsi membutuhkan 5 jam untuk melompat dan 5 jam untuk kembali ke prosedur utama. Saya mengasumsikan setiap perintah di saya ISR()
mengambil 1 jam untuk dieksekusi; Jadi singkatnya harus ada overhead 5 + 5 + 5 = 15
jam. Durasi satu jam harus sesuai dengan laju jam 20MHz 1/20000000 = 0.00000005 = 50 ns
. Total overhead dalam detik harus kemudian: 15 * 50 ns = 750 ns = 0.75 µs
. Sekarang saya tidak mengerti mengapa saya tidak bisa menangkap apa pun di bawah 5,6 μs. Adakah yang bisa menjelaskan apa yang terjadi?
Jawaban:
Ada beberapa masalah:
AND
adalah instruksi satu jam,MUL
(berlipat ganda) membutuhkan dua jam, sedangkanLPM
(memuat memori program) adalah tiga, danCALL
adalah 4. Jadi, sehubungan dengan pelaksanaan instruksi, itu benar-benar tergantung pada instruksi.RETI
instruksi, kompiler menambahkan semua jenis kode lain, yang juga membutuhkan waktu. Misalnya Anda mungkin perlu variabel lokal yang dibuat di stack dan harus dibuka, dll. Hal terbaik yang harus dilakukan untuk melihat apa yang sebenarnya terjadi adalah dengan melihat pembongkaran.Jika
x
waktu yang dibutuhkan untuk memperbaiki interupsi Anda, maka sinyal B tidak akan pernah ditangkap.Jika kami mengambil kode ISR Anda, masukkan ke dalam rutin ISR (saya menggunakan
ISR(PCINT0_vect)
) rutin, deklarasikan semua variabelvolatile
, dan kompilasi untuk ATmega168P, kode yang dibongkar terlihat seperti berikut (lihat jawaban @ jipple untuk info lebih lanjut) sebelum kita sampai ke kode bahwa "melakukan sesuatu" ; dengan kata lain prolog ke ISR Anda adalah sebagai berikut:jadi,
PUSH
x 5,in
x 1,clr
x 1. Tidak seburuk vars 32-bit jipple, tapi masih tidak ada apa-apanya.Sebagian dari ini perlu (perluas diskusi dalam komentar). Jelas, karena rutin ISR dapat terjadi kapan saja, ia harus mempra-register terlebih dahulu register yang digunakannya, kecuali Anda tahu bahwa tidak ada kode di mana interupsi dapat terjadi menggunakan register yang sama dengan rutin interupsi Anda. Misalnya baris berikut dalam ISR yang dibongkar:
Apakah ada karena semuanya berjalan
r24
: Andapinc
dimuat di sana sebelum masuk ke memori, dll. Jadi Anda harus memilikinya terlebih dahulu.__SREG__
dimasukkan ke dalamr0
dan kemudian didorong: jika ini bisa melewatir24
maka Anda bisa menyelamatkan diri Anda sendiri aPUSH
Beberapa solusi yang mungkin:
ISR_NAKED
, gcc tidak menghasilkan kode prolog / epilog, dan Anda bertanggung jawab untuk menyimpan register apa pun yang dimodifikasi oleh kode Anda, serta meneleponreti
(kembali dari interupsi). Sayangnya, tidak ada cara menggunakan register di avr-gcc C langsung (jelas Anda dapat dalam perakitan), namun, apa yang dapat Anda lakukan adalah variabel mengikat untuk register tertentu denganregister
+asm
kata kunci, seperti ini:register uint8_t counter asm("r3");
. Jika Anda melakukannya, untuk ISR Anda akan tahu register apa yang Anda gunakan di ISR. Masalahnya kemudian adalah bahwa tidak ada cara untuk menghasilkanpush
danpop
untuk menyimpan register yang digunakan tanpa perakitan inline (lih. poin 1). Untuk memastikan Anda harus menyimpan lebih sedikit register, Anda juga dapat mengikat semua variabel non-ISR ke register spesifik, namun, tidak, Anda tidak mengalami masalah yang menggunakan register gcc untuk mengocok data ke dan dari memori. Ini berarti bahwa kecuali Anda melihat pembongkaran, Anda tidak akan tahu register apa yang digunakan kode utama Anda. Jadi jika Anda mempertimbangkanISR_NAKED
, Anda mungkin juga menulis ISR dalam pertemuan.sumber
Ada banyak register PUSH'ing dan POP'ing untuk ditumpuk terjadi sebelum ISR Anda yang sebenarnya dimulai, yaitu di atas siklus 5 jam yang Anda sebutkan. Lihatlah pembongkaran kode yang dihasilkan.
Bergantung pada rantai alat yang Anda gunakan, membuang daftar yang kami lakukan dengan berbagai cara. Saya bekerja pada baris perintah Linux dan ini adalah perintah yang saya gunakan (membutuhkan file .elf sebagai input):
Lihatlah sniplet kode yang baru-baru ini saya gunakan untuk ATtiny. Beginilah bentuk kode-C:
Dan ini adalah kode perakitan yang dihasilkan untuk itu:
Sejujurnya, rutin C saya menggunakan beberapa variabel lebih banyak yang menyebabkan semua push dan pop ini, tetapi Anda mendapatkan idenya.
Memuat variabel 32 bit terlihat seperti ini:
Meningkatkan variabel 32 bit dengan 1 terlihat seperti ini:
Menyimpan variabel 32-bit terlihat seperti ini:
Maka tentu saja Anda harus memunculkan nilai-nilai lama setelah Anda meninggalkan ISR:
Menurut ringkasan instruksi dalam lembar data, sebagian besar instruksi adalah siklus tunggal, tetapi PUSH dan POP adalah siklus ganda. Anda mendapat ide dari mana keterlambatan itu berasal?
sumber
avr-objdump -C -d $(src).elf
!avr-objdump
dimuntahkan, mereka dijelaskan secara singkat dalam lembar data di bawah Instruksi Ringkasan. Menurut pendapat saya itu adalah praktik yang baik untuk membiasakan diri dengan mnemonik karena dapat banyak membantu ketika men-debug kode C Anda.Makefile
: jadi setiap kali Anda membangun proyek Anda dibongkar secara otomatis juga sehingga Anda tidak perlu memikirkannya, atau ingat bagaimana melakukannya secara manual.