
Dalam aplikasi yang cukup besar, bagian penting dari proyek ini adalah logika bisnis. Lebih mudah untuk debug bagian ini dari program di komputer, dan kemudian menanamkannya dalam proyek untuk mikrokontroler, berharap bahwa bagian ini akan dieksekusi persis seperti yang dimaksudkan tanpa debugging (kasus ideal).
Karena sebagian besar program untuk mikrokontroler ditulis dalam C / C ++, untuk tujuan ini mereka biasanya menggunakan kelas abstrak yang menyediakan antarmuka ke entitas tingkat rendah (jika proyek ditulis hanya menggunakan C, struktur fungsi pointer sering digunakan). Pendekatan ini memberikan tingkat abstraksi yang diperlukan pada setrika, namun penuh dengan kebutuhan untuk kompilasi ulang proyek secara konstan, diikuti dengan pemrograman memori non-volatile dari mikrokontroler dengan file
firmware biner yang
besar .
Namun, ada cara lain - menggunakan bahasa skrip yang memungkinkan Anda untuk men-debug logika bisnis secara real time pada perangkat itu sendiri atau memuat skrip kerja langsung dari memori eksternal, tanpa menyertakan kode ini dalam firmware mikrokontroler.
Saya memilih Lua sebagai bahasa scripting.
Kenapa Lua?
Ada beberapa bahasa skrip yang dapat Anda sematkan dalam proyek untuk mikrokontroler. Beberapa BASIC-like sederhana, PyMite, Pawn ... Masing-masing memiliki pro dan kontra, sebuah diskusi yang tidak termasuk dalam daftar masalah yang dibahas dalam artikel ini.
Secara singkat tentang apa yang baik khususnya lua - dapat ditemukan dalam artikel
"Lua dalam 60 menit .
" Artikel ini banyak menginspirasi saya dan, untuk studi yang lebih rinci tentang masalah ini, saya membaca buku panduan resmi dari penulis bahasa Robert Jeruzalimsky "
Programming in Lua " (tersedia dalam terjemahan resmi Rusia).
Saya juga ingin menyebutkan proyek eLua. Dalam kasus saya, saya sudah memiliki lapisan tingkat rendah perangkat lunak yang siap pakai untuk berinteraksi dengan kedua periferal mikrokontroler dan periferal lain yang diperlukan yang terdapat di papan perangkat. Oleh karena itu, saya belum mempertimbangkan proyek ini (karena diakui memberikan lapisan untuk menghubungkan inti Lua dengan periferal mikrokontroler).
Tentang proyek di mana Lua akan tertanam
Secara tradisional ,
proyek kotak pasir saya akan digunakan sebagai kualitas bidang untuk eksperimen (tautan ke komit dengan lua yang sudah terintegrasi dengan semua perbaikan yang diperlukan yang dijelaskan di bawah).
Proyek ini didasarkan pada mikrokontroler stm32f405rgt6 dengan 1 MB non-volatile dan 192 KB RAM (2 blok yang lebih tua dengan total kapasitas 128 KB saat ini digunakan).
Proyek ini memiliki sistem operasi real-time FreeRTOS untuk mendukung infrastruktur perangkat keras. Semua memori untuk tugas, semafor, dan objek FreeRTOS lainnya dialokasikan secara statis pada tahap penautan (terletak di area .bss RAM). Semua entitas FreeRTOS (semaphore, antrian, tumpukan tugas, dll.) Adalah bagian dari objek global di area pribadi kelas mereka. Namun, tumpukan FreeRTOS masih dialokasikan untuk mendukung fungsi
malloc ,
bebas ,
calloc (diperlukan untuk fungsi seperti
printf ) yang didefinisikan ulang untuk bekerja dengannya. Ada API yang dinaikkan untuk bekerja dengan kartu MicroSD (FatFS), serta men-debug UART (115200, 8N1).
Tentang logika menggunakan Lua sebagai bagian dari proyek
Untuk keperluan debugging business logic, diasumsikan bahwa perintah akan dikirim melalui UART, dikemas (sebagai objek terpisah) ke dalam garis jadi (diakhiri dengan karakter "\ n" + 0-terminator) dan dikirim ke mesin lua. Jika eksekusi tidak berhasil, output melalui printf (karena
sebelumnya terlibat dalam proyek). Ketika logika debugged, dimungkinkan untuk mengunduh file logika bisnis akhir dari file dari kartu microSD (tidak termasuk dalam materi artikel ini). Juga, untuk keperluan debug Lua, mesin akan dieksekusi di dalam utas FreeRTOS yang terpisah (di masa depan, utas terpisah akan dialokasikan untuk setiap skrip logika bisnis yang di-debug di mana ia akan dieksekusi dengan lingkungannya).
Pencantuman lua submodule dalam proyek
Cermin resmi proyek di github akan digunakan sebagai sumber perpustakaan lua (karena proyek saya juga diposting di sana. Anda dapat menggunakan sumber langsung dari
situs resmi ). Karena proyek memiliki sistem yang mapan untuk mengumpulkan submodul sebagai bagian dari proyek, CMakeLists individual untuk masing-masing submodule, saya membuat
submodule terpisah di mana saya memasukkan fork ini dan CMakeLists untuk mempertahankan gaya build tunggal.
CMakeLists membangun sumber repositori lua sebagai perpustakaan statis dengan flag kompilasi submodule berikut (diambil dari
file konfigurasi submodule di proyek utama):
SET(C_COMPILER_FLAGS "-std=gnu99;-fshort-enums;-fno-exceptions;-Wno-type-limits;-ffunction-sections;-fdata-sections;") SET(MODULE_LUA_COMP_FLAGS "-O0;-g3;${C_COMPILER_FLAGS}"
Dan bendera spesifikasi prosesor yang digunakan (diatur dalam
root CMakeLists ):
SET(HARDWARE_FLAGS -mthumb; -mcpu=cortex-m4; -mfloat-abi=hard; -mfpu=fpv4-sp-d16;)
Penting untuk mencatat perlunya CMakeLists root untuk menentukan definisi yang memungkinkan untuk tidak menggunakan nilai ganda (karena mikrokontroler tidak memiliki dukungan perangkat keras untuk ganda. Hanya float):
add_definitions(-DLUA_32BITS)
Yah, itu tetap hanya untuk menginformasikan linker tentang perlunya merakit perpustakaan ini dan memasukkan hasilnya dalam tata letak tugas akhir:
CMakeLists merencanakan untuk menghubungkan proyek dengan perpustakaan lua add_subdirectory(${CMAKE_SOURCE_DIR}/bsp/submodules/module_lua) ... target_link_libraries(${PROJECT_NAME}.elf PUBLIC
Menentukan fungsi untuk bekerja dengan memori
Karena Lua sendiri tidak berurusan dengan memori, tanggung jawab ini ditransfer ke pengguna. Namun, ketika menggunakan pustaka
lauxlib yang dibundel dan fungsi
luaL_newstate darinya, fungsi
l_alloc terikat sebagai sistem memori. Ini didefinisikan sebagai berikut:
static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { (void)ud; (void)osize; if (nsize == 0) { free(ptr); return NULL; } else return realloc(ptr, nsize); }
Seperti yang disebutkan di awal artikel, proyek ini sudah memiliki fungsi
malloc dan
bebas , tetapi tidak ada fungsi
realokasi . Kami harus memperbaikinya.
Dalam mekanisme standar untuk bekerja dengan tumpukan FreeRTOS, file heap_4.c yang digunakan dalam proyek tidak memiliki fungsi untuk mengubah ukuran blok memori yang dialokasikan sebelumnya. Dalam hal ini, perlu untuk membuat implementasinya atas dasar
malloc dan
gratis .
Karena di masa depan dimungkinkan untuk mengubah skema alokasi memori (menggunakan file heap_x.c lain), diputuskan untuk tidak menggunakan interior dari skema saat ini (heap_4.c), tetapi untuk membuat tambahan tingkat yang lebih tinggi. Meski kurang efektif.
Penting untuk dicatat bahwa metode
realloc tidak hanya menghapus blok lama (jika ada) dan membuat yang baru, tetapi juga memindahkan data dari blok lama ke yang baru. Selain itu, jika blok lama memiliki lebih banyak data daripada yang baru, yang baru diisi dengan yang lama hingga batasnya, dan data yang tersisa dibuang.
Jika fakta ini tidak diperhitungkan, maka mesin Anda akan dapat menjalankan skrip seperti itu tiga kali dari baris "
a = 3 \ n ", setelah itu akan jatuh ke dalam kesalahan yang sulit. Masalahnya dapat dipecahkan setelah mempelajari gambar residual register di penangan kesalahan yang keras, dari mana akan mungkin untuk mengetahui bahwa kecelakaan itu terjadi setelah mencoba memperluas tabel dalam isi kode penerjemah dan perpustakaannya. Jika Anda memanggil skrip seperti "
cetak 'tes' ", maka perilaku akan berubah tergantung pada bagaimana file firmware dirakit (dengan kata lain, perilaku tersebut tidak terdefinisi).
Untuk menyalin data dari blok lama ke yang baru, kita perlu mengetahui ukuran blok yang lama. FreeRTOS heap_4.c (seperti file lain yang menyediakan metode penanganan tumpukan) tidak menyediakan API untuk ini. Karena itu, Anda harus menyelesaikannya. Sebagai dasar, saya mengambil fungsi
vPortFree dan memotong fungsinya ke bentuk berikut:
Kode Fungsi VPortGetSizeBlock int vPortGetSizeBlock (void *pv) { uint8_t *puc = (uint8_t *)pv; BlockLink_t *pxLink; if (pv != NULL) { puc -= xHeapStructSize; pxLink = (BlockLink_t *)puc; configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0); configASSERT(pxLink->pxNextFreeBlock == NULL); return pxLink->xBlockSize & ~xBlockAllocatedBit; } return 0; }
Sekarang kecil, tulis
realloc berdasarkan
malloc ,
gratis , dan
vPortGetSizeBlock :
Realloc kode implementasi berdasarkan pada malloc, gratis, dan vPortGetSizeBlock void *realloc (void *ptr, size_t new_size) { if (ptr == nullptr) { return malloc(new_size); } void* p = malloc(new_size); if (p == nullptr) { return p; } size_t old_size = vPortGetSizeBlock(ptr); size_t cpy_len = (new_size < old_size)?new_size:old_size; memcpy(p, ptr, cpy_len); free(ptr); return p; }
Tambahkan dukungan untuk bekerja dengan stdout
Sebagaimana diketahui dari deskripsi resmi, penerjemah lua itu sendiri tidak dapat bekerja dengan I / O. Untuk keperluan ini, salah satu perpustakaan standar terhubung. Untuk output, ia menggunakan aliran
stdout . Fungsi
luaopen_io dari perpustakaan standar bertanggung jawab untuk menghubungkan ke aliran. Untuk mendukung bekerja dengan
stdout (tidak seperti
printf ), Anda harus mengganti fungsi
fwrite . Saya mendefinisikan ulang berdasarkan fungsi yang dijelaskan dalam
artikel sebelumnya .
Fungsi penulisan size_t fwrite(const void *buf, size_t size, size_t count, FILE *stream) { stream = stream; size_t len = size * count; const char *s = reinterpret_cast<const char*>(buf); for (size_t i = 0; i < len; i++) { if (_write_char((s[i])) != 0) { return -1; } } return len; }
Tanpa definisinya, fungsi
cetak di lua akan berhasil dijalankan, tetapi tidak akan ada keluaran. Selain itu, tidak akan ada kesalahan pada tumpukan Lua dari mesin (karena secara formal fungsi ini dijalankan dengan sukses).
Selain fungsi ini, kita akan memerlukan fungsi
fflush (untuk mode interaktif berfungsi, yang akan
dibahas nanti). Karena fungsi ini tidak dapat ditimpa, Anda harus menamainya sedikit berbeda. Fungsi ini merupakan versi
singkat dari fungsi
fwrite dan dimaksudkan untuk mengirim apa yang sekarang ada di buffer dengan pembersihan berikutnya (tanpa transfer carriage tambahan).
Fungsi Mc_fflush int mc_fflush () { uint32_t len = buf_p; buf_p = 0; if (uart_1.tx(tx_buf, len, 100) != mc_interfaces::res::ok) { errno = EIO; return -1; } return 0; }
Mengambil string dari port serial
Untuk mendapatkan string untuk mesin lua, saya memutuskan untuk menulis kelas terminal-uart sederhana, yang:
- menerima data pada port serial byte-by-byte (in interrupt);
- menambahkan byte yang diterima ke antrian, tempat aliran menerimanya dari;
- dalam aliran byte, jika ini bukan umpan baris, dikirim kembali dalam bentuk yang datangnya;
- jika umpan baris telah tiba (' \ r '), maka 2 byte dari carriage return terminal dikirim (" \ n \ r ");
- setelah mengirim respons, pawang byte yang tiba (objek tata letak baris) dipanggil;
- mengontrol penekanan tombol karakter hapus (untuk menghindari penghapusan karakter layanan dari jendela terminal);
Tautan ke sumber:
- Antarmuka kelas UART ada di sini ;
- Kelas dasar UART ada di sini dan di sini ;
- kelas uart_terminal di sini dan di sini ;
- membuat objek kelas sebagai bagian dari proyek di sini .
Selain itu, saya perhatikan bahwa agar objek ini berfungsi dengan baik, Anda perlu menetapkan prioritas untuk gangguan UART dalam rentang yang diizinkan untuk bekerja dengan fungsi FreeRTOS dari interupsi. Jika tidak, Anda bisa mendapatkan kesalahan sulit yang sulit di-debug. Dalam contoh saat ini, opsi berikut untuk interupsi diatur dalam file
FreeRTOSConfig.h .
Pengaturan di FreeRTOSConfig.h #define configPRIO_BITS 4 #define configKERNEL_INTERRUPT_PRIORITY 0XF0
Dalam proyek itu sendiri, objek class
nvic menetapkan prioritas interrupt 0x9, yang termasuk dalam rentang yang valid (class nvic dijelaskan di
sini dan di
sini ).
Formasi string untuk mesin Lua
Bytes yang diterima dari objek uart_terminal ditransfer ke instance dari serial_cli kelas sederhana, yang menyediakan antarmuka minimal untuk mengedit string dan mentransfernya langsung ke utas di mana mesin lua dieksekusi (dengan memanggil fungsi callback). Setelah menerima karakter '\ r', fungsi panggilan balik dipanggil. Fungsi ini harus menyalin garis ke dirinya sendiri dan "melepaskan" kontrol (karena penerimaan byte baru diblokir selama panggilan. Ini bukan masalah dengan aliran diprioritaskan dengan benar dan kecepatan UART yang cukup rendah).
Tautan ke sumber:
- file deskripsi serial_cli di sini dan di sini ;
- membuat objek kelas sebagai bagian dari proyek di sini .
Penting untuk dicatat bahwa kelas ini menganggap string yang lebih panjang dari 255 karakter tidak valid dan membuangnya. Ini disengaja, karena penerjemah lua memungkinkan Anda untuk memasukkan konstruk baris demi baris, menunggu akhir blok.
Mengirimkan string ke juru bahasa Lua dan pelaksanaannya
Penerjemah Lua itu sendiri tidak tahu bagaimana menerima kode blok baris demi baris, dan kemudian menjalankan seluruh blok itu sendiri. Namun, jika Anda menginstal Lua pada komputer dan menjalankan interpreter dalam mode interaktif, kita dapat melihat bahwa eksekusi dilakukan baris demi baris dengan notasi yang sesuai saat Anda mengetik, bahwa blok belum selesai. Karena mode interaktif adalah apa yang disediakan dalam paket standar, kita dapat melihat kodenya. Itu terletak di file
lua.c. Kami tertarik pada fungsi
doREPL dan semua yang digunakannya. Agar tidak muncul dengan sepeda, untuk mendapatkan fungsi mode interaktif dalam proyek, saya membuat port kode ini di kelas yang terpisah, yang saya beri nama
lua_repl dengan nama fungsi asli, yang menggunakan printf untuk menampilkan informasi ke konsol dan memiliki metode
add_lua_string publik untuk menambahkan baris yang diterima dari objek kelas. serial_cli dijelaskan di atas.
Referensi:
Kelas dibuat sesuai dengan pola singleton Myers, karena tidak perlu memberikan beberapa mode interaktif dalam perangkat yang sama. Objek kelas lua_repl menerima data dari objek kelas serial_cli di
sini .
Karena proyek sudah memiliki sistem terpadu untuk menginisialisasi dan melayani objek global, penunjuk ke objek kelas lua_repl diteruskan ke objek
pemain kelas global
:: pangkalan di sini . Dalam metode
awal objek
pemain kelas
:: basis (dideklarasikan di
sini . Disebut juga dari main), metode
init dari objek kelas lua_repl dipanggil dengan prioritas tugas FreeRTOS 3 (dalam proyek, Anda dapat menetapkan prioritas tugas dari 1 hingga 4. Di mana 1 Apakah prioritas terendah, dan 4 adalah yang tertinggi). Setelah inisialisasi berhasil, kelas global memulai penjadwal FreeRTOS dan mode interaktif memulai kerjanya.
Masalah Porting
Di bawah ini adalah daftar masalah yang saya temui selama port Lua dari mesin.
2-3 skrip baris tunggal penugasan variabel dieksekusi, maka semuanya jatuh ke dalam kesalahan yang berat
Masalahnya adalah dengan metode realokasi. Diperlukan tidak hanya untuk memilih kembali blok, tetapi juga untuk menyalin konten yang lama (seperti dijelaskan di atas).
Saat mencoba mencetak nilai, penerjemah jatuh ke kesalahan yang sulit
Sudah lebih sulit untuk mendeteksi masalah, tetapi pada akhirnya saya berhasil mengetahui bahwa snprintf digunakan untuk mencetak. Karena lua menyimpan nilai dalam dobel (atau mengapung dalam kasus kami), printf (dan turunannya) dengan dukungan floating point diperlukan (saya menulis tentang seluk-beluk printf di
sini ).
Persyaratan untuk memori non-volatile (flash)
Berikut adalah beberapa pengukuran yang saya lakukan untuk menilai berapa banyak memori (flash) non-volatile perlu dialokasikan untuk mengintegrasikan mesin Lua ke dalam proyek. Kompilasi dilakukan dengan menggunakan gcc-arm-none-eabi-8-2018-q4-major. Versi Lua 5.4 digunakan. Di bawah ini dalam pengukuran, frasa βtanpa Luaβ berarti non-inklusi dari interpreter dan metode interaksi dengan itu dan perpustakaannya, serta objek kelas lua_repl dalam proyek. Semua entitas tingkat rendah (termasuk penggantian untuk fungsi
printf dan
fwrite ) tetap ada dalam proyek. Ukuran tumpukan FreeRTOS adalah 1024 * 25 byte. Sisanya ditempati oleh entitas proyek global.
Tabel ringkasan hasil adalah sebagai berikut (semua ukuran dalam byte):
Opsi pembuatan | Tanpa lua | Hanya inti | Lua dengan perpustakaan dasar | Lua dengan pangkalan perpustakaan, coroutine, tabel, string | luaL_openlibs |
---|
-O0 -g3 | 103028 | 220924 | 236124 | 262652 | 308372 |
-O1 -g3 | 74940 | 144732 | 156916 | 174452 | 213068 |
-Os -g0 | 71172 | 134228 | 145756 | 161428 | 198400 |
Persyaratan RAM
Karena konsumsi RAM sepenuhnya tergantung pada tugas, saya akan memberikan tabel ringkasan dari memori yang dikonsumsi segera setelah menyalakan mesin dengan satu set perpustakaan yang berbeda (ditampilkan oleh
cetakan (collectgarbage ("count") * 1024 perintah).
Komposisi | RAM yang digunakan |
Lua dengan perpustakaan dasar | 4809 |
Lua dengan pangkalan perpustakaan, coroutine, tabel, string | 6407 |
luaL_openlibs | 12769 |
Dalam hal menggunakan semua pustaka, ukuran RAM yang diperlukan meningkat secara signifikan dibandingkan dengan set sebelumnya. Namun, penggunaannya dalam sebagian besar aplikasi tidak diperlukan.
Selain itu, 4 kb juga dialokasikan ke tumpukan tugas, di mana mesin Lua dijalankan.
Penggunaan lebih lanjut
Untuk penggunaan penuh mesin dalam proyek, Anda perlu menjelaskan lebih lanjut semua antarmuka yang diperlukan oleh kode logika bisnis untuk perangkat keras atau objek layanan proyek. Namun, ini adalah topik dari artikel terpisah.
Ringkasan
Artikel ini menjelaskan cara menghubungkan mesin Lua ke proyek untuk mikrokontroler, serta meluncurkan interpreter interaktif penuh yang memungkinkan Anda untuk bereksperimen dengan logika bisnis langsung dari baris perintah terminal. Selain itu, persyaratan untuk perangkat keras mikrokontroler dipertimbangkan untuk konfigurasi yang berbeda dari mesin Lua.