Dokumentasi Arduino mengatakan, adalah mungkin untuk menjaga konstanta seperti string atau apa pun yang saya tidak ingin ubah selama runtime dalam memori program.
Semua konstanta awalnya dalam memori program. Di mana lagi mereka berada ketika listrik mati?
Saya pikir itu tertanam di suatu tempat di segmen kode, yang harus cukup mungkin di dalam arsitektur von-Neumann.
Ini sebenarnya arsitektur Harvard .
Kenapa saya harus menyalin konten sialan ke RAM sebelum mengaksesnya?
Kamu tidak. Bahkan ada instruksi perangkat keras (LPM - Load Program Memory) yang memindahkan data langsung dari memori program ke register.
Saya punya contoh teknik ini dalam output Arduino Uno ke monitor VGA . Dalam kode itu ada font bitmap yang disimpan dalam memori program. Itu dibaca dari on-the-fly dan disalin ke output seperti ini:
// blit pixel data to screen
while (i--)
UDR0 = pgm_read_byte (linePtr + (* messagePtr++));
Pembongkaran garis-garis tersebut menunjukkan (sebagian):
f1a: e4 91 lpm r30, Z+
f1c: e0 93 c6 00 sts 0x00C6, r30
Anda dapat melihat bahwa satu byte memori program disalin ke R30, dan kemudian segera disimpan ke dalam register USART UDR0. Tidak ada RAM yang terlibat.
Namun ada kerumitan. Untuk string normal, kompiler mengharapkan untuk menemukan data dalam RAM bukan PROGMEM. Mereka adalah ruang alamat yang berbeda, dan karena itu 0x200 dalam RAM adalah sesuatu yang berbeda dari 0x200 dalam PROGMEM. Jadi kompiler mengalami kesulitan menyalin konstanta (seperti string) ke dalam RAM pada saat startup program, sehingga tidak perlu khawatir mengetahui perbedaannya nanti.
Bagaimana kode (32kiB) ditangani kemudian dengan hanya 2kiB RAM?
Pertanyaan bagus. Anda tidak akan lolos dengan string konstan lebih dari 2 KB, karena tidak akan ada ruang untuk menyalin semuanya.
Itu sebabnya orang-orang yang menulis hal-hal seperti menu dan hal-hal bertele-tele lainnya, mengambil langkah-langkah tambahan untuk memberikan string atribut PROGMEM, yang menonaktifkan mereka disalin ke dalam RAM.
Tapi saya bingung dengan instruksi itu untuk hanya membaca dan mencetak data dari memori program:
Jika Anda menambahkan atribut PROGMEM Anda harus mengambil langkah-langkah untuk memberi tahu kompilator bahwa string ini berada dalam ruang alamat yang berbeda. Membuat salinan lengkap (sementara) adalah satu cara. Atau hanya mencetak langsung dari PROGMEM, satu byte setiap kali. Contohnya adalah:
// Print a string from Program Memory directly to save RAM
void printProgStr (const char * str)
{
char c;
if (!str)
return;
while ((c = pgm_read_byte(str++)))
Serial.print (c);
} // end of printProgStr
Jika Anda melewatkan fungsi ini sebagai penunjuk ke string di PROGMEM, ia melakukan "pembacaan khusus" (pgm_read_byte) untuk menarik data dari PROGMEM daripada RAM, dan mencetaknya. Perhatikan bahwa ini membutuhkan satu siklus clock tambahan, per byte.
Dan yang lebih menarik: Apa yang terjadi pada konstanta literal seperti dalam ungkapan ini a = 5*(10+7)
adalah 5, 10 dan 7 benar-benar disalin ke RAM sebelum memuatnya ke dalam register? Aku tidak percaya itu.
Tidak, karena mereka tidak harus seperti itu. Itu akan dikompilasi menjadi instruksi "muat literal ke register". Instruksi itu sudah ada dalam PROGMEM, jadi literal sekarang ditangani. Tidak perlu menyalinnya ke RAM dan kemudian membacanya kembali.
Saya memiliki deskripsi panjang tentang hal-hal ini di halaman Menempatkan data konstan ke dalam memori program (PROGMEM) . Itu memiliki kode contoh untuk mengatur string, dan array string, cukup mudah.
Itu juga menyebutkan makro F () yang merupakan cara mudah hanya mencetak dari PROGMEM:
Serial.println (F("Hello, world"));
Sedikit kompleksitas preprosesor memungkinkan kompilasi menjadi fungsi pembantu yang menarik byte dalam string dari PROGMEM byte pada suatu waktu. Tidak diperlukan penggunaan menengah RAM.
Cukup mudah untuk menggunakan teknik itu untuk hal-hal selain Serial (mis. LCD Anda) dengan menurunkan pencetakan LCD dari kelas Print.
Sebagai contoh, di salah satu perpustakaan LCD yang saya tulis, saya melakukan hal itu:
class I2C_graphical_LCD_display : public Print
{
...
size_t write(uint8_t c);
};
Poin kuncinya di sini adalah untuk memperoleh dari Print, dan menimpa fungsi "write". Sekarang fungsi utama Anda melakukan apa pun yang dibutuhkan untuk menghasilkan karakter. Karena berasal dari Print, Anda sekarang dapat menggunakan makro F (). misalnya.
lcd.println (F("Hello, world"));
string_table
array. Array itu mungkin 20KB, dan tidak akan pernah muat dalam memori (bahkan sementara). Namun Anda dapat memuat hanya satu indeks menggunakan metode di atas.