
Saya adalah pemilik perangkat yang luar biasa - Pencatat GPS Holux M-241. Masalahnya sangat nyaman dan berguna saat bepergian. Dengan bantuan logger, saya menulis trek perjalanan GPS, di mana Anda kemudian dapat melihat jalan Anda secara rinci, dan juga melampirkan foto yang Anda ambil ke koordinat GPS. Dia juga memiliki layar kecil yang menunjukkan informasi tambahan - jam, kecepatan saat ini, ketinggian dan arah, odometer dan banyak lagi.
Di sini saya pernah menulis ulasan singkat.
Dengan semua kelebihan sepotong besi, saya mulai tumbuh darinya. Saya kehilangan beberapa barang kecil tapi berguna: beberapa odometer, menunjukkan kecepatan vertikal, mengukur parameter bagian lintasan. Sepertinya hal-hal kecil, tetapi perusahaan Holux menemukan ini tidak cukup berguna untuk implementasi di firmware. Selain itu, saya tidak menyukai beberapa parameter perangkat keras, dan beberapa hal telah menjadi usang dalam 10 tahun ...
Pada titik tertentu, saya menyadari bahwa saya sendiri dapat membuat logger dengan fitur yang saya butuhkan. Untungnya, semua komponen yang diperlukan cukup murah dan terjangkau. Saya mulai membuat implementasi berdasarkan Arduino. Di bawah potongan, buku harian konstruksi tempat saya mencoba melukis solusi teknis saya.
Menentukan fitur
Banyak yang akan bertanya mengapa saya perlu membuat logger sendiri, jika pasti ada sesuatu yang siap untuk produsen terkemuka. Mungkin Sejujurnya, saya tidak benar-benar mencarinya. Namun yang pasti akan ada sesuatu yang hilang. Bagaimanapun, proyek ini adalah penggemar bagi saya. Mengapa kita tidak mulai membangun perangkat impian kita?
Jadi, untuk apa saya menghargai Holux M-241 saya.
- Layar membuat "kotak hitam", yang hasilnya hanya tersedia setelah perjalanan, alat yang sangat nyaman, bacaan yang tersedia di sini dan sekarang. Memiliki layar memungkinkan hampir semua fitur pada daftar ini menjadi mungkin.
- Sebuah arloji berguna dalam dirinya sendiri. Pada perjalanan GPS, penebang yang menggantung pada tali di lehernya sering kali ternyata lebih dekat daripada ponsel di sakunya atau di ransel. Arloji mendukung semua zona waktu (meskipun dengan pengalihan manual)
- Tombol POI memungkinkan Anda untuk menandai koordinat saat ini di trek. Misalnya, untuk mencatat tengara yang telah menyelinap di luar jendela bus, tentang yang saya ingin google nanti.
- Dengan menggunakan odometer, Anda dapat mengukur jarak yang ditempuh dari beberapa titik. Misalnya, jarak yang ditempuh per hari, atau panjang lintasan.
- Kecepatan, ketinggian, dan arah saat ini membantu Anda menemukan diri Anda di luar angkasa
- Kelangsungan hidup 12-14 jam dari satu baterai AA dalam banyak kasus memungkinkan Anda untuk tidak memikirkan masalah catu daya. Yaitu biaya hampir selalu cukup untuk perjalanan sehari penuh.
- Kompak dan mudah digunakan - hal-hal di dunia modern sangat bagus
Namun, beberapa hal dapat dilakukan sedikit lebih baik:
- Subsistem daya pada baterai AA ditulis oleh banyak orang sebagai plus plus - satu baterai berlangsung lama, dan Anda dapat mengisi ulang pasokan di hutan belantara. Anda dapat menyimpan setidaknya satu bulan berkemah mandiri.
Tetapi bagi saya, daya tahan baterai adalah wasir belaka. Anda harus membawa beberapa baterai dan siapa yang tahu seberapa tinggi kualitas mereka (tiba-tiba mereka berbaring di rak selama 5 tahun dan sudah habis sendiri). Dengan baterai, perdarahan bahkan lebih besar. Pengisi daya saya hanya dapat mengisi daya secara berpasangan. Kita harus membuang baterai agar memiliki tingkat pengosongan yang sama. Akibatnya, Anda tidak pernah ingat di mana sudah habis, dan di mana belum.
Selama 6 tahun menggunakan logger, saya hanya berakhir di hutan belantara tanpa listrik beberapa kali. Sebagai aturan, saya mendapatkan akses ke outlet setidaknya sekali sehari. Dalam hal ini, baterai lithium bawaan akan jauh lebih nyaman. Nah, dalam kasus ekstrim, saya punya paverbank.
- Indikasi tingkat debit dibuat sangat bodoh - indikator mulai berkedip ketika baterai hampir habis. Selain itu, dapat mati dalam 5 menit, dan mungkin bekerja satu jam lagi. Sangat mudah untuk melewatkan momen ini dan kehilangan sebagian dari log.
- Sebagai orang yang tertarik dalam penerbangan, akan sangat menarik bagi saya untuk mengamati kecepatan vertikal saat ini .
- Beberapa odometer - seringkali menarik untuk mengukur lebih dari satu jarak. Misalnya, jarak yang ditempuh per hari dan untuk seluruh perjalanan.
- Odometer diatur ulang saat Anda mematikan perangkat atau saat mengganti baterai. Ini sangat tidak nyaman. Jika Anda berhenti untuk makan di kafe, maka pencatat GPS tidak dapat dimatikan karena nilainya akan diatur ulang. Dia harus membiarkannya dihidupkan dan dia terus berputar beberapa kilometer dan memakan baterai. Akan jauh lebih nyaman untuk dapat menempatkan odometer pada jeda dan menyimpan nilai di antara inklusi.
- Pengukuran parameter situs . Ketika bermain ski, misalnya, saya tertarik pada panjangnya keturunan, ketinggian, rata-rata dan kecepatan maksimum di situs, waktu yang dihabiskan. Apa yang ingin Anda ketahui adalah segera, dan tidak di rumah ketika Anda mengunduh trek.
- Akurasi buruk. Ketika Anda bergerak cepat - tidak ada yang lain. Tetapi ketika kecepatannya kecil di trek, "suara" + - 50m terlihat jelas. Dan selama satu jam berdiri, Anda bisa "mendesak" hampir satu kilometer. Manfaat teknologi selama 10 tahun telah berjalan jauh ke depan dan penerima modern memberikan akurasi yang jauh lebih besar.
- Kecepatan penggabungan trek hanya 38.400. Tidak, yah, pada tahun 2017 tidak serius untuk menggunakan port COM untuk mentransfer sejumlah besar data. Menggabungkan 2 megabita flash internal membutuhkan waktu lebih dari 20 menit.
Selain itu, tidak setiap program dapat mencerna format trek yang digabungkan. Utilitas asli sangat menyedihkan. Untungnya, ada BT747, yang dapat secara memadai menggabungkan trek dan mengubahnya menjadi semacam format yang dapat dicerna.
- Ukuran flash drive hanya 2MB. Di satu sisi, ini cukup untuk perjalanan dua minggu dengan poin penghematan setiap 5 detik. Tapi pertama-tama, format internal yang dikemas
membutuhkan konversi, dan yang kedua tidak memungkinkan untuk meningkatkan volume - Perangkat penyimpanan massal untuk beberapa alasan sekarang tidak dalam mode. Antarmuka modern berusaha menyembunyikan fakta keberadaan file. Saya telah menggunakan komputer selama 25 tahun, dan bekerja dengan file secara langsung jauh lebih nyaman bagi saya daripada dengan cara lain.
Tidak ada apa pun di sini yang tidak dapat diwujudkan tanpa upaya yang signifikan.
Ada yang berbeda. Saya tidak menggunakannya sendiri, tetapi tiba-tiba seseorang berguna:
- Memperlihatkan koordinat saat ini (lintang, bujur)
- Ikon-ikon yang berbeda digambar di sisi kiri layar, esensi yang bahkan tidak dapat saya ingat tanpa manual.
- Ada switching meter / km - kaki / mil.
- Pencatat Bluetooth dapat dihubungkan ke ponsel tanpa GPS.
- Jarak absolut ke titik.
- Logging berdasarkan waktu (setiap N detik) atau berdasarkan jarak (setiap X meter).
- Dukungan untuk berbagai bahasa.
Pilih besi
Persyaratannya kurang lebih didefinisikan. Sudah waktunya untuk memahami bagaimana semua ini dapat diimplementasikan. Komponen utama yang akan saya miliki adalah:
- Mikrokontroler - Saya tidak punya rencana untuk algoritma komputasi canggih, sehingga kekuatan pemrosesan kernel tidak terlalu penting. Saya juga tidak memiliki persyaratan khusus untuk pengisian - satu set periferal standar akan dilakukan.
Di tangan hanya sebaran arduino aneka, serta beberapa stm32f103c8t6. Saya memutuskan untuk memulai dengan AVR, yang saya ketahui dengan baik di level controller / register / peripheral. Jika saya mengalami pembatasan - akan ada alasan untuk merasakan STM32.
- Penerima GPS dipilih dari modul NEO6MV2, Beitan BN-800 dan Beitan BN-880. Forum yang di-Google untuk beberapa waktu. Orang yang berpengalaman mengatakan bahwa penerima pertama adalah abad terakhir. Dua lainnya berbeda satu sama lain hanya di lokasi antena - di BN-800 itu tergantung pada kabel, dan di BN-880 itu dilem dengan sandwich ke modul utama. Butuh BN-880 .
- Layar - aslinya menggunakan LCD 128 x 32 dengan lampu latar. Saya tidak menemukan yang persis sama. Saya membeli OLED 0,91 ”pada pengontrol SSD1306 dan layar LCD 1,2” pada pengontrol ST7565R . Saya memutuskan untuk memulai dari yang pertama, karena lebih mudah untuk terhubung dengan sisir standar sesuai dengan I2C atau SPI. Tapi itu sedikit lebih kecil dibandingkan dengan aslinya, dan juga tidak akan bekerja di atasnya untuk terus menampilkan gambar karena alasan efisiensi bahan bakar. Tampilan kedua seharusnya tidak terlalu rakus, tetapi Anda harus menyolder konektor yang rumit untuknya dan mencari tahu cara menyalakan lampu latar.
Dari hal-hal kecil:
- Tombol pernah membeli seluruh tas;
- Melindungi dengan untuk kartu SD - juga berbaring di sekitar;
- Saya membeli sepasang pengontrol biaya yang berbeda untuk baterai lithium, tetapi saya masih tidak memahaminya.
Saya memutuskan untuk merancang papan di bagian paling akhir, ketika firmware sudah siap. Pada saat ini, saya akhirnya akan memutuskan komponen utama dan skema untuk dimasukkannya. Pada tahap pertama, saya memutuskan untuk melakukan debugging di papan tempat memotong roti dengan menghubungkan komponen menggunakan kabel patch.
Tetapi pertama-tama Anda harus memutuskan masalah yang sangat penting - nutrisi komponen. Menurut saya masuk akal untuk menyalakan segala sesuatu mulai dari 3.3V: GPS dan layar hanya di atasnya dan tahu cara bekerja. Ini juga tegangan asli untuk USB dan SD. Selain itu, sirkuit dapat ditenagai dari satu kaleng lithium.
Pilihannya jatuh pada Arduino Pro Mini, yang dapat ditemukan dalam versi 8MHz / 3.3V. Tapi dia tidak punya USB on board - saya harus menggunakan adaptor USB-UART.
Langkah pertama
Awalnya, proyek ini dibuat di Arduino IDE. Tapi jujur saja, bahasa saya tidak berani menyebutnya IDE - seperti editor teks dengan kompiler. Bagaimanapun, setelah Visual Studio, di mana saya telah bekerja selama 13 tahun terakhir, saya tidak dapat melakukan sesuatu yang serius di IDE Arduino tanpa air mata dan matyuk.
Untungnya, ada Atmel Studio gratis, di mana bahkan Visual Assist sudah ada di dalam kotak !!! Program tahu semua yang dibutuhkan, semuanya sudah akrab dan sebagai gantinya. Ya, hampir semuanya (saya tidak menemukan cara mengkompilasi hanya satu file, misalnya, untuk memeriksa sintaks)

