STM32 Bagian 2: Inisialisasi

Pemrograman adalah memecah sesuatu yang besar dan tidak mungkin menjadi sesuatu yang kecil dan sangat nyata.


Halo semua, untuk mulai dengan, saya ingin mengucapkan terima kasih kepada moderator karena melewatkan posting (menjijikkan) pertama saya, dan untuk menyapa ibu! Dan saya juga ingin mengucapkan terima kasih kepada semua pembaca dan orang-orang yang menunjukkan kesalahan saya dan membantu memperbaikinya. Saya segera membuat reservasi bahwa dalam bahasa Rusia saya tidak menulis dari kelas 6, secara umum, jangan marah.

STM32 Bagian 1: Dasar-Dasar

Jadi mari kita mulai. Dalam artikel sebelumnya, saya dengan cepat menelusuri poin pertama. Ini adalah file startup.c kami, yang bertanggung jawab untuk 2 vektor (stack, Reset_Handler) dan sedikit tentang skrip linker. Hari ini kami akan menambah kode inisialisasi kami, menganalisis tautan untuk suku cadang dan mencari tahu bagaimana semuanya bekerja.


extern void *_estack;

void Reset_Handler();

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    &_estack,
    &Reset_Handler
};

void __attribute__((naked, noreturn)) Reset_Handler()
{
    while(1);
}


Jika kode ini tidak jelas bagi seseorang, maka Anda dapat membacanya di sini (artikelnya bukan air mancur, tetapi mencoba menjelaskannya).

Sekarang mari kita cari tahu apa yang hilang di sini dan bagaimana cara menambahkannya. Jelas, mikron memiliki vektor lain. Misalnya, vektor Hard_Fault adalah vektor yang dipanggil untuk tindakan yang tidak benar dengan μ, misalnya, jika ia mencoba menjalankan instruksi yang tidak dimengerti oleh prosesor, ia akan melompat ke alamat vektor Hard_Fault. Ada banyak vektor seperti itu dan bukan fakta bahwa dalam program kami kami akan menggunakan segalanya. Juga, vektor interupsi berada di tabel yang sama.

(Bagi mereka yang masih belum tahu apa itu vektor, ini adalah penunjuk ke alamat, alias "penunjuk").

Kami melengkapi kode kami, dan kemudian saya akan menjelaskan apa yang saya lakukan.

extern void *_estack;

void Reset_Handler();
void Default_Handler();

void NMI_Handler()   __attribute__((weak, alias("Default_Handler")));
void HardFault_Handler()   __attribute__((weak, alias("Default_Handler")));
//     

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    &_estack,
    &Reset_Handler
    &NMI_Handler,
    &HardFault_Handler
};

void __attribute__((naked, noreturn)) Reset_Handler()
{
    while(1);
}

void __attribute__((naked, noreturn)) Default_Handler(){
    while(1);
}


Di sini saya menambahkan fungsi baru Default_Handler (); yang akan menangani semua interupsi, tentu saja. Tetapi juga dengan bantuan, __attribute__((weak, alias("Default_Handler")));saya mencatat bahwa jika suatu fungsi dengan nama yang sama dideklarasikan, maka itu akan menjadi pengendali interupsi ini. Dengan kata lain, sekarang ini hanya nama panggilan untuk Default_Handler. Jadi kami dapat menambahkan semua vektor dalam daftar dari manual Referensi Anda. Ini dilakukan agar jika Anda tiba-tiba memutuskan untuk menggunakan interupsi, tetapi lupa membuat function handler, maka Default_Handler biasa dipanggil.

Saya pikir pada titik ini dengan vektor Anda dapat menyelesaikan dan pergi ke inisialisasi sektor dan ke linker. Untuk memulai, mari perbarui startup.c kami lagi dengan menambahkan inisialisasi sektor data dan bss ke badan Reset_Handler serta beberapa variabel eksternal.

extern void *_sidata, *_sdata, *_edata, *_sbss, *_ebss;

void __attribute__((naked, noreturn)) Reset_Handler()
{


	void **pSource, **pDest;
	for (pSource = &_sidata, pDest = &_sdata; pDest != &_edata; pSource++, pDest++)
		*pDest = *pSource;

	for (pDest = &_sbss; pDest != &_ebss; pDest++)
		*pDest = 0;

	while(1);
}


Jadi, variabel baru, dan lagi mereka dideklarasikan dalam skrip linker kami. Ingat kembali skrip sebelumnya . Dan juga membandingkannya dengan yang baru.

MEMORY{
	ROM(rx) : ORIGIN = 0x08000000, LENGTH = 1M
	SRAM (rwx):     ORIGIN = 0x20010000, LENGTH = 240K
}

_estack = LENGTH(SRAM) + ORIGIN(SRAM);

SECTIONS{
	.isr_vector : {
	KEEP(*(.isr_vector))
	} >ROM
	
	.text : {
        . = ALIGN(4);
        *(.text)
    } >ROM
	
	_sidata = LOADADDR(.data);
        .data : {
		. = ALIGN(4);
		_sdata = .;
		*(.data)
		. = ALIGN(4);
		_edata = .;
	} >SRAM AT>ROM
	
	.bss : {
		. = ALIGN(4);
		_sbss = .;
		*(.bss)
		. = ALIGN(4);
		_ebss = .;
	} >SRAM
}


Dua bagian baru telah ditambahkan, yaitu .data dan .bss. mengapa kita membutuhkan mereka? well. data akan meninggalkan variabel global dan statis akan pergi ke bss. Kedua bagian ini dalam RAM, dan jika semuanya sederhana dengan bagian bss (ini hanya nol), maka ada beberapa kesulitan dengan bagian data. Pertama, data sudah menginisialisasi data yang diketahui pada waktu kompilasi. Dengan demikian, pada awalnya bagian data akan terletak di suatu tempat di memori flash. Kedua, kita perlu menunjukkan ke skrip apakah ada di memori flash, tetapi itu akan ditransfer ke RAM selama eksekusi program. Mari kita lihat sepotong skrip tempat kami menggambarkan bagian data.


        _sidata = LOADADDR(.data);
        .data : {
		. = ALIGN(4);
		_sdata = .;
		*(.data)
		. = ALIGN(4);
		_edata = .;
	} >SRAM AT>ROM


Itu dimulai dengan deklarasi _sidata dan penugasan, pada saat ini, dari makna yang tidak jelas bagi kami. Selanjutnya, deskripsi bagian itu sendiri dimulai. Saya menyarankan Anda untuk membaca tentang fungsi ALIGN di sini , tetapi secara singkat, kami memberikan nilai (alamat saat ini) ke nilai kami yang lebih mudah diterima oleh prosesor kami ketika mencoba memuat data atau instruksi dari sana.

Juga termasuk _sdata, _edata, dan bagian akhir. Tetapi pada akhir bagian ada yang kita butuhkan.SRAM AT>ROM. Baris ini memberi tahu linker kami bahwa bagian tersebut ada dalam SRAM tetapi bagian ini dimuat ke ROM atau memori flash. Sekarang kembali ke variabel _sidata kami dan fungsi LOADADDR. Fungsi ini akan mengembalikan kita nilai yang akan sama dengan alamat bagian pemuatan. Yaitu, kami mengindikasikan bahwa bagian tersebut dimuat ke dalam memori flash, tetapi kami menggunakannya dalam RAM, dan untuk mentransfer bagian ini, kami perlu mengetahui alamatnya dalam memori flash, yang merupakan fungsi dari pengembalian LOADADDR. Sementara itu, variabel _sdata dan _edata menunjukkan lokasi bagian dalam RAM, sehingga memberi kita kesempatan untuk memuat bagian data ke dalam memori yang benar. Biarkan saya mengingatkan Anda bahwa ini adalah kode inisialisasi yang kami tambahkan di atas.

Mengapa kita memerlukan tautan ini dan kesulitannya dengan distribusi bagian dari aplikasi kita? Bagian Data hanyalah contoh kecil mengapa kita harus dapat menempatkan dan menggeser bagian dalam memori. Di bawah ini adalah contoh dari bagian teks yang saya gunakan.

.text (ORIGIN(ROM_ITCM) + SIZEOF(.isr_vector)): {
        . = ALIGN(4);
        *(.text)
    } AT>ROM_AXIM


Inti dari blok ini adalah saya memuat bagian teks juga ke dalam memori flash tetapi menggunakan bus yang berbeda untuk mengakses instruksi dan akselerator ART (semacam seperti cache biasa). Dan agar semuanya berfungsi, saya menunjukkan alamat muat dan alamat selama eksekusi kode, tetapi itu tidak akan berfungsi jika saya tidak menunjukkan bahwa bagian tersebut harus diimbangi. Ada banyak lagi penemuan seperti itu, saya tidak akan menunjukkan semuanya.

Pada tahap ini, bagian dimuat jika perlu dan diinisialisasi. kami memiliki basis kerja untuk penulisan kode lebih lanjut. Pada artikel selanjutnya, kita akan menulis driver pertama kita dan mengedipkan LED.

Terima kasih atas perhatian Anda, dan sukses atas usaha Anda.

All Articles