Banyak yang telah ditulis tentang makro di assembler. Dan dalam dokumentasi, dan di berbagai artikel. Tetapi dalam kebanyakan kasus, itu datang ke daftar arahan sederhana dengan deskripsi singkat tentang fungsi mereka, atau ke satu set contoh yang berbeda dari makro siap pakai.
Tujuan artikel ini adalah untuk menggambarkan pendekatan spesifik untuk pemrograman bahasa assembly untuk menghasilkan kode yang paling sederhana dan mudah dibaca menggunakan makro. Artikel tidak akan menjelaskan sintaksis perintah dan arahan individu. Deskripsi terperinci telah diberikan oleh produsen . Kami akan fokus pada bagaimana menggunakan peluang ini untuk memecahkan masalah tertentu.
Pada suatu waktu, ATMEL mencoba dan mengembangkan garis mikrokontroler delapan-bit dengan arsitektur berkualitas sangat tinggi dan sederhana, tetapi pada saat yang sama sistem komandonya sangat kuat. Tetapi, seperti yang Anda tahu, tidak ada batasan untuk kesempurnaan, dan beberapa instruksi yang sering digunakan tidak cukup. Untungnya, assembler makro, ramah dan benar-benar gratis yang disediakan oleh pabrikan, dapat menyederhanakan kode secara signifikan melalui penggunaan arahan. Sebelum melanjutkan langsung ke makro, kami akan melakukan beberapa langkah awal
Definisi konstanta
.EQU FOSC = 16000000 .EQU CLK8 = 0
Dua definisi ini memungkinkan Anda untuk menyingkirkan "angka ajaib" di makro, di mana nilai register dihitung berdasarkan frekuensi prosesor dan status sekering pembagi periferal. Definisi pertama adalah frekuensi kristal prosesor di hertz, yang kedua adalah keadaan pembagi frekuensi periferal.
Daftar penamaan
.DEF TempL = r16 .DEF TempH = r17 .DEF TempQL = r18 .DEF TempQH = r19 .DEF AL = r0 .DEF AH = r1 .DEF AQL = r2 .DEF AQH = r3
Sekilas penamaan register penamaan yang dapat digunakan dalam makro. Hanya empat register untuk Temp diperlukan jika kita berurusan dengan nilai 32-bit (misalnya, dalam operasi mengalikan dua angka 16-bit). Jika kami yakin bahwa dua register penyimpanan sementara cukup bagi kami untuk digunakan dalam makro, maka TempQL dan TempQH tidak dapat ditentukan. Definisi untuk A diperlukan untuk makro yang menggunakan operasi multiplikasi. AQ tidak lagi diperlukan jika kita tidak menggunakan aritmatika 32-bit dengan makro kita.
Macro untuk mengimplementasikan perintah sederhana
Sekarang setelah kami mengetahui penamaan register, kami akan mulai mengimplementasikan perintah yang hilang dan mulai dengan mencoba menyederhanakan yang sudah ada. Assembler AVR memiliki satu fitur aneh. Untuk input dan output, 64 port pertama menggunakan perintah masuk / keluar , dan untuk lds / sts yang tersisa. Agar tidak melihat dokumentasi setiap kali mencari perintah yang diperlukan untuk port tertentu, kami akan membuat seperangkat perintah universal yang secara independen akan menggantikan nilai-nilai yang diperlukan.
.MACRO XOUT .IF @0<64 out @0,@1 .ELSE sts @0,@1 .ENDIF .ENDMACRO .MACRO XIN .IF @1<64 in @0,@1 .ELSE lds @0,@1 .ENDIF .ENDMACRO
Agar substitusi berfungsi dengan benar, kompilasi bersyarat digunakan dalam makro. Dalam kasus ketika alamat port kurang dari 64, bagian kondisional pertama dijalankan, sebaliknya yang kedua. Makro kami sepenuhnya mengulangi fungsionalitas perintah standar untuk bekerja dengan port input / output, oleh karena itu, untuk menunjukkan bahwa tim kami memiliki fitur-fitur canggih, kami menambahkan awalan penamaan standar X.
Salah satu perintah yang paling umum yang tidak tersedia di assembler, tetapi selalu diperlukan, adalah perintah untuk menulis konstanta ke register input output. Implementasi makro untuk perintah ini akan terlihat seperti ini
.MACRO OUTI ldi TempL,@1 .IF @0<64 out @0, TempL .ELSE sts @0, TempL .ENDIF .ENDMACRO
Dalam hal ini, nama dalam makro, agar tidak melanggar logika penamaan perintah, tambahkan ke nama standar postfix I , yang digunakan oleh pengembang untuk menunjukkan perintah untuk bekerja dengan konstanta. Dalam makro ini, kami menggunakan register TempL yang didefinisikan sebelumnya untuk operasi .
Dalam beberapa kasus, tidak diperlukan satu register, tetapi seluruh pasangan menyimpan nilai 16-bit. Buat makro baru untuk menulis nilai 16-bit ke sepasang register I / O
.MACRO OUTIW ldi TempL,HIGH(@1) .IF @0<64 out @0H, TempL .ELSE sts @0H, TempL .ENDIF ldi TempL,LOW(@1) .IF @0<64 out @0L, TempL .ELSE sts @0L, TempL .ENDIF .ENDMACRO
Dalam makro ini, kami menggunakan fungsi RENDAH dan TINGGI bawaan untuk mengekstrak byte rendah dan tinggi dari nilai 16-bit. Dalam nama makro, tambahkan postfix I dan W ke perintah untuk menunjukkan bahwa dalam hal ini perintah bekerja dengan nilai 16-bit (kata).
Tidak kurang sering dalam program ada memuat pasangan register, misalnya, untuk mengatur pointer ke memori. Mari kita buat makro seperti itu
.MACRO ldiw ldi @0L, LOW(@1) ldi @0H, HIGH(@1) .ENDMACRO
Dalam makro ini, kami menggunakan fakta bahwa penamaan standar register dan port di pabrik menyiratkan postfix L untuk yang lebih rendah dan postfix H untuk bagian atas dari nilai byte ganda. Jika Anda mengikuti aturan ini saat memberi nama variabel Anda sendiri, maka makro akan bekerja dengan benar, termasuk dengan mereka. Keindahan makro juga terletak pada kenyataan bahwa mereka menyediakan substitusi sederhana, oleh karena itu, dalam kasus ketika operan kedua adalah angka, dan dalam kasus ketika ini adalah nama label, makro akan bekerja dengan benar.
Makro untuk mengimplementasikan perintah kompleks.
Ketika datang ke operasi yang lebih kompleks, makro umumnya tidak digunakan, lebih memilih rutinitas. Namun, dalam kasus ini, makro dapat membuat hidup lebih mudah dan membuat kode lebih mudah dibaca. Dalam hal ini, kompilasi bersyarat datang untuk menyelamatkan. Pendekatan pemrograman mungkin terlihat seperti ini:
Kami menempatkan semua rutinitas kami di file terpisah, yang akan kami beri nama, misalnya, Library.inc . Setiap subrutin dalam file ini akan memiliki formulir berikut
_sub0: .IFDEF __sub0 ; ----- ----- ret .ENDIF
Dalam hal ini, keberadaan definisi __sub0 berarti bahwa subrutin harus dimasukkan dalam kode yang dihasilkan. Kalau tidak, itu diabaikan.
Selanjutnya, dalam file terpisah Macro.inc, kami mendefinisikan makro formulir
.MACRO SUB0 .IFNDEF __sub0 .DEF __sub0 .ENDIF ; --- call _sub0 .ENDMACRO
Saat menggunakan makro ini, kami memeriksa definisi __sub0 dan, jika tidak ada, kami melakukan penentuan. Akibatnya, menggunakan makro membuka kunci dimasukkannya kode subrutin dalam file output. Dalam hal menggunakan rutin dalam makro, kode program utama akan mengambil bentuk berikut
.INCLUDE “Macro.inc” ;---- ---- .INCLUDE “Library.inc”
Sebagai contoh, kami memberikan implementasi makro untuk membagi bilangan bulat 8-bit yang tidak ditandatangani. Kami menyimpan logika pabrikan dan menempatkan hasilnya di AL (r0) , dan sisanya dari divisi di AH (r1) . Subrutin akan terlihat sebagai berikut
_div8u: .IFDEF __ div8u ;AH - ;AL ;TempL - ;TempH - ;TempQL - clr AL; clr AH; ldi TempQL,9 d8u_1: rol TempL dec TempQL brne d8u_2 ret d8u_2: rol A sub AH, TempH brcc d8u_3 add AH,TempH clc rjmp d8u_1 d8u_3: sec rjmp d8u_1 .ENDIF
Definisi makro untuk menggunakan rutin ini adalah sebagai berikut
.MACRO DIV8U .IFNDEF __div8u .DEF __div8u .ENDIF mov TempL, @0 mov TempH, @1 call _div8u .ENDMACRO
Jika diinginkan, Anda dapat menambahkan versi untuk bekerja dengan konstanta
.MACRO DIV8UI .IFNDEF __div8u .DEF __div8u .ENDIF mov TempL, @0 ldi TempH, @1 call _div8u .ENDMACRO
Akibatnya, menggunakan operasi divisi dalam teks program adalah sepele
DIV8U r10, r11 ; r0 = r10/r11 r1 = r10 % r11 DIV8UI r10, 35 ; r0 = r10/35 r1 = r10 % 35
Menggunakan kompilasi bersyarat, kami dapat menempatkan semua rutin yang dapat bermanfaat bagi kami di Library.inc . Dalam hal ini, hanya mereka yang dipanggil setidaknya satu kali yang akan muncul dalam kode output. Perhatikan posisi label entri. Keluaran label di luar batas kondisi disebabkan oleh kompiler. Jika Anda menempatkan label di badan blok bersyarat, kompiler dapat membuat kesalahan. Kehadiran tag yang tidak digunakan dalam kode tidak menakutkan, karena keberadaan sejumlah tag tidak mempengaruhi hasilnya.
Makro perifer
Salah satu operasi yang sulit dilakukan tanpa menggunakan dokumentasi pabrikan adalah menginisialisasi perangkat periferal. Bahkan dengan penggunaan penunjukan mnemonik register dan bit dari kode, bisa sulit untuk memahami di mana mode perangkat dikonfigurasi, terutama karena kadang-kadang mode dikonfigurasi oleh kombinasi nilai bit dari register yang berbeda. Mari kita lihat bagaimana makro dapat digunakan dengan contoh USART .
Mari kita mulai dengan makro inisialisasi mode asinkron.
.MACRO USART_INIT ; speed, bytes, parity, stop-bits .IF CLK8 == 0 .SET DIVIDER = FOSC/16/@0-1 .ELSE .SET DIVIDER = FOSC/128/@0-1 .ENDIF ; Set baud rate to UBRR0 outi UBRR0H, HIGH(DIVIDER) outi UBRR0L, LOW(DIVIDER) ; Enable receiver and transmitter .SET UCSR0B_ = (1<<RXEN0)|(1<<TXEN0) outi UCSR0B, UCSR0B_ .SET UCSR0C_ = 0 .IF @2 == 'E' .SET UCSR0C_ |= (1<<UPM01) .ENDIF .IF @2 == 'O' .SET UCSR0C_ |= (1<<UPM00) .ENDIF .IF @3== 2 .SET UCSR0C_ |= (1<<USBS0) .ENDIF .IF @1== 6 .SET UCSR0C_ |= (1<<UCSZ00) .ENDIF .IF @1== 7 .SET UCSR0C_ |= (1<<UCSZ01) .ENDIF .IF @1== 8 .SET UCSR0C_ = UCSR0C_ |(1<<UCSZ01)|(1<<UCSZ00) .ENDIF .IF @1== 9 .SET UCSR0C_ |= (1<<UCSZ02)|(1<<UCSZ01)|(1<<UCSZ00) .ENDIF ; Set frame format outi UCSR0C,UCSR0C_ .ENDMACRO
Dengan menggunakan makro, kami dapat mengganti inisialisasi register pengaturan USART dengan nilai-nilai yang tidak dapat dipahami tanpa membaca dokumentasi dengan garis yang dapat ditangani bahkan oleh mereka yang pertama kali menemukan pengontrol ini. Dalam makro ini, juga akhirnya menjadi jelas mengapa kami menentukan frekuensi dan konstanta pembagi. Yah, harus dicatat bahwa terlepas dari kode makro yang mengesankan itu sendiri, yang dihasilkan akan memiliki penampilan yang sama seperti jika kita menulis inisialisasi dengan cara yang biasa.
Untuk menyelesaikan dengan USART, berikut adalah beberapa makro kecil
.MACRO USART_SEND_ASYNC outi UDR0, @0 .ENDMACRO
Hanya ada satu baris, tetapi menggunakan makro ini akan memungkinkan Anda untuk lebih melihat di mana program menampilkan data di USART . Jika kami menganggap bekerja dalam mode sinkron tanpa menggunakan interupsi, maka alih-alih USART_SEND_ASYNC lebih baik menggunakan makro di bawah ini
.MACRO USART_SEND USART_Transmit: xin TempL, UCSR0A sbrs TempL, UDRE0 rjmp USART_Transmit outi UDR0, @0 .ENDMACRO
Dalam hal ini, kami mengaktifkan pemeriksaan okupansi port dan menampilkan data hanya ketika port tersebut gratis. Jelas, pendekatan ini untuk bekerja dengan perangkat periferal akan bekerja untuk perangkat apa pun, dan tidak hanya untuk USART .
Perbandingan program tanpa dan menggunakan makro.
Mari kita lihat contoh kecil dan bandingkan kode yang ditulis tanpa menggunakan makro dengan kode tempat mereka digunakan. Misalnya, ambil program yang menampilkan klasik "Hello world!" ke terminal melalui perangkat keras UART .
RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 USART_Init: out UBRR0H, r17 out UBRR0L, r16 ldi r16, (1<<RXEN0)|(1<<TXEN0) out UCSRnB,r16 ldi r16, (1<<USBS0)|(3<<UCSZ00) out UCSR0C,r16 ldi ZL, LOW(STR<<1) ldi ZH, HIGH(STR<<1) LOOP: lpm r16, Z+ or r16,r16 breq END USART_Transmit: in r17, UCSR0A sbrs r17, UDRE0 rjmp USART_Transmit out UDR0,r16 rjmp LOOP END: rjmp END STR: .DB “Hello world!”,0
Dan di sini adalah program yang sama, tetapi ditulis menggunakan macro
.INCLUDE “macro.inc” .EQU FOSC = 16000000 .EQU CLK8 = 0 RESET: ldiw SP, RAMEND; USART_INIT 19200, 8, "N", 1 ldiw Z, STR<<1 LOOP: lpm TempL, Z+ test TempL breq END USART_SEND TempL rjmp LOOP END: rjmp END STR: .DB “Hello world!”,0
Dalam contoh ini, kami menggunakan makro yang dijelaskan di atas, yang memungkinkan kami untuk menyederhanakan kode program secara signifikan dan membuatnya lebih mudah dipahami. Kode biner di kedua program akan benar-benar identik.
Kesimpulan
Menggunakan makro dapat secara signifikan mengurangi kode assembler program, agar lebih mudah dipahami dan dibaca. Kompilasi bersyarat memungkinkan Anda membuat perintah universal dan pustaka prosedur tanpa membuat kode output yang berlebihan. Sebagai kekurangan, seseorang dapat menunjukkan sangat sederhana dengan standar bahasa tingkat tinggi yang mengatur operasi dan pembatasan yang diizinkan ketika menyatakan data "maju". Pembatasan ini tidak memungkinkan, misalnya, menulis dengan cara makro perintah universal penuh untuk transisi jmp / rjmp dan secara signifikan mengembang kode makro itu sendiri ketika menerapkan logika kompleks.