Perpustakaan generator kode assembler untuk mikrokontroler AVR. Bagian 3

โ† Bagian 2. Memulai
Bagian 4. Memprogram perangkat periferal dan menangani interupsi โ†’


Perpustakaan Generator Kode Assembler untuk Mikrokontroler AVR


Bagian 3. Pengalamatan tidak langsung dan kontrol aliran


Pada bagian sebelumnya, kami membahas secara rinci tentang bekerja dengan variabel register 8-bit. Jika Anda melewatkan posting sebelumnya, saya sarankan Anda untuk membacanya. Di sana, Anda dapat menemukan tautan ke perpustakaan untuk mencoba sendiri contoh-contoh dalam artikel tersebut. Bagi mereka yang mengunduh perpustakaan sebelumnya, saya sarankan mengunduh versi terbaru, karena perpustakaan terus diperbarui dan beberapa contoh mungkin tidak berfungsi di versi lama perpustakaan.


Sayangnya, kedalaman bit dari variabel register yang sebelumnya dianggap jelas tidak cukup untuk digunakan sebagai pointer memori. Oleh karena itu, sebelum melanjutkan langsung ke diskusi tentang pointer, kami mempertimbangkan kelas lain dari deskripsi data. Sebagian besar perintah dalam arsitektur AVR Mega dirancang untuk bekerja hanya dengan register operan, mis., Kedua operan dan hasilnya berukuran 8 bit. Namun, ada sejumlah operasi di mana dua register RON yang ditempatkan secara berurutan dianggap sebagai register 16-bit tunggal. Ada beberapa operasi seperti itu, dan mereka terutama berfokus pada bekerja dengan pointer.


Dari sudut pandang sintaks perpustakaan, bekerja dengan pasangan register hampir sama dengan bekerja dengan variabel register. Pertimbangkan contoh kecil di mana kami mencoba bekerja dengan pasangan register. Untuk menghemat ruang di sini dan di bawah, kami akan memberikan hasil eksekusi hanya jika diperlukan untuk menjelaskan fitur-fitur tertentu dari pembuatan kode.


var m = new Mega328(); var dr1 = m.DREG(); var dr2 = m.DREG(); dr1.Load(0xAA55); dr2.Load(0x55AA); dr1++; dr1--; dr1 += 0x100; dr1 += dr2; dr2 *= dr1; dr2 /= dr1; var t = AVRASM.Text(m); 

Dalam contoh ini, kami mendeklarasikan dua variabel 2-byte yang terletak di pasangan register menggunakan perintah DREG (). Dengan perintah berikut, kami menetapkan nilai awal dan melakukan serangkaian operasi aritmatika. Seperti yang Anda lihat dari contoh, sintaks untuk bekerja dengan pasangan register sebagian besar sama dengan bekerja dengan register biasa. Pasangan register juga dapat dianggap sebagai variabel yang terdiri dari dua register independen. Register diakses sebagai kumpulan dua register 8-bit melalui properti Tinggi untuk mengakses 8 bit atas sebagai register 8-bit, dan properti Rendah untuk mengakses 8 bit yang lebih rendah. Kode akan terlihat seperti ini


 var m = new Mega328(); var dr1 = m.DREG(); dr1.Load(0xAA55); dr1.Low--; dr1.High += dr1.Low; var t = AVRASM.Text(m); 

Seperti yang Anda lihat dari contoh, kita dapat bekerja dengan Tinggi dan Rendah sebagai variabel register independen, termasuk melakukan berbagai operasi aritmatika dan logis di antara mereka.


Sekarang setelah kami menemukan variabel panjang-ganda, kami dapat mulai menjelaskan cara bekerja dengan variabel dalam memori. Perpustakaan memungkinkan Anda untuk bekerja dengan 8, 16-bit variabel dan array byte yang panjangnya sewenang-wenang. Pertimbangkan contoh alokasi ruang untuk variabel dalam RAM.


 var m = new Mega328(); var bt = m.BYTE(); //8-    var wd = m.WORD(); //16-    var arr = m.ARRAY(16); //  16  var t = AVRASM.Text(m); 

Mari kita lihat apa yang terjadi.


 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DSEG L0002: .BYTE 16 L0001: .BYTE 2 L0000: .BYTE 1 

Di bagian definisi data, kami memiliki alokasi memori. Perhatikan bahwa urutan alokasi berbeda dari deklarasi variabel. Ini bukan kebetulan. Alokasi memori untuk variabel terjadi setelah pengurutan dalam urutan menurun sesuai dengan kriteria berikut (dalam urutan menurunnya kepentingan) Kelipatan pembagi maksimum derajat 2 โ†’ Ukuran memori yang dialokasikan. Ini berarti bahwa jika kita ingin mengalokasikan 4 array berukuran 64, 48,40, dan 16 byte, maka urutan alokasi, terlepas dari urutan deklarasi, akan terlihat seperti ini:


