Pertanyaan Membangun gambar firmware dua bagian menggunakan GCC toolchain


Saya memiliki beberapa firmware yang dibangun dengan GCC yang berjalan pada mikrokontroler berbasis ARM Cortex M0. Membangun saat ini menghasilkan gambar biner tunggal yang dapat ditulis ke dalam memori program mikrokontroler.

Untuk alasan yang berkaitan dengan pembaruan lapangan, saya perlu membagi gambar ini menjadi dua bagian yang dapat diperbarui secara terpisah. Saya akan memanggil ini Inti dan Aplikasi.

  • Inti: berisi tabel vektor interupsi, main() rutin, dan berbagai driver dan rutinitas perpustakaan. Ini akan ditempatkan di paruh pertama memori program.

  • Aplikasi: berisi kode khusus aplikasi. Ini akan ditempatkan di paruh kedua dari memori program. Ini akan memiliki satu titik masuk, di alamat yang dikenal, yang disebut oleh inti untuk memulai aplikasi. Ini akan mengakses fungsi dan data di intinya melalui alamat yang dikenal.

Ada beberapa batasan yang jelas di sini, yang saya sadari:

  • Saat membangun aplikasi, alamat simbol di inti perlu diketahui. Jadi intinya harus dibangun terlebih dahulu, dan harus tersedia ketika menghubungkan aplikasi.

  • Gambar aplikasi hanya akan kompatibel dengan gambar inti spesifik yang dibangun terhadapnya.

  • Akan mungkin untuk memperbarui aplikasi tanpa memperbarui inti, tetapi tidak sebaliknya.

Semua itu baik-baik saja.

Pertanyaan saya adalah sederhana, bagaimana saya bisa membangun gambar-gambar ini menggunakan GCC dan GNU binutils?

Intinya saya ingin membangun inti seperti gambar firmware normal, dan kemudian membangun gambar aplikasi, dengan aplikasi yang memperlakukan inti seperti perpustakaan. Tetapi tidak berbagi tautan (yang akan membutuhkan mekanisme penautan yang dinamis) atau tautan statis (yang akan menyalin fungsi inti yang digunakan ke dalam biner aplikasi) berlaku di sini. Yang saya coba lakukan sebenarnya jauh lebih sederhana: tautan terhadap biner yang ada menggunakan alamat tetapnya yang sudah diketahui. Tidak jelas bagi saya bagaimana melakukannya dengan alat.


6
2018-02-03 17:35


asal


Jawaban:


Kami memiliki ini bekerja sekarang jadi saya akan menjawab pertanyaan saya sendiri. Inilah yang diperlukan untuk melakukan ini, mulai dari membangun satu gambar normal, mengubah itu menjadi "inti" dan kemudian mengatur membangun untuk "app".

  1. Tentukan cara membagi kedua flash dan RAM ke area terpisah untuk inti dan aplikasi. Tentukan alamat awal dan ukuran masing-masing area.

  2. Buat skrip tautan untuk intinya. Ini akan sama dengan skrip tautan standar untuk platform kecuali bahwa itu hanya harus menggunakan area yang dicadangkan untuk inti. Ini bisa dilakukan dengan mengubah ORIGIN dan LENGTH dari entri flash & RAM di MEMORY bagian dari skrip tautan.

  3. Buat file header yang menyatakan titik masuk untuk aplikasi. Ini hanya membutuhkan contoh prototipe:

void app_init(void);.

  1. Sertakan tajuk ini dari kode inti C dan memiliki panggilan inti app_init() untuk memulai aplikasi.

  2. Buat file simbol yang menyatakan alamat titik masuk, yang akan menjadi alamat awal dari area flash untuk aplikasi. Saya akan menyebutnya app.sym. Itu hanya bisa menjadi satu baris dalam format berikut:

app_init = 0x00010000;

  1. Bangun inti, gunakan skrip tautan utama dan tambahkan --just-symbols=app.sym ke parameter penaut untuk memberi alamat app_init. Simpan file ELF dari build, yang akan saya panggil core.elf.

  2. Buat skrip tautan untuk aplikasi. Ini lagi-lagi akan didasarkan pada skrip tautan standar untuk platform, tetapi dengan rentang memori flash & RAM diubah ke yang disediakan untuk aplikasi. Selain itu, perlu bagian khusus untuk memastikannya app_init ditempatkan di awal area flash aplikasi, sebelum sisa kode di .text bagian:

BAGIAN
{
    .tanda:
    {
        KEEP (* (. App_init))
        *(.teks*)
  1. Tuliskan app_init fungsi. Ini harus dalam perakitan, karena harus melakukan beberapa pekerjaan tingkat rendah sebelum kode C dalam aplikasi dapat dipanggil. Ini harus ditandai dengan .section .app_init sehingga penaut menempatkannya di tempat yang benar di awal area flash aplikasi. Itu app_init fungsi harus:

    1. Isi variabel dalam aplikasi .data bagian dengan nilai awal dari flash.
    2. Setel variabel dalam aplikasi .bss bagian ke nol.
    3. Panggil titik masuk C untuk aplikasi, yang akan saya hubungi app_start().
  2. Tuliskan app_start() fungsi yang memulai aplikasi.

  3. Buat aplikasi, menggunakan skrip tautan aplikasi. Langkah tautan ini harus melewati file objek yang berisi app_init, app_start, dan kode apa pun yang disebut oleh app_start yang belum ada di intinya. Parameter penaut --just-symbols=core.elf harus dilewatkan untuk menghubungkan fungsi di inti dengan alamat mereka. Selain itu, -nostartfiles harus dilewatkan untuk meninggalkan kode startup runtime normal C.

Butuh beberapa saat untuk mencari tahu semua ini tetapi sekarang bekerja dengan baik.


3
2018-02-10 17:03



Pertama-tama ... jika ini hanya untuk pembaruan lapangan, Anda tidak perlu bergantung pada tabel vektor interupsi di inti ruang untuk aplikasi. saya berpikir Bagian ARM M0 selalu memiliki kemampuan untuk memindahkannya. Saya tahu itu dapat dilakukan pada beberapa (semua?) Hal-hal STM32Fx, tapi saya percaya ini adalah hal ARM M-x, bukan hal ST. Lihatlah hal ini sebelum memutuskan untuk membuat aplikasi Anda ISRs semua menjadi pengait yang dipanggil dari intinya.

Jika Anda berencana untuk memiliki banyak interaksi dengan inti Anda (btw, saya selalu menyebut bagian yang memperbarui diri "bootloader" pada MCU), inilah saran alternatif:

Punya Inti lulus pointer ke struct / tabel fungsi yang menggambarkan kemampuannya ke dalam Aplikasi titik masuk?

Ini akan memungkinkan pemisahan lengkap kode untuk aplikasi vs inti kecuali untuk header bersama (dengan asumsi ABI Anda tidak berubah) dan mencegah benturan nama.

Ini juga menyediakan cara yang wajar untuk mencegah GCC agar tidak menggunakan fungsi apa pun yang hanya dapat Anda panggil dari Aplikasi tanpa mengacaukan pengaturan optimasi Anda atau mengacaukan pragmasinya.

core.h:

struct core_functions
{
    int (*pcore_func1)(int a, int b);
    void (*pcore_func2)(void);
};

core.c:

int core_func1(int a, int b){ return a + b; }
void core_func2(void){ // do something here }

static const struct core_functions cfuncs= 
{
    core_func1,
    core_func2
};

void core_main()
{
   // do setup here
   void (app_entry*)(const struct core_functions *) = ENTRY_POINT;
   app_entry( &cfuncs );
}

app.c

void app_main(const struct core_functions * core)
{
   int res;
   res = core->pcore_func1(20, 30);
}

Kelemahan / biaya adalah sedikit waktu proses & overhead memori dan lebih banyak kode.


0
2018-02-03 22:56