Ambang 32K untuk data dalam ROM mikrokontroler AVR

Apa yang bisa lebih buruk dari kruk? Hanya kruk yang tidak sepenuhnya didokumentasikan.


gambar


Ini adalah screenshot dari lingkungan pengembangan terintegrasi resmi terbaru untuk mikrokontroler AVR 8-bit, Atmel Studio 7, bahasa pemrograman C. Seperti yang Anda lihat dari kolom Nilai, variabel my_array berisi angka 0x8089. Dengan kata lain, array my_array terletak di memori mulai dari alamat 0x8089.


Pada saat yang sama, kolom Type memberi kita informasi yang sedikit berbeda: my_array adalah array dari 4 elemen tipe int16_t yang terletak di ROM (ini ditunjukkan oleh kata prog, tidak seperti data untuk RAM), mulai dari alamat 0x18089. Berhenti, tapi bagaimanapun juga 0x8089! = 0x18089. Apa alamat sebenarnya dari array?


Bahasa C dan arsitektur Harvard


Mikrokontroler AVR 8-bit yang diproduksi sebelumnya oleh Atmel, dan sekarang Microchip, sangat populer, khususnya, karena fakta bahwa mereka adalah basis Arduino, dibangun di atas arsitektur Harvard, yaitu kode dan data terletak di ruang alamat yang berbeda. Dokumentasi resmi berisi contoh kode dalam dua bahasa: assembler dan C. Sebelumnya, pabrikan menawarkan lingkungan pengembangan terintegrasi gratis yang hanya mendukung assembler. Tetapi bagaimana dengan mereka yang ingin memprogram dalam C, atau bahkan C ++? Ada solusi berbayar, misalnya, IAR AVR dan CodeVisionAVR. Secara pribadi, saya tidak pernah menggunakannya, karena ketika saya mulai pemrograman AVR pada 2008, sudah ada WinAVR gratis dengan kemampuan untuk berintegrasi dengan AVR Studio 4, dan itu hanya termasuk dalam Atmel Studio 7 saat ini.


Proyek WinAVR didasarkan pada kompiler GNU GCC, yang dikembangkan untuk arsitektur von Neumann, yang menyiratkan ruang alamat tunggal untuk kode dan data. Saat mengadaptasi GCC ke AVR, kruk berikut diterapkan: alamat 0 hingga 0x007ffff dialokasikan untuk kode (ROM, flash), dan 0x00800100 untuk 0x0080ffff untuk data (RAM, SRAM). Ada segala macam trik lain, misalnya, alamat dari 0x00800000 hingga 0x008000ff mewakili register yang dapat diakses oleh opcode yang sama dengan RAM. Pada prinsipnya, jika Anda seorang programmer sederhana, seperti Arduino pemula, dan bukan seorang hacker, pencampur assembler dan C / C ++ dalam firmware yang sama, Anda tidak perlu mengetahui semua ini.


Selain kompiler, WinAVR mencakup berbagai pustaka (bagian dari pustaka C standar dan modul khusus-AVR) dalam bentuk proyek AVR Libc. Versi terbaru, 2.0.0, dirilis hampir tiga tahun lalu, dan dokumentasinya tidak hanya tersedia di situs proyek itu sendiri, tetapi juga di situs pabrikan mikrokontroler. Ada juga terjemahan bahasa Rusia yang tidak resmi.


Data di ruang alamat kode


Kadang-kadang dalam mikrokontroler Anda perlu meletakkan tidak hanya banyak, tetapi banyak data: begitu banyak sehingga mereka tidak masuk ke dalam RAM. Selain itu, data ini tidak dapat diubah, dikenal pada saat firmware. Misalnya, gambar raster, melodi atau semacam meja. Pada saat yang sama, kode seringkali hanya mengambil sebagian kecil dari ROM yang tersedia. Jadi mengapa tidak menggunakan sisa ruang untuk data? Mudah! Dokumentasi avr-libc 2.0.0 mencakup seluruh bab 5 Data dalam Ruang Program. Jika Anda menghilangkan bagian tentang garis, maka semuanya sangat sederhana. Pertimbangkan sebuah contoh. Untuk RAM, kami menulis seperti ini:


unsigned char array2d[2][3] = {...}; unsigned char element = array2d[i][j]; 

Dan untuk ROM seperti ini:


 #include <avr/pgmspace.h> const unsigned char array2d[2][3] PROGMEM = {...}; unsigned char element = pgm_read_byte(&(array2d[i][j])); 

Sangat sederhana bahwa teknologi ini telah berulang kali dibahas bahkan di RuNet.


Jadi apa masalahnya?


Ingat pernyataan bahwa 640 KB sudah cukup untuk semua orang? Ingat bagaimana Anda beralih dari arsitektur 16-bit ke 32-bit, dan dari 32-bit ke 64-bit? Bagaimana Windows 98 bekerja tidak stabil pada lebih dari 512 MB RAM sementara itu dirancang untuk 2 GB? Pernahkah Anda memperbarui BIOS sehingga motherboard bekerja dengan hard drive yang lebih besar dari 8 GB? Ingat jumper pada 80 GB hard drive, memangkas volumenya menjadi 32 GB?