Panjang 64 - Kelipatan Pembagi Maksimum Gelar 2 = 64
Panjang 48 - Kelipatan Pembagi Maksimum Derajat 2 = 16
Panjang 16 - Pembagi maksimum kelipatan derajat 2 = 16
Panjang 40 - Kelipatan Pembagi Maksimum Derajat 2 = 8
Ini dilakukan untuk menyederhanakan kontrol batas array.
dan mengurangi ukuran kode dalam operasi dengan pointer. Kami tidak dapat secara langsung melakukan operasi dengan variabel dalam memori, oleh karena itu semua yang tersedia bagi kami adalah membaca / menulis untuk mendaftarkan variabel. Cara paling sederhana untuk bekerja dengan variabel dalam memori adalah pengalamatan langsung.


 var m = new Mega328(); var bt = m.BYTE(); // 8-    var rr = m.REG(); //    rr.Load(0xAA); //  rr   0xAA rr.Mstore(bt); //     rr.Clear(); //  rr.Mload(bt); //    var t = AVRASM.Text(m); 

Dalam contoh ini, kami mendeklarasikan variabel dalam memori dan variabel register. Setelah itu, kami menetapkan variabel nilai 0x55 dan menulisnya ke dalam variabel dalam memori. Kemudian dihapus dan dikembalikan lagi.


Untuk bekerja dengan elemen array, kami menggunakan sintaks berikut


 var rr = m.REG(); var arr = m.ARRAY(10); rr.MLoad(arr[5]); 

Penomoran elemen dalam array dimulai dengan 0. Dengan demikian, dalam contoh di atas, nilai 6 elemen array ditulis ke sel rr.


Sekarang Anda bisa pergi ke pengalamatan tidak langsung. Perpustakaan memiliki tipe data sendiri untuk pointer ke ruang memori RAM - MEMPtr . Mari kita lihat bagaimana kita bisa menggunakannya. Kami memodifikasi contoh kami sebelumnya sehingga pekerjaan dengan variabel dalam memori dilakukan melalui pointer.


 var m = new Mega328(); var bt1 = m.BYTE(); var bt2 = m.BYTE(); var rr = m.REG(); var ptr = m.MEMPTR(); //  ptr ptr.Load(bt1); //ptr   bt1 rr.Load(0xAA); // rr - 0xAA ptr.MStore(rr); //  bt1 0xAA rr.Load(0x55); // rr - 0x55 ptr.Load(bt2); //ptr   bt2 ptr.MStore(rr); //  bt2 0x55 ptr.Load(bt1); //ptr   bt1 ptr.MLoad(rr); //  rr  0xAA var t = AVRASM.Text(m); 

Itu bisa dilihat dari teks yang pertama kita nyatakan pointer ptr , dan kemudian dilakukan operasi tulis dan baca dengannya. Selain kemampuan untuk mengubah alamat baca / tulis dalam perintah selama eksekusi, penggunaan pointer menyederhanakan bekerja dengan array, menggabungkan operasi baca / tulis dengan kenaikan / penurunan pointer. Mari kita lihat sebuah program yang dapat mengisi array dengan nilai tertentu.


 var m = new Mega328(); var bt1 = m.ARRAY(4); //   4  var rr = m.REG(); var ptr = m.MEMPTR(); ptr.Load(bt1.Label); //ptr   bt1 rr.Load(0xAA); // rr - 0xAA ptr.MStoreInc(rr); //  bt1 0xAA ptr.MStoreInc(rr); //  bt1+1 0xAA ptr.MStoreInc(rr); //  bt1+2 0xAA ptr.MStoreInc(rr); //  bt1+3 0xAA rr.Clear(); rr.MLoad(bt1[2]); //  rr 3-   var t = AVRASM.Text(m); 

Dalam contoh ini, kami mengambil keuntungan dari kemampuan untuk menambah pointer saat menulis ke memori.
Selanjutnya, kita beralih ke kemampuan perpustakaan untuk mengontrol aliran perintah. Jika lebih mudah, cara memprogram lompatan bersyarat dan lompatan bersyarat menggunakan perpustakaan. Cara termudah untuk mengelola ini adalah dengan menggunakan perintah navigasi label. Label dalam suatu program dinyatakan dengan dua cara berbeda. Yang pertama adalah bahwa dengan tim AVRASM.Label kami membuat label untuk digunakan di masa depan, tetapi jangan memasukkannya ke dalam kode program. Metode ini digunakan untuk membuat lompatan maju, yaitu, dalam kasus di mana perintah lompat harus mendahului label. Untuk mengatur label di tempat yang diperlukan dari kode assembler, Anda harus menjalankan perintah AVRASM.newLabel ([variabel dari label yang dibuat sebelumnya]) . Untuk kembali, Anda dapat menggunakan sintaksis yang lebih sederhana dengan mengatur label dan menetapkan nilainya ke variabel dengan satu perintah AVRASM.newLabel () tanpa parameter.


