← Bagian 1. Kenalan pertama
Bagian 3. Pengalamatan tidak langsung dan kontrol aliran →
Perpustakaan Generator Kode Assembler untuk Mikrokontroler AVR
Bagian 2. Memulai
Seperti yang direncanakan, pada bagian ini kami akan mempertimbangkan secara lebih rinci fitur pemrograman menggunakan perpustakaan NanoRTOS. Mereka yang mulai membaca dari posting ini dapat membiasakan diri dengan deskripsi umum dan kemampuan perpustakaan di artikel sebelumnya . Karena ruang lingkup terbatas dari publikasi yang direncanakan, diasumsikan bahwa pembaca yang dihormati setidaknya minimal akrab dengan pemrograman C #, dan juga memiliki pemahaman tentang arsitektur dan pemrograman dalam bahasa assembly untuk pengendali AVR seri Mega.
Studi tentang teknologi apa pun paling baik dikombinasikan dengan pelaksanaan contoh, jadi saya sarankan mengunduh perpustakaan itu sendiri dari https://drive.google.com/open?id=1FfBBpxlJkWC027ikYpn6NXbOGp7DS-5B dan mencoba menghubungkannya ke proyek aplikasi konsol. Jika berhasil, Anda dapat melanjutkan dengan aman. Anda dapat menggunakan aplikasi C # atau proyek UnitTest apa pun sebagai shell untuk mengeksekusi contoh. Saya pribadi lebih suka yang terakhir, karena memungkinkan Anda untuk menyimpan beberapa contoh berbeda di satu tempat dan menjalankannya sesuai kebutuhan. Bagaimanapun, hanya teks dari contoh akan dimasukkan dalam daftar untuk menghemat ruang.
Bekerja dengan perpustakaan harus selalu dimulai dengan pengumuman seperti mikrokontroler. Karena parameter dan rangkaian perangkat periferal bergantung pada jenis pengontrol, pilihan pengontrol tertentu memengaruhi pembentukan kode assembler. Baris deklarasi pengontrol yang menjadi dasar penulisan program adalah sebagai berikut
var m = new Mega328();
Selanjutnya, pengaturan tambahan mikrokontroler dapat mengikuti, seperti parameter jam atau penugasan fungsi sistem untuk output. Sebagai contoh, izin untuk menggunakan pengaturan ulang perangkat keras menghilangkan penggunaan output sebagai port. Semua parameter pengontrol memiliki nilai default, dan dalam contoh kami akan menghilangkannya, kecuali jika itu penting, tetapi dalam proyek nyata saya menyarankan Anda untuk selalu menginstalnya. Misalnya, pengaturan jam mungkin terlihat seperti ini
m.FCLK = 16000000; m.CKDIV8 = false;
Pengaturan ini berarti mikrokontroler diatur oleh resonator kuarsa atau sumber eksternal dengan frekuensi 16 MHz, dan pembagi frekuensi untuk perangkat periferal dimatikan.
Fungsi Teks dari kelas statis AVRASM bertanggung jawab atas hasil karya. Fungsi ini akan selalu dipanggil di akhir kode untuk menampilkan hasilnya dalam bentuk assembler. Instance yang sebelumnya ditugaskan dari kelas controller, fungsi menerima sebagai parameter. Jadi, kerangka kerja paling sederhana dari program untuk bekerja dengan perpustakaan mengambil bentuk berikut
var m = new Mega328();
Jika kami mencoba menjalankan program, itu harus berhasil, tetapi tidak akan menghasilkan kode apa pun. Terlepas dari kesia-siaan hasilnya, ini tetap memberikan alasan untuk menyimpulkan bahwa perpustakaan tidak menghasilkan kode pembungkus.
Kami telah belajar cara membuat program kosong. Sekarang mari kita coba membuat beberapa kode di dalamnya. Mari kita mulai dengan yang paling primitif. Mari kita lihat bagaimana kita bisa menyelesaikan masalah kenaikan variabel delapan-bit yang terletak di sel RON yang berubah-ubah. Dari sudut pandang assembler, ini adalah perintah [register] inc . Masukkan baris berikut ke dalam tubuh program pengadaan kami
var r = m.REG(); r++;
Tujuan tim cukup jelas. Perintah pertama mengaitkan variabel r dengan salah satu register prosesor. Perintah kedua berbicara tentang perlunya meningkatkan variabel ini. Setelah eksekusi, kami mendapatkan hasil pertama dari eksekusi kode.
RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 inc R0000 .DSEG
Mari kita melihat lebih dekat apa yang terjadi sebagai hasilnya. Empat perintah pertama adalah inisialisasi dari penunjuk tumpukan. Berikutnya adalah definisi nama variabel. Dan akhirnya, inc kami, yang semuanya dimulai. Tidak ada yang ekstra, kecuali untuk inisialisasi tumpukan tidak muncul. Pertanyaan yang mungkin timbul ketika melihat kode ini adalah R0000 seperti apa? Kami memiliki variabel bernama r ? Dalam program C #, seorang programmer dapat secara sadar dan legal menggunakan nama yang sama, menggunakan cakupan. Dari sudut pandang assembler, semua label dan definisi harus unik. Agar tidak memaksa programmer untuk memantau keunikan nama, secara default nama-nama tersebut dihasilkan oleh sistem. Namun, ada situasi di mana, untuk tujuan debugging, Anda masih ingin mentransfer nama yang disadari dari program ke kode output, sehingga dapat dengan mudah ditemukan. Tidak menakutkan. Ganti m.REG () dengan m.REG (”r”) dan jalankan kembali kodenya. Sebagai hasilnya, kita akan melihat yang berikut ini
RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF r = r20 inc r .DSEG
Jadi, dengan penamaan register beres. Sekarang mari kita lihat mengapa tiba-tiba register mulai ditugaskan dari 20, dan bukan dari 0? Untuk menjawab pertanyaan ini, kami ingat bahwa mulai dari 16, register memiliki peluang besar untuk menginisialisasi mereka dengan konstanta. Dan karena fitur ini sangat diminati, kami memulai distribusi dari bagian atas untuk meningkatkan peluang pengoptimalan. Maka semua sama itu tidak jelas - mengapa c20 dan bukan 16? Alasannya adalah bahwa terjemahan sejumlah perintah ke dalam kode assembler tidak mungkin dilakukan tanpa menggunakan sel-sel penyimpanan sementara. Untuk tujuan ini, kami telah mengalokasikan 4 sel dari 16 hingga 19. Ini tidak berarti bahwa mereka telah menjadi sepenuhnya tidak dapat diakses oleh programmer. Hanya saja akses ke mereka diatur sedikit berbeda sehingga programmer menyadari akan kemungkinan keterbatasan penggunaannya dan bertindak secara sadar. Kami menghapus definisi register r dari kode dan mengganti baris yang mengikutinya
m.TempL ++;
Mari kita lihat hasilnya
RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 inc TempL .DSEG
Di sini, tampaknya, perlu dicatat bahwa assembler output memerlukan interpretasi yang benar dari file koneksi dengan definisi dan macro Common.inc dari paket pengembangan. Ini sebenarnya berisi semua makro dan definisi yang diperlukan, termasuk pencocokan nama untuk sel-sel penyimpanan sementara. Yaitu, TempL = r16, TempH = r17, TempQL = r18, TempQH = r19. Dalam hal ini, kami tidak menggunakan perintah tunggal yang akan menggunakan sel penyimpanan sementara untuk bekerja, jadi keputusan kami untuk menggunakannya dalam operasi TempL cukup dapat diterima. Dan apa yang harus kita lakukan jika kita benar-benar yakin bahwa tidak ada penetapan konstanta pada variabel kita tidak bersinar dan kita tidak ingin menghabiskan sel-sel berharga dari bagian atas di atasnya? Kembalikan definisi kita ke kode sumber dengan mengubahnya ke var r = m.REGL ("r"); dan mengevaluasi hasil persalinan
RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF r = r4 inc r .DSEG
Tujuan tercapai. Kami berhasil menjelaskan ke perpustakaan di mana, menurut pendapat kami, variabel harus ditempatkan. Mari kita melangkah lebih jauh. Mari kita lihat apa yang terjadi jika beberapa variabel dideklarasikan sekaligus. Kami menyalin definisi dan tindakan kami beberapa kali lagi. Untuk perubahan, kami akan mengatur ulang satu variabel baru, dan mengurangi nilai lainnya dengan 1. Hasilnya harus seperti ini.
var m = new Mega328(); var r = m.REGL("r"); r++; var rr = m.REGL("rr"); rr--; var rrr = m.REGL("rrr"); rrr.Clear(); var t = AVRASM.Text(m); . RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF r = r4 inc r .DEF rr = r5 dec rr .DEF rrr = r6 clr rrr .DSEG
Bagus Itulah yang diminta. Sekarang mari kita lihat bagaimana kita dapat membebaskan register untuk keperluan lain jika kita tidak lagi membutuhkannya. Di sini, sayangnya, sejauh ini semuanya dengan tangan. Aturan C # tentang batas visibilitas dan pelepasan variabel otomatis ketika pergi ke luar negeri untuk mode pembuatan kode belum berfungsi. Mari kita lihat bagaimana Anda masih bisa membebaskan sel jika perlu. Tambahkan hanya satu baris ke program kami dan lihat hasilnya.
var m = new Mega328(); var r = m.REGL("r"); r++; var rr = m.REGL("rr"); rr--; r.Dispose(); var rrr = m.REGL("rrr"); rrr.Clear(); var t = AVRASM.Text(m);
RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF r = r4 inc r .DEF rr = r5 dec rr .UNDEF r .DEF rrr = r4 clr rrr .DSEG
Sangat mudah untuk melihat bahwa register keempat yang kami bebaskan tersedia lagi untuk digunakan. Mempertimbangkan fakta bahwa setiap deklarasi variabel baru menangkap register, kita dapat menyimpulkan bahwa ketika menyusun program, Anda perlu membebaskan register tepat waktu jika Anda tidak ingin menghadapi situasi ketika mereka menjadi langka.
Saat menganalisis contoh-contoh, kami telah menunjukkan di sepanjang jalan bagaimana melakukan operasi unicast pada register. Sekarang mari kita lihat bagaimana keadaannya dengan multicast. Arsitektur prosesor memungkinkan maksimum dua alamat instruksi (terutama yang korosif, dengan pengecualian dua instruksi yang hasilnya ditempatkan dalam register tetap). Ini harus dipahami sedemikian rupa sehingga operan pertama dalam operasi setelah eksekusi akan berisi hasilnya. Sintaks khusus [register1] [operasi] = [register2] disediakan untuk jenis operasi ini. Mari kita lihat tampilannya dalam praktik. Mari kita coba mendeklarasikan dan menambahkan dua variabel register.
var m = new Mega328(); var op1 = m.REG(); var op2 = m.REG(); op1 += op2; var t = AVRASM.Text(m);
Hasilnya, kita akan lihat
RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 .DEF R0001 = r21 add R0000,R0001 .DSEG
Mendapat apa yang mereka harapkan. Anda dapat bereksperimen dengan operasi sendiri -, &, | dan pastikan hasilnya tidak lebih buruk.
Sejauh ini, semua hal di atas jelas tidak cukup untuk menulis bahkan program yang paling sederhana. Faktanya adalah bahwa kita belum menyentuh inisialisasi register sendiri. Arsitektur mikrokontroler memungkinkan Anda untuk menginisialisasi register dengan konstanta, nilai register lain, nilai sel memori RAM pada alamat tertentu, nilai sel memori RAM pada penunjuk yang terletak pada pasangan register khusus, nilai sel input / output pada alamat tertentu, serta nilai sel memori program pada penunjuk. ditempatkan di sepasang register khusus. Kami akan menangani penanganan tidak langsung nanti, tetapi untuk saat ini kami akan mempertimbangkan kasus yang lebih sederhana. Kami akan menulis dan menjalankan program pengujian berikut.
var m = new Mega328(); var op1 = m.REG(); var op2 = m.REG(); op1.Load(0x10); op2.Load('s'); op1.Load(op2); var t = AVRASM.Text(m);
Di sini kita mendeklarasikan dan menginisialisasi dua variabel dengan angka dan simbol, dan kemudian kita menyalin nilai variabel op2 ke dalam sel op1. Jelas, jumlahnya harus berada dalam kisaran 0-255 sehingga tidak ada kesalahan terjadi. Hasilnya akan
RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 .DEF R0001 = r21 ldi R0000,16 ldi R0001,'s' mov R0000,R0001 .DSEG
Dapat dilihat dari contoh bahwa untuk semua operasi yang terdaftar satu fungsi digunakan, dan perpustakaan itu sendiri menghasilkan set perintah assembler yang benar. Seperti yang telah disebutkan berulang kali, pemuatan langsung data ke dalam register dengan perintah ldi hanya tersedia untuk bagian atas register. Mari kita buat perpustakaan kita lebih rumit dengan mengubah program sehingga mengalokasikan register untuk variabel dari bagian bawah.
var m = new Mega328(); var op1 = m.REGL(); var op2 = m.REGL(); op1.Load(0x10); op2.Load('s'); op1.Load(op2); var t = AVRASM.Text(m);
Kami mendapatkan
RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r4 .DEF R0001 = r5 ldi TempL,16 mov R0000,TempL ldi TempL,'s' mov R0001,TempL mov R0000,R0001 .DSEG
Perpustakaan mengatasi tugas ini, pada saat yang sama menghabiskan jumlah tim minimum. Dan pada saat yang sama kami melihat mengapa kami perlu mengalokasikan register penyimpanan sementara. Nah, akhirnya, mari kita lihat bagaimana matematika diterapkan untuk bekerja dengan konstanta. Kita tahu tentang keberadaan perintah assembler subi untuk mengurangi konstanta dari register, dan sekarang kita akan mencoba untuk menggambarkannya dalam hal perpustakaan.
var m = new Mega328(); var op1 = m.REG(); op1.Load(0x10); op1 -= 10; var t = AVRASM.Text(m);
Hasilnya akan
RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 ldi R0000,16 subi R0000,0x0A .DSEG
Ternyata. Dan bagaimana perpustakaan akan berperilaku jika tidak ada perintah assembler yang dapat melakukan operasi yang diperlukan? Misalnya, jika kita ingin tidak mengurangi, tetapi menambahkan konstanta. Mari kita coba dan lihat
var m = new Mega328(); var op1 = m.REG(); op1.Load(0x10); op1 += 10; var t = AVRASM.Text(m);
RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 ldi R0000,16 subi R0000,0xF6 .DSEG
Perpustakaan keluar dengan mengurangi nilai negatif. Mari kita lihat bagaimana keadaan dengan perubahan itu. Geser nilai register sebesar 5 ke kanan.
var m = new Mega328(); var op1 = m.REG(); op1.Load(0x10); op1 >>= 5; var t = AVRASM.Text(m);
Hasilnya akan
RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 ldi R0000,16 swap R0000 andi R0000,15 lsr R0000 .DSEG
Tidak semuanya jelas di sini, tetapi menjalankan 2 tim lebih cepat daripada jika solusi frontal dari lima tim shift digunakan.
Jadi, dalam artikel ini kami memeriksa penggunaan perpustakaan untuk bekerja dengan mendaftar aritmatika. Dalam artikel berikutnya, kami akan terus menggambarkan bagaimana perpustakaan bekerja dengan pointer dan mempertimbangkan metode untuk mengendalikan aliran eksekusi perintah (loop, transisi, dll.)