Dimulai dari layar - ini diperlukan untuk men-debug kerangka firmware, lalu mengisinya dengan fungsionalitas. Dia berhenti di
perpustakaan pertama yang tersedia untuk Adafruit SSD1306 . Dia tahu semua yang dibutuhkan dan menyediakan antarmuka yang sangat sederhana.
Dimainkan dengan font. Ternyata satu font dapat memakan waktu hingga 8 kb (ukuran hurufnya 24pt) - Anda tidak dapat terutama memutar-mutar di pengontrol 32kb. Font besar diperlukan, misalnya, untuk menampilkan waktu.
Kode Contoh Huruf#include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <gfxfont.h> #include <fonts/FreeMono12pt7b.h> #include <fonts/FreeMono18pt7b.h> ... #include <fonts/FreeSerifItalic24pt7b.h> #include <fonts/FreeSerifItalic9pt7b.h> #include <fonts/TomThumb.h> struct font_and_name { const char * PROGMEM name; GFXfont * font; }; #define FONT(name) {#name, &name} const font_and_name fonts[] = { // FONT(FreeMono12pt7b), FONT(FreeMono18pt7b), /* FONT(FreeMono24pt7b), FONT(FreeMono9pt7b), FONT(FreeMonoBold12pt7b), ... FONT(FreeSerifItalic9pt7b), FONT(TomThumb)*/ }; const unsigned int fonts_count = sizeof(fonts) / sizeof(font_and_name); unsigned int current_font = 0; extern Adafruit_SSD1306 display; void RunFontTest() { display.clearDisplay(); display.setCursor(0,30); display.setFont(fonts[current_font].font); display.print("12:34:56"); display.setCursor(0,6); display.setFont(&TomThumb); display.print(fonts[current_font].name); display.display(); } void SwitchToNextFont() { current_font = ++current_font % fonts_count; }
Font lengkap dengan pustaka sangat canggung. Font monospace ternyata sangat lebar - garis "12:34:56" tidak cocok, Serif - semua angka memiliki bobot yang berbeda. Kecuali font 5x7 standar di perpustakaan terlihat dapat dimakan.