Jenis transisi yang paling sederhana adalah transisi tanpa syarat. Untuk menyebutnya, kami menggunakan perintah GO ([jump_mark]] . Mari kita lihat tampilannya dengan sebuah contoh.


 var m = new Mega328(); var r = m.REG(); //  var lbl1 = AVRASM.Label;//        m.GO(lbl1); r++; //    r++; AVRASM.NewLabel(lbl1);//  //  var lbl2 = AVRASM.NewLabel();//    r--; //    r--; m.GO(lbl2); var t = AVRASM.Text(m); 

Transisi bersyarat memiliki kontrol lebih besar atas aliran eksekusi. Perilaku mereka tergantung pada keadaan bendera operasi dan ini memungkinkan untuk mengontrol aliran operasi tergantung pada hasil eksekusi mereka. Pustaka menggunakan fungsi IF untuk menggambarkan blok perintah yang harus dijalankan hanya dalam kondisi tertentu. Mari kita lihat sebuah contoh.


 var m = new Mega328(); var rr1 = m.REG(); var rr2 = m.REG(); rr1.Load(0x22); rr2.Load(0x33); m.IF(rr1 == rr2, () => { AVRASM.Comment(" - ,  "); }); var t = AVRASM.Text(m); 

Karena sintaks dari perintah IF tidak cukup familiar, pertimbangkan lebih detail. Argumen pertama di sini adalah kondisi transisi. Berikut ini adalah metode di mana blok kode ditempatkan, yang harus dieksekusi jika kondisi terpenuhi. Varian fungsi adalah kemampuan untuk menggambarkan cabang alternatif, mis., Blok kode yang harus dijalankan jika kondisi tidak terpenuhi. Selain itu, Anda dapat memperhatikan fungsi AVRASM.Comment () , yang dengannya kami dapat menambahkan komentar ke assembler output.


 var m = new Mega328(); var rr1 = m.REG(); var rr2 = m.REG(); rr1.Load(0x22); rr2.Load(0x33); m.IF(rr1 == rr2, () => { AVRASM.Comment(" - ,  "); },()=> { AVRASM.Comment(" - ,   "); }); AVRASM.Comment(" "); var t = AVRASM.Text(m); 

Hasilnya dalam hal ini akan terlihat sebagai berikut


 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 .DEF R0001 = r21 ldi R0000,34 ldi R0001,51 cp R0000,R0001 brne L0002 ;---  - ,   --- xjmp L0004 L0002: ;---  - ,    --- L0004: ;---   --- .DSEG 

Contoh sebelumnya menunjukkan opsi percabangan bersyarat di mana perintah perbandingan digunakan untuk menentukan kondisi percabangan. Dalam beberapa kasus, ini tidak diperlukan, karena kondisi transisi harus ditentukan oleh keadaan bendera setelah operasi terakhir dilakukan. Sintaks berikut disediakan untuk kasus-kasus seperti itu.


 var m = new Mega328(); var rr1 = m.REG(); rr1.Load(0x22); rr1--; m.IFEMPTY(() =>AVRASM.Comment(",    0")); var t = AVRASM.Text(m); 

Dalam contoh ini, fungsi IFEMPTY memeriksa status bendera Z setelah kenaikan dan mengeksekusi kode dari blok bersyarat saat mencapai 0.
Yang paling fleksibel dalam hal penggunaan dapat dianggap sebagai fungsi LOOP . Ini dimaksudkan untuk deskripsi siklus program yang nyaman. Pertimbangkan tanda tangannya


 LOOP(Register iter, Action<Register, string> Condition, Action<Register, string> body) 

Parameter iter memberikan variabel register yang dapat digunakan sebagai iterator dalam satu loop. Parameter kedua berisi blok kode yang menggambarkan kondisi untuk keluar dari loop. Iterator yang ditetapkan dan label awal dari loop untuk kembali dilewatkan ke blok kode ini. Parameter terakhir digunakan untuk menggambarkan blok kode dari badan utama loop. Contoh paling sederhana menggunakan fungsi LOOP adalah loop rintisan, yaitu loop tak terbatas untuk melompat ke baris yang sama. Sintaks dalam hal ini adalah sebagai berikut


 m.LOOP(m.TempL, (r, l) => m.GO(l), (r,l) => { }); 

Hasil kompilasi diberikan di bawah ini.


 L0002: xjmp L0002 

Mari kita kembali ke contoh pengisian array dengan nilai tertentu dan mengubahnya sehingga pengisian dilakukan dalam satu lingkaran


 var m = new Mega328(); var rr1 = m.REG(); var rr2 = m.REG(); var arr = m.ARRAY(16); var ptr = m.MEMPTR(); ptr.Load(arr[0]); //     rr2.Load(16); //    rr1.Load(0xAA); //   m.LOOP(rr2, (r, l) => //rr2     . { r--; //   m.IFNOTEMPTY(l); // ,   }, (r,l) => ptr.MStoreInc(rr1)); //   var t = AVRASM.Text(m); 

Kode keluaran dalam hal ini akan terlihat sebagai berikut


 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 .DEF R0001 = r21 ldi YL, LOW(L0002+0) ldi YH, HIGH(L0002+0) ldi R0001,16 ldi R0000,170 L0003: st Y+,R0000 dec R0001 brne L0003 L0004: .DSEG L0002: .BYTE 16 

Cara lain untuk mengatur transisi adalah melalui transisi yang ditangani secara tidak langsung. Analog terdekat dalam bahasa tingkat tinggi bagi mereka adalah penunjuk ke fungsi. Pointer dalam hal ini tidak akan menunjuk ke ruang RAM, tetapi ke kode program. Karena AVR memiliki arsitektur Harvard dan menggunakan serangkaian instruksi spesifik untuk mengakses memori program, ROMPtr digunakan sebagai penunjuk daripada MEMPtr yang dijelaskan di atas. Kasus penggunaan untuk transisi yang ditangani secara tidak langsung dapat diilustrasikan dengan contoh berikut.


 var m = new Mega328(); var block1 = AVRASM.Label; var block2 = AVRASM.Label; var block3 = AVRASM.Label; var ptr = m.ROMPTR(); ptr.Load(block1); //     var loop = AVRASM.NewLabel(); AVRASM.Comment("   "); m.GOIndirect(ptr); //   ,     AVRASM.NewLabel(block1); AVRASM.Comment("  1"); ptr.Load(block2); m.GO(loop); AVRASM.NewLabel(block2); AVRASM.Comment("  2"); ptr.Load(block3); m.GO(loop); AVRASM.NewLabel(block3); AVRASM.Comment("  3"); ptr.Load(block1); m.GO(loop); var t = AVRASM.Text(m); 

Dalam contoh ini, kami memiliki 3 blok perintah. Setelah menyelesaikan setiap blok, kontrol ditransfer kembali ke perintah cabang yang ditangani secara tidak langsung. Karena pada akhir blok perintah kita mengatur vektor transisi ke blok baru setiap kali, eksekusi akan terlihat seperti Block1 โ†’ Block2 โ†’ Block3 โ†’ Block1 ... dan seterusnya dalam lingkaran. Perintah ini, bersama-sama dengan perintah cabang kondisional, memungkinkan cara bahasa yang sederhana dan nyaman untuk menggambarkan algoritma yang cukup rumit seperti mesin negara.


Versi yang lebih canggih dari cabang yang ditangani secara tidak langsung adalah perintah SWITCH . Itu tidak menggunakan pointer ke label transisi untuk transisi, tetapi pointer ke variabel di memori di mana alamat label transisi disimpan.


 var m = new Mega328(); var block1 = AVRASM.Label; var block2 = AVRASM.Label; var block3 = AVRASM.Label; var arr = m.ARRAY(6); var ptr = m.MEMPTR(); //    m.Temp.Load(block1); m.Temp.Store(arr[0]); m.Temp.Load(block2); m.Temp.Store(arr[2]); m.Temp.Load(block3); m.Temp.Store(arr[4]); ptr.Load(arr[0]); //     var loop = AVRASM.NewLabel(); m.SWITCH(ptr); //   ,     AVRASM.NewLabel(block1); AVRASM.Comment("  1"); ptr.Load(arr[2]); //       m.GO(loop); AVRASM.NewLabel(block2); AVRASM.Comment("  2"); m.Temp.Load(block3); ptr.MStore(m.Temp); //       m.GO(loop); AVRASM.NewLabel(block3); AVRASM.Comment("  3"); ptr.Load(arr[0]); //       m.GO(loop); 

Dalam contoh ini, urutan transisi adalah sebagai berikut: Block1 โ†’ Block2 โ†’ Block3 โ†’ Block1 โ†’ Block3 โ†’ Block1 โ†’ Block3 โ†’ Block1 ... Kami dapat menerapkan algoritma di mana perintah Block2 dieksekusi hanya pada siklus pertama.


Di bagian posting selanjutnya, kami akan mempertimbangkan untuk bekerja dengan perangkat periferal, menerapkan interupsi, rutinitas, dan banyak lagi.

Source: https://habr.com/ru/post/id463123/


All Articles