Masalah pertama menyusul saya ketika saya mencoba membuat array setidaknya 32 KB di ROM. Kenapa di ROM, dan tidak di RAM? Karena saat ini, AVR 8-bit dengan lebih dari 32 KB RAM tidak ada. Dan dengan lebih dari 256 B - ada. Ini mungkin mengapa pembuat kompiler memilih 16 b (2 B) untuk pointer dalam RAM (dan pada saat yang sama untuk tipe int), yang dapat ditemukan dalam pembacaan paragraf Tipe data yang terdapat di bab 11.14 Register apa yang digunakan oleh kompiler C? Dokumentasi AVR Libc. Oh, dan kami tidak akan meretas, tetapi di sini adalah register ... Tapi kembali ke array. Ternyata Anda tidak dapat membuat objek yang lebih besar dari 32.767 B (2 ^ (16 - 1) - 1 B). Saya tidak tahu mengapa perlu untuk membuat panjang objek menjadi signifikan, tetapi ini adalah fakta: tidak ada objek, bahkan array multidimensi, dapat memiliki panjang 32.768 B atau lebih. Agak seperti batasan ruang alamat aplikasi 32-bit (4 GB) dalam OS 64-bit, bukan?


Sejauh yang saya tahu, masalah ini tidak memiliki solusi. Jika Anda ingin menempatkan objek dengan panjang 32.768 di ROM, pisahkan menjadi objek yang lebih kecil.


Kami kembali ke tipe data paragraf: pointer adalah 16 bit. Kami menerapkan pengetahuan ini untuk Bab 5 Data di Ruang Program. Tidak, teori sangat diperlukan, praktik diperlukan. Saya menulis sebuah program pengujian, meluncurkan debugger (sayangnya, perangkat lunak, bukan perangkat keras) dan melihat bahwa fungsi pgm_read_byte dapat mengembalikan data yang alamatnya sesuai dengan 16 bit (64 KB; terima kasih, bukan 15). Kemudian terjadi overflow, bagian yang lebih lama dibuang. Adalah logis, mengingat bahwa pointer 16-bit. Tetapi dua pertanyaan muncul: mengapa ini tidak ditulis dalam bab 5 (pertanyaan retoris, tetapi dialah yang mendorong saya untuk menulis artikel ini) dan bagaimana mengatasi batas ROM 64 KB tanpa beralih ke assembler.


Untungnya, selain Bab 5, ada Referensi File 25,18 pgmspace.h lain, dari mana kita belajar bahwa keluarga fungsi pgm_read_* hanya pgm_read_*_near untuk pgm_read_*_near , yang menerima alamat 16-bit, dan ada juga pgm_read_*_far , dan Anda dapat mengirimkan Alamat 32-bit Eureka!


Kami menulis kode:


 unsigned char element = pgm_read_byte_far(&(array2d[i][j])); 

Itu mengkompilasi, tetapi tidak berfungsi seperti yang kita inginkan (jika array2d terletak setelah 32 KB). Mengapa Ya, karena & operasi mengembalikan nomor 16-bit yang sudah ditandatangani! Lucu bahwa pgm_read_*_near keluarga pgm_read_*_near menerima alamat 16-bit yang tidak ditandatangani, yaitu, ia mampu bekerja dengan 64 KB data, dan operasi & hanya berguna untuk 32 KB.


Mari kita lanjutkan. Apa yang kita miliki di pgmspace.h selain pgm_read_* ? Fungsi pgm_get_far_address(var) , yang sudah memiliki setengah halaman deskripsi, dan menggantikan operasi & .


Mungkin benar:


 unsigned char element = pgm_read_byte_far(pgm_get_far_address(array2d[i][j])); 

Kesalahan kompilasi. Kita membaca deskripsi: 'var' harus diselesaikan dengan menghubungkan waktu sebagai simbol yang ada, yaitu, nama variabel tipe sederhana, nama array (bukan elemen indeks array, jika indeks adalah konstanta, kompiler tidak mengeluh tetapi gagal mendapatkan alamat jika optimisasi diaktifkan), nama struct atau nama bidang struct, pengenal fungsi, pengenal yang ditentukan oleh linker, ...


Kami menempatkan kruk lain: kami beralih dari indeks array ke pointer aritmatika:


 unsigned char element = pgm_read_byte_far(pgm_get_far_address(array2d) + i*3*sizeof(unsigned char) + j*sizeof(unsigned char)); 

Sekarang semuanya berfungsi.


Kesimpulan


Jika Anda menulis dalam C / C ++ untuk mikrokontroler AVR 8-bit menggunakan kompiler GCC dan menyimpan data dalam ROM, maka:


  • dengan ukuran ROM tidak lebih dari 32 KB, Anda tidak akan mengalami masalah dengan hanya membaca Bab 5 Data dalam Ruang Program;
  • untuk ROM yang lebih besar dari 32 KB, Anda harus menggunakan keluarga fungsi pgm_read_*_far , fungsi pgm_get_far_address alih-alih & , aritmatika penunjuk alih-alih indeks array, dan ukuran objek apa pun tidak boleh melebihi 32.767 B.

Referensi


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


All Articles