Ternyata font-font ini dikonversi dari beberapa font open source ttf yang sama sekali tidak dioptimalkan untuk resolusi kecil.
Saya harus menggambar font saya. Lebih tepatnya, pertama, gali masing-masing simbol dari yang sudah jadi. Simbol ':' dalam tabel ASCII sangat berguna tepat setelah angka dan dapat dibeli dalam satu blok. Anda juga dapat membuat font bukan untuk semua karakter, tetapi hanya untuk rentang, misalnya, dari 0x30 ('0') hingga 0x3a (':'). T.O. dari FreeSans18pt7b ternyata membuat font yang sangat ringkas hanya untuk karakter yang diperlukan. Benar, saya harus sedikit menyesuaikan lebar agar teks pas dengan lebar layar.
Ternyata font 18pt sebenarnya tingginya 25 piksel. Karena itu, ia sedikit cocok dengan prasasti lain

Tampilan terbalik, omong-omong, membantu untuk memahami di mana batas-batas area gambar sebenarnya dan bagaimana garis berada relatif terhadap batas ini - layar memiliki bingkai yang sangat besar.
Di-google untuk waktu yang lama font yang sudah jadi, tetapi mereka tidak cocok dalam ukuran, atau dalam bentuk, atau konten. Misalnya, di Internet, poros font 8x12 (dump generator karakter kartu VGA). Namun pada kenyataannya, font-font ini berukuran 6x8, yaitu banyak ruang berjalan - dalam hal resolusi dan ukuran kecil seperti milik saya itu sangat penting.
Saya harus menggambar font sendiri, karena format font perpustakaan Adafruit sangat sederhana. Saya menyiapkan gambar di Paint.net - Saya hanya menggambar huruf-huruf dalam font yang tepat, kemudian sedikit mengoreksi dengan pensil. Saya menyimpan gambar sebagai png, dan kemudian mengirimnya dengan cepat ke skrip python yang tertulis di lutut saya. Script ini menghasilkan kode setengah jadi yang sudah mengarahkan aturan di IDE tepat di kode hex.

Sebagai contoh, ini adalah bagaimana proses pembuatan font monospaced 8x12 dengan huruf kecil dan spasi tampak. Setiap karakter pada akhirnya ternyata sekitar 7x10, dan secara default menempati 10 byte. Mungkin saja untuk mengemas setiap karakter dalam 8-9 byte (perpustakaan memungkinkan ini), tapi saya tidak repot-repot. Selain itu, dalam formulir ini, Anda dapat mengedit masing-masing piksel langsung dalam kode.
Bingkai
Perangkat asli menyediakan antarmuka yang sangat sederhana dan nyaman. Informasi dikelompokkan ke dalam kategori yang ditampilkan dari masing-masing halaman (layar). Dengan menggunakan tombol, Anda dapat menelusuri halaman, dan menggunakan tombol kedua untuk memilih item saat ini atau melakukan tindakan yang ditunjukkan dalam tanda tangan di bawah tombol. Menurut saya pendekatan ini sangat nyaman dan tidak perlu mengubah apa pun.
Saya suka keindahan OOP, karena saya segera menyilaukan antarmuka kecil, setiap halaman mengimplementasikan antarmuka sesuai kebutuhan. Halaman tahu cara menggambar sendiri dan menerapkan reaksi terhadap tombol.
class Screen { Screen * nextScreen; public: Screen(); virtual ~Screen() {} virtual void drawScreen() = 0; virtual void drawHeader(); virtual void onSelButton(); virtual void onOkButton(); virtual PROGMEM const char * getSelButtonText(); virtual PROGMEM const char * getOkButtonText(); Screen * addScreen(Screen * screen); };
Tombol dapat melakukan berbagai tindakan tergantung pada layar saat ini. Oleh karena itu, di bagian atas layar dengan ketinggian 8 piksel, saya ditugaskan ke label untuk tombol. Teks untuk tanda tangan tergantung pada layar saat ini dan dikembalikan oleh fungsi virtual getSelButtonText () dan getOkButtonText (). Juga dalam item layanan tajuk seperti kekuatan sinyal GPS dan pengisian daya baterai masih akan ditampilkan. Layar ¾ yang tersisa tersedia untuk informasi yang berguna.
Seperti yang saya katakan, layar bisa dibalik, yang berarti bahwa di suatu tempat harus ada daftar objek untuk halaman yang berbeda. Apa yang lebih dari satu - layar dapat disarangkan, seperti submenu. Saya bahkan memulai kelas ScreenManager, yang seharusnya mengelola daftar ini, tetapi kemudian saya menemukan solusinya lebih mudah.
Jadi setiap layar hanya memiliki pointer ke yang berikutnya. Jika layar memungkinkan Anda untuk masuk ke submenu, maka ia menambahkan satu penunjuk lagi ke layar submenu ini
class Screen { Screen * nextScreen; … }; class ParentScreen : public Screen { Screen * childScreen; … };
Secara default, penangan tombol hanya memanggil fungsi ganti layar, melewatinya dengan pointer yang diinginkan. Fungsi ini ternyata sepele - hanya mengubah pointer ke layar saat ini. Untuk memastikan layar bersarang, saya membuat tumpukan kecil. Jadi seluruh manajer layar cocok dalam 25 baris dan 4 fungsi kecil.
Screen * screenStack[3]; int screenIdx = 0; void setCurrentScreen(Screen * screen) { screenStack[screenIdx] = screen; } Screen * getCurrentScreen() { return screenStack[screenIdx]; } void enterChildScreen(Screen * screen) { screenIdx++;
Benar, kode untuk mengisi struktur ini tidak terlihat sangat bagus, tetapi sejauh ini belum ditemukan dengan lebih baik.
Screen * createCurrentTimeScreen() { TimeZoneScreen * tzScreen = new TimeZoneScreen(1, 30); tzScreen = tzScreen->addScreen(new TimeZoneScreen(2, 45)); tzScreen = tzScreen->addScreen(new TimeZoneScreen(-3, 30));
Pikiran ituPenataan, tentu saja, ternyata indah, tetapi saya khawatir itu memakan banyak memori. Anda harus melawan diri sendiri dan zafigachit meja statis besar dengan pointer.
Silakan. Dalam implementasi antarmuka saya, saya ingin melakukan sesuatu seperti kotak pesan, pesan singkat yang akan muncul selama satu atau dua detik, dan kemudian menghilang. Misalnya, jika Anda menekan tombol POI (Tempat Menarik) di layar dengan koordinat saat ini, maka selain menulis titik ke trek, alangkah baiknya untuk menunjukkan kepada pengguna pesan "Waypoint Disimpan" (di perangkat asli, ikon tambahan hanya ditampilkan sebentar). Atau, ketika baterai hampir habis, "menghibur" pengguna dengan pesan.

Karena data dari GPS akan datang terus-menerus, tidak ada pembicaraan tentang fungsi pemblokiran. Oleh karena itu, saya harus menciptakan mesin keadaan sederhana (state machine), yang pada fungsi loop () akan memilih apa yang harus dilakukan - perlihatkan layar saat ini atau kotak pesan.
enum State { IDLE_DISPLAY_OFF, IDLE, MESSAGE_BOX, BUTTON_PRESSED, };
Juga nyaman untuk menangani penekanan tombol menggunakan mesin negara. Mungkin itu akan benar melalui interupsi, tetapi ternyata juga baik. Ini bekerja seperti ini: jika tombol ditekan dalam status IDLE, ingat waktu itu ditekan dan pergi ke status BUTTON_PRESSED. Dalam keadaan ini, kami menunggu hingga pengguna melepaskan tombol. Di sini kita bisa menghitung durasi ketika tombol ditekan. Respons singkat (<30 ms) diabaikan begitu saja - kemungkinan besar ini adalah pantulan kontak. Perjalanan panjang sudah bisa diartikan sebagai tombol tekan.
Saya berencana untuk menggunakan kedua penekanan tombol untuk tindakan biasa, dan panjang (> 1c) untuk fungsi khusus. Misalnya, tekan sebentar memulai / menjeda odometer, tekan lama akan mengatur ulang penghitung ke 0.
Mungkin negara bagian lain akan ditambahkan. Jadi, misalnya, dalam logger asli setelah beralih ke halaman berikutnya, nilai pada layar sering berubah, dan lebih jarang setelah beberapa detik - sekali per detik. Ini bisa dilakukan dengan menambahkan status lain.
Ketika bingkai sudah siap, saya sudah mulai menghubungkan GPS. Namun di sini ada nuansa yang membuat saya menunda tugas ini.
Pengoptimalan firmware
Sebelum melanjutkan, saya perlu terganggu oleh beberapa detail teknis. Faktanya adalah bahwa di sekitar tempat ini saya mulai menambah konsumsi memori. Ternyata garis yang dinyatakan secara sembarangan tanpa pengubah PROGMEM pada awal firmware disalin ke RAM dan memakan ruang di sana sepanjang waktu berjalan.
Berbagai arsitekturSingkatnya. Pada komputer besar,
arsitektur Von Neumann digunakan di mana kode dan data berada di ruang alamat yang sama. Yaitu data dari RAM dan ROM akan dibaca dengan cara yang sama.
Mikrokontroler biasanya menggunakan
arsitektur Harvard , tempat kode dan data dipisahkan. T.O. Anda harus menggunakan berbagai fungsi untuk membaca memori dan flash. Dari sudut pandang bahasa C / C ++, pointer terlihat sama, tetapi ketika menulis sebuah program, kita perlu tahu persis di mana tepatnya memori yang menunjuk pointer kita dan memanggil fungsi yang sesuai.
Untungnya, sebagian pengembang perpustakaan sudah menangani ini. Kelas utama pustaka tampilan - Adafruit_SSD1306 diwarisi dari kelas Print dari pustaka standar Arduino.
Ini memberi kami serangkaian modifikasi modifikasi metode cetak - untuk mencetak string, karakter tunggal, angka, dan lainnya. Jadi ia memiliki 2 fungsi terpisah untuk jalur pencetakan:
size_t print(const __FlashStringHelper *); size_t print(const char[]);
Yang pertama tahu bahwa Anda perlu mencetak garis dari flash drive dan memuatnya karakter per karakter. Yang kedua mencetak karakter dari RAM. Faktanya, kedua fungsi ini mengambil pointer ke string, hanya dari ruang alamat yang berbeda.
Untuk waktu yang lama saya mencari kode arduino untuk __FlashStringHelper ini untuk mempelajari cara memanggil fungsi print () yang diinginkan. Ternyata orang-orang melakukan trik: mereka hanya menyatakan jenis ini menggunakan deklarasi maju (tanpa menyatakan jenis itu sendiri) dan menulis makro yang melemparkan pointer ke garis dalam sekejap ke tipe __FlashStringHelper. Hanya untuk kompiler untuk memilih fungsi kelebihan beban yang diperlukan
class __FlashStringHelper; #define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))
Ini memungkinkan Anda untuk menulis seperti ini:
display.print(F(“String in flash memory”));
Tetapi tidak memungkinkan menulis seperti
const char text[] PROGMEM = "String in flash memory"; display.print(F(text));
Dan, tampaknya, perpustakaan tidak menyediakan apa pun yang bisa dilakukan dengan cara itu. Saya tahu bahwa tidak baik menggunakan potongan perpustakaan pribadi dalam kode saya, tetapi apa yang harus saya lakukan? Saya menggambar makro saya, yang melakukan apa yang saya butuhkan.
#define USE_PGM_STRING(x) reinterpret_cast<const __FlashStringHelper *>(x)
Jadi fungsi menggambar topi mulai terlihat seperti ini:
void Screen::drawHeader() { display.setFont(NULL); display.setCursor(20, 0); display.print('\x1e'); display.print(USE_PGM_STRING(getSelButtonText())); display.setCursor(80, 0); display.print('\x1e'); display.print(USE_PGM_STRING(getOkButtonText())); }
Yah, karena saya masuk ke bagian rendah dari firmware, saya memutuskan untuk mempelajari lebih dalam bagaimana semuanya bekerja di dalam.
Secara umum, orang-orang yang datang dengan Arduino perlu mendirikan sebuah monumen. Mereka membuat platform yang sederhana dan nyaman untuk pembuatan prototipe dan kerajinan tangan. Sejumlah besar orang dengan pengetahuan minimal tentang elektronik dan pemrograman dapat memasuki dunia Arduino. Tetapi semua ini mulus dan indah saat melakukan sampah seperti lampu tanda dengan LED atau membaca termometer. Begitu Anda mengayunkan sesuatu yang serius, Anda harus segera memahami lebih dalam dari yang Anda inginkan sejak awal.
Jadi, setelah setiap perpustakaan ditambahkan atau bahkan kelas, saya mencatat seberapa cepat konsumsi memori meningkat. Pada titik ini, saya sibuk dengan lebih dari 14 KB flash 32 KB dan 1300 byte RAM (dari 2k). Setiap gerakan yang ceroboh menambahkan 10 persen ke gerakan yang sudah digunakan. Tapi saya masih belum benar-benar menghubungkan perpustakaan GPS dan SD / FAT32, dan kucing itu sendiri menangis. Saya harus mengambil
checker disassembler dan mempelajari apa yang dilakukan kompiler.
Diam-diam saya berharap bahwa linker membuang fungsi yang tidak digunakan. Tetapi ternyata sebagian dari mereka menyisipkan linker hampir seluruhnya. Dalam firmware, saya menemukan fungsi menggambar garis dan beberapa lainnya dari perpustakaan bekerja dengan layar, meskipun dalam kode saya jelas tidak memanggil mereka pada waktu itu. Secara implisit, mereka juga tidak boleh disebut - mengapa saya memerlukan fungsi menggambar garis jika saya hanya menggambar huruf dari bitmap? Lebih dari 5.2kb tiba-tiba (dan itu tidak termasuk font).
Selain pustaka kontrol tampilan, saya juga menemukan:
- 2,6 kb - di SoftwareSerial (saya menariknya ke proyek di beberapa titik)
- 1,6 kb - I2C
- 1.3 kb - HardwareSerial
- 2 kb - TinyGPS
- 2,5 kb pada arduino yang sebenarnya (inisialisasi, pin, semua jenis tabel, timer utama untuk fungsi millis () dan delay ()),
Jumlahnya sangat indikatif, seperti pengoptimal serius mencampur kode. Beberapa fungsi dapat dimulai di satu tempat, dan kemudian yang lain dari perpustakaan lain, yang dipanggil dari yang pertama, dapat segera mengikutinya. Selain itu, cabang-cabang yang terpisah dari fungsi-fungsi ini dapat ditemukan di ujung flash lainnya.
Juga dalam kode yang saya temukan:
- Kontrol layar oleh SPI (meskipun saya sudah terhubung melalui I2C)
- Metode kelas dasar itu sendiri tidak disebut, karena didefinisikan ulang dalam ahli waris
- Destructors yang tidak pernah dipanggil oleh desain
- Fungsi menggambar (dan tidak semua - bagian dari fungsi yang masih dilemparkan oleh linker)
- malloc / gratis saat dalam kode saya semua objek pada dasarnya statis
Tetapi tidak hanya konsumsi memori flash, tetapi juga SRAM tumbuh dengan pesat:
- 130 byte - I2C
- 100 byte - SoftwareSerial
- 157 byte - Serial
- 558 byte - Tampilan (yang 512 adalah frame buffer)
Yang tidak kalah menghibur adalah bagian. Data. Ada sekitar 700 byte dan hal ini dimuat dari flash ke RAM di awal. Ternyata ada tempat yang dicadangkan untuk variabel dalam memori, dan bersama dengan nilai inisialisasi. Disini tinggal variabel-variabel dan konstanta yang Anda lupa deklarasikan sebagai const PROGMEM.
Di antara ini adalah array yang lumayan dengan "layar splash" layar - nilai-nilai awal dari frame buffer. Secara teoritis, jika Anda membuat layar display () segera setelah start, Anda dapat melihat tulisan bunga dan Prasasti, tetapi dalam kasus saya tidak ada gunanya menghabiskan memori flash untuk ini.
Bagian. Data juga berisi vtables. Mereka disalin ke memori dari flash drive, rupanya karena alasan efisiensi dalam runtime. Tetapi Anda harus mengorbankan sepotong RAM yang agak besar - lebih dari selusin kelas lebih dari 150 byte. Selain itu, tampaknya tidak ada kunci kompiler yang, mengorbankan kinerja, akan meninggalkan tabel virtual dalam memori flash.
Apa yang harus dilakukan? Saya belum tahu. Itu akan tergantung pada bagaimana konsumsi akan terus tumbuh. Untuk menemukan kusen yang baik perlu diperbaiki tanpa ampun. Kemungkinan besar saya harus menarik semua perpustakaan ke dalam proyek saya secara eksplisit dan kemudian membahasnya secara menyeluruh. Dan Anda mungkin juga harus menulis ulang beberapa bagian secara berbeda untuk mengoptimalkan memori. Atau beralih ke perangkat keras yang lebih kuat. Bagaimanapun, sekarang saya tahu tentang masalah dan ada strategi tentang cara memperbaikinya.
PEMBARUAN:Sedikit kemajuan dalam efisiensi sumber daya. Saya melakukan pembaruan untuk bagian ini, karena selanjutnya saya ingin fokus pada hal-hal yang sama sekali berbeda.
Dalam komentar ada beberapa kebingungan tentang penggunaan C ++. Secara khusus, mengapa dia begitu buruk dan menyimpan vtable dalam RAM yang berharga? Secara umum, fungsi virtual, konstruktor, dan destruktor adalah overhead. Mengapa Mari kita cari tahu!
Berikut ini adalah statistik tentang memori pada beberapa tahap proyek
Ukuran program: 15 458 byte (digunakan 50% dari maksimum 30 720 byte) (2,45 detik)
Penggunaan Memori Minimum: 1258 byte (61% dari maksimum 2048 byte)
Eksperimen No. 1 - tulis ulang ke C.
Saya melemparkan kelas, menulis ulang semuanya di atas meja dengan pointer ke fungsi.
Karena layar sebenarnya selalu memiliki struktur yang sama, semua anggota data telah menjadi variabel global biasa.Statistik setelah refactoringUkuran program: 14 568 byte (digunakan 47% dari maksimum 30 720 byte) (2,35 detik)Penggunaan Memori Minimum: 1176 byte (57% dari maksimum 2048 byte)Total. Memenangkan 900 byte flash dan 80 byte RAM. Apa yang tepatnya meninggalkan flash tidak menggali. 80 byte RAM hanya ukuran vtable. Semua data lain (anggota kelas) tetap ada.Saya harus mengatakan bahwa saya tidak merusak segalanya - saya hanya ingin melihat gambaran besarnya tanpa menghabiskan banyak waktu untuk itu. Setelah refactoring, saya "kehilangan" tangkapan layar bersarang. Dengan mereka, konsumsi akan sedikit lebih banyak.Tetapi yang paling penting dalam percobaan ini adalah kualitas kode telah menurun secara signifikan. Kode satu fungsional telah tersebar di beberapa file. Untuk beberapa bagian data, "satu pemilik" tidak ada lagi, beberapa modul mulai masuk ke memori orang lain. Kode telah menjadi luas dan jelek.Eksperimen # 2 - menekan byte dari C ++Saya memutar kembali eksperimen saya, saya memutuskan untuk meninggalkan semuanya sebagai kelas. Hanya kali ini, layar dilakukan pada objek yang didistribusikan secara statis. Struktur halaman di layar saya sudah diperbaiki. Anda dapat menentukannya pada tahap kompilasi tanpa menggunakan baru / hapus.Ukuran program: 15 408 byte (digunakan 50% dari maksimum 30 720 byte) (2,60 detik)Penggunaan Memori Minimum: 1273 byte (62% dari maksimum 2048 byte)Konsumsi RAM sedikit meningkat. Tapi ini, pada kenyataannya, menjadi lebih baik. Peningkatan konsumsi RAM dijelaskan oleh perpindahan objek dari heap ke area memori yang didistribusikan secara statis. Yaitu
sebenarnya, objek dibuat sebelumnya, tetapi ini tidak masuk ke statistik. Dan sekarang benda-benda ini diperhitungkan secara eksplisit.Tetapi untuk secara signifikan mengurangi konsumsi flash tidak bekerja. Kode masih berisi konstruktor sendiri, yang masih dipanggil saat startup. Yaitu
Kompiler tidak dapat "mengeksekusi" mereka terlebih dahulu dan meletakkan semua nilai di area yang dialokasikan sebelumnya. Dan masih ada destruktor dalam kode, meskipun jelas bagi landak bahwa objek tidak akan pernah dihapus.Dalam upaya untuk menyelamatkan setidaknya sedikit, saya menghapus semua destruktor dalam hierarki, dan khususnya destruktor virtual di kelas dasar. Idenya adalah untuk membebaskan beberapa byte di setiap vtable. Dan kemudian kejutan menunggu saya:Ukuran program: 14 704 byte (digunakan 48% dari maksimum 30 720 byte) (2,94 detik)Penggunaan Memori Minimum: 1211 byte (59% dari maksimum 2048 byte)Ternyata vtable tidak berjalan dengan satu pointer, tetapi sudah oleh 2. Selain itu, keduanya berkaitan dengan destruktor. Hanya satu destruktor kosong (terlihat untuk objek pada stack), dan yang lainnya dengan panggilan gratis, terlihat untuk objek pada heap (-12 byte RAM). Juga, variabel yang terkait dengan pinggul (8 byte) dan label objek yang tidak pernah dibuat (Layar, ParentScreen - 40 byte) hilang.Konsumsi flash telah menurun secara signifikan - sebesar 700 byte. Tidak hanya para destruktor itu sendiri, tetapi juga implementasi malloc / free / new / delete hilang. 700 byte untuk destruktor virtual kosong! 700 byte, Carl!Itu tidak akan bolak-balik, di sini semua angka di satu tempatIntinya: Konsumsi dalam C ++ ternyata hampir sama dengan di C. Tetapi pada saat yang sama, enkapsulasi, pewarisan dan polimorfisme adalah kekuatan. Saya siap membayar lebih untuk ini dengan beberapa peningkatan konsumsi. Mungkin saya tidak bisa menulis dengan indah di C, tapi mengapa, jika saya bisa menulis dengan indah di C ++?Kata penutup
Awalnya, saya ingin menulis satu artikel di akhir proyek. Tetapi karena uang kertas menumpuk dengan cepat saat pekerjaan berjalan, artikel itu mengancam menjadi sangat besar. Jadi saya memutuskan untuk memecahnya menjadi beberapa bagian. Pada bagian ini, saya berbicara tentang tahap persiapan: memahami apa yang sebenarnya saya inginkan, memilih platform, menerapkan kerangka kerja aplikasi.Pada bagian selanjutnya, saya berencana untuk beralih ke implementasi fungsi dasar - bekerja dengan GPS. Saya sudah menemukan beberapa garu yang menarik yang ingin saya ceritakan.Selama lebih dari 10 tahun saya belum memprogram secara serius untuk mikrokontroler. Ternyata saya agak dimanjakan oleh banyaknya sumber daya komputer besar dan sempit dalam realitas ATMega32. Oleh karena itu, saya harus memikirkan berbagai opsi cadangan, seperti memangkas fungsionalitas perpustakaan atau mendesain ulang aplikasi atas nama penggunaan memori yang efisien. Saya juga tidak mengecualikan kemungkinan beralih ke pengontrol yang lebih kuat - ATMega64 atau sesuatu dari jalur STM32.Berdasarkan gaya, artikel itu ternyata seperti majalah konstruksi. Dan saya akan dengan senang hati memberikan komentar yang membangun - belum terlambat untuk mengubah apa pun. Mereka yang ingin dapat bergabung dengan proyek saya di github .Akhir dari bagian pertama.Bagian kedua