شرح مفصل لعملية STM32 الذاكرة وتحميل التشغيل.
في حين أن جميع الأمثلة أدناه مخصصة لـ STM32F446 ، تنطبق المبادئ الأساسية على معظم MCU.
STM32CUBEMX ينتج STM32F446RETX_FLASH.LD الذي سننظر إليه كمرجع لجميع استكشافاتنا.
يأخذ الرابط ملفات الكائن التي ينتجها التحويل البرمجي وينشئ إخراج التجميع النهائي ، وهو في حالتنا الثنائي . يستخدم الرابط دائمًا ملف نصي Linker ؛ حتى لو لم تحدد واحدة ، يتم استخدام البرنامج النصي الافتراضي.
من المهم أن نلاحظ ، أن البرنامج النصي Linker يصف فقط الذاكرة بناءً على مواصفات MCU ولا يغير أي ذاكرة الأجهزة.
لا يمكننا التحدث عن عملية تحميل التشغيل دون فهم بنية الذاكرة. في الواقع ، هو الغرض من محمل التمهيد أن يكون هناك ذاكرة في حالة جاهزة لتنفيذ طريقة main() .
يتم تنظيم ذاكرة البرنامج وذاكرة البيانات والسجلات ومنافذ I/O في STM32F4 ضمن نفس مساحة العنوان الخطي.
...
+ -- -- 0x2001FFFF -- -- +
| |
| RAM |
| |
+ -- -- 0x20000000 -- -- +
| ... |
+ -- -- 0x1FFF7A0F -- -- +
| |
| System |
| |
+ -- -- 0x1FFF0000 -- -- +
| ... |
+ -- -- 0x081FFFFF -- -- +
| |
| Flash |
| |
+ -- -- 0x08000000 -- -- +
| ... |
+ -- -- 0x001FFFFF -- -- +
| |
| Alias |
| |
+ -- -- 0x00000000 -- -- + حيث تشير الذاكرة الملقائية إلى ذاكرة الفلاش أو النظام أو ذاكرة الوصول العشوائي حسب دبوس BOOT0 . بشكل افتراضي إنه فلاش.
مساحتان لها أكثر اهتمامنا الآن:
ومع ذلك ، في الممارسة العملية ، يمكن أن يختلف التقسيم الوظيفي. على سبيل المثال ، قد تحتاج إلى تحميل البيانات الثنائية للبرنامج في ذاكرة الوصول العشوائي أو العكس.
ينعكس بنية الذاكرة في ملف الرابط. يبدأ الفلاش في ORIGIN = 0x8000000 وذاكرة الوصول العشوائي في ORIGIN = 0x20000000 .
// linker file
MEMORY
{
RAM ( xrw ) : ORIGIN = 0x20000000 , LENGTH = 128 K
FLASH ( rx ) : ORIGIN = 0x8000000 , LENGTH = 512 K
}لاحظ أن هذا مجرد تطبيق افتراضي ، يمكنك بسهولة تقسيم الفلاش والذاكرة وذاكرة الوصول العشوائي وإضافة الكتل أو الأقسام الخاصة بك بمجرد الالتزام بمواصفات ذاكرة MCU.
ثم يتم تقسيم الذاكرة في ملف الارتباط إلى أقسام ، كل منها له عنوانه ووجهة (على سبيل المثال ، فلاش أو ذاكرة الوصول العشوائي).
// linker file
SECTIONS
{
...
. text :
{
...
} > FLASH
...
}لاستكشاف . جدول الرموز ، سنستخدم أمر ARM-OEBI-OBJDUMP وفرز جميع الإدخالات حسب عناوينهم:
arm-none-eabi-objdump -t stm32-boot-explained.elf | sortينتج البرنامج الإخراج كما هو موضح في التفاصيل في الدليل:
Address Flag_Bits Section Size Nameيتم تجميع رمز البرنامج إلى هذا القسم في ذاكرة الفلاش .
يبدأ القسم من عنوان ORIGIN الفلاش ، ويشير _etext إلى العنوان الأخير للقسم.
08000000 l d . text 00000000 . text
08000 c00 g . text 00000000 _etextيمكننا أن نرى موقع الوظائف ، التي نستخدمها في ملف تحميل التمهيد الخاص بنا:
080005 c8 g F . text 00000048 __libc_init_array
08000610 g F . text 000000d 8 main
080006e8 g F . text 00000064 Reset_Handler
08000b c4 g F . text 00000024 SystemInit يوجد القسم في ذاكرة ذاكرة الوصول العشوائي ويحمل جميع المتغيرات ذات القيم المحددة. يمتد نطاق العنوان من _sdata إلى _edata .
20000000 g . data 00000000 _sdata
20000010 g . data 00000000 _edataدعنا نتحقق من المتغيرات من Main.c يقيم في هذا القسم:
20000000 l O . data 00000004 static_data_int
20000008 g O . data 00000008 data_double يجب ملء القيم الفعلية بواسطة برنامج نصي لعملية تحميل عن طريق نسخ البيانات من ذاكرة الفلاش على العنوان _sidata :
08000 c08 g * ABS * 00000000 _sidata كتلة بدء تشغيل رمز بيانات RAM لجميع المتغيرات دون قيمة مخصصة. يعتني محمل التمهيد بتعيين بيانات هذه الكتلة على 0 .
يمتد نطاق الذاكرة من _sbss إلى _ebss .
20000010 g . bss 00000000 _sbss
20000044 g . bss 00000000 _ebssجميع المتغيرات من main.c بدون قيمة صريحة موجودة في هذا القسم:
2000002 c l O . bss 00000004 static_bss_int
20000030 g O . bss 00000008 bss_double
20000038 g O . bss 00000008 bss_my_struct
20000040 g O . bss 00000004 bss_my_unionدعنا نتحقق من GDB :
(gdb) p bss_double
$1 = 0
(gdb) p & bss_double
$2 = (double * ) 0x20000030 < bss_double > بمجرد تعيين بعض القيمة للمتغير ، لا يتغير عنوانه ولا يزال يبقى في .bss :
(gdb) p bss_double
$3 = 86
(gdb) p & bss_double
$4 = (double * ) 0x20000030 < bss_double > جميع ذاكرة ذاكرة الوصول العشوائي فوق _end وحتى يتم تخصيص _estack للكومة والذاكرة المكدس.
20000048 g . _user_heap_stack 00000000 _end
20020000 g . isr_vector 00000000 _estack _ يتم حساب عنوان _estack على أنه ORIGIN(RAM) + LENGTH(RAM) . بحيث من أجل ذاكرة وصول عشوائي 128 كيلو بايت:
_estack = 0x20000000 + 128 * 1024 # dec
= 0x20000000 + 0x20000 # hex
= 0x20020000 المكدس هو بنية LIFO التي تبدأ في _estack وتنمو لأسفل. يتم تعريف الحد الأدنى لحجم المكدس في ملف الرابط باسم _Min_Stack_Size . يتم تحرير ذاكرة المكدس تلقائيًا.
+ -- -- 0x20020000 -- -- + < -- _estack
| |
| Stack |
| |
+ - - 0x2001FC00 - - + < -- - _Min_Stack_Size
| |
+ -- -- 0x200 sssss -- -- + < -- $msp register
| |
| |
| Free space |
| |
| |
+ -- -- 0x200 hhhhh -- -- + < -- Actual heap end
| |
| Heap |
| |
+ - - 0x20000248 - - + < -- + _Min_Heap_Size
| |
+ -- -- 0x20000048 -- -- + < -- _end يبدأ الكومة بدوره من _end وينمو لأعلى حتى _estack - _Min_Stack_Size عند طلب malloc .
لدينا متغير stack_int محدد في Main.C. دعنا نتحقق من العنوان باستخدام GDB بعد تهيئته:
(gdb) p & stack_int
$1 = (unsigned short * ) 0x2001ffd6
(gdb) p $msp
$2 = (void * ) 0x2001ffd0 الذي يتطابق مع توقعاتنا ، عندما يكون المتغير أعلى من $msp .
تجدر الإشارة إلى أن تطبيق مكتبة C القياسي شائع الاستخدام في تطبيقات C المضمنة هو Newlib. تتطلب هذه المكتبة تنفيذ وظائف معينة خاصة بالنظام. يقوم STM32CUBEMX بإنشاء ملف SYSCALLS.C مع التطبيقات الافتراضية اللازمة.
هذا هو الحال أيضًا لمكالمة sbrk التي تزيد من مساحة بيانات البرنامج. يستخدم malloc هذه الوظيفة لتخصيص المزيد من ذاكرة الكومة. قد تجد تطبيقًا تم إنشاؤه بواسطة STM32CUBEMX في sysmem.c. إنه يسمح ببساطة للكومة بالنمو من _end إلى _estack - _Min_Stack_Size .
الآن عندما نفهم ذاكرة MCUS ، دعنا نتواصل مع برنامجنا مع GDB ، إليك ما نراه أول إخراج:
...
Reading symbols from ./stm32-boot-explained.elf...
Remote debugging using localhost:61234
Reset_Handler () at %PATH%/bootloader.c:25
25 void Reset_Handler () {
(gdb) info registers
...
pc 0x80006e8 0x80006e8 < Reset_Handler >
... تم تحديد Reset_Handler بطريقة أو بأخرى كنقطة تمهيد لتطبيقنا.
ربما لاحظت أن هناك تعليمات ENTRY في ملف الرابط:
// linker file
ENTRY ( Reset_Handler ) في الواقع ، فإنه يحفظ إشارة إلى عنوان دالة Reset_Handler في رأس الملف .ff :
arm-none-eabi-objdump -t -f stm32-boot-explained.elf | grep " start address "
start address 0x080006e9 بالنظر إلى جدول الرمز الخاص بنا ، يوجد Reset_Handler في قسم Flash .text ، تمامًا مثل جميع الوظائف الأخرى:
080006e8 g F . text 00000064 Reset_Handlerالعناوين محاذاة الصفحة ، وهذا هو السبب في أن هناك احتمال عدم تطابق بين رأس .ELL والعنوان الفعلي.
على الرغم من أن هذه المعلومات تستخدم في الغالب من قبل الرابط للتحقق من وجود رمز نقطة الدخول داخل الكود ، إلا أنه ليس له معنى عملي لـ MCU.
وفقًا لمواصفات STM32 ، تجلب وحدة المعالجة المركزية قيمة أعلى _estack من العنوان 0x00000000 ، ثم تبدأ تنفيذ الكود من ذاكرة التمهيد التي تبدأ في 0x00000004 .
+ -- -- 0x001FFFFF -- -- +
| |
| Alias |
| |
+ -- -- 0x00000000 -- -- + هذا هو بالضبط المكان الذي يتم فيه تعريف الذاكرة الملقائية المذكورة أعلاه. مع التكوين الافتراضي ، عندما يكون Pin Value BOOT0 = 0 ، فإنه يمتد إلى كتلة ذاكرة الفلاش تبدأ من 0x8000000 .
تتضمن الخيارات الأخرى المستندة إلى BOOT0 و BOOT1 ذاكرة النظام مع محمل تمهيد مضمّن أو ذاكرة ذاكرة الوصول العشوائي. تتم برمجة جهاز تحميل التشغيل المدمج بواسطة ST أثناء الإنتاج وخارج نطاق هذا الدليل.
لكن Reset_Handler يحتوي على عنوان 0x08000524 وهو ليس بالضبط بداية ذاكرة الفلاش ، كيف يجد MCU طريقة bootstrap إذن؟
هنا حيث يأتي طاولة المتجهات في اللعب.
08000000 g O . isr_vector 000001 c4 Vector_Table يعامل MCU بداية الذاكرة كجدول متجه ، والذي يحتوي على مؤشرات لمختلف إجراءات خدمة المقاطعة ووظائف بدء التشغيل الأساسية ، بما في ذلك Reset_Handler . راجع المواصفات لرؤية بنية الجدول الدقيقة التي تتوقع MCU تحميلها من 0x00000000 . يجب ملء الجدول الفعلي بواسطة محمل التمهيد.
| عنوان | اسم |
|---|---|
| 0x00000000 | محجوز |
| 0x00000004 | إعادة تعيين المعالج |
| 0x00000008 | مقاطعة غير قابلة للإخفاء |
| 0x00000012 | خطأ صعب |
| ... | مقاطعة أخرى |
Reset_Handler هذه هي وظيفة تحميل التمهيد يمكن استخدامها في العديد من التطبيقات ، من المهام الخاصة بالأمان إلى التحديث التلقائي للبرامج الثابتة. هنا ، سنستكشف التنفيذ الافتراضي الأساسي لفهم تفاعله مع ذاكرة MCU.
بشكل افتراضي ، يتم تعريف طريقة Reset_Handler في ملف startup_stm32f436xx.s ASM المقدم بواسطة STM23CUBEMX. يتم كتابة تطبيق Bootloader.c الفعلي في هذا المشروع في C من أجل الوضوح.
لاحظ أنه يمكن الوصول إلى المتغيرات المحددة في البرنامج النصي Linker في رمز C:
extern uint32_t _estack ;بحيث يمكن بسهولة تكرار إصدار ASM.
ثم يمكن تقسيم عملية التحميل إلى الخطوات التالية:
SystemInit() ).data من الفلاش إلى رام.bss__libc_init_array() وظيفة)main() )بالنسبة للخطوات رقم 1 و #4 ، يوفر STM32CUBEMX تطبيقات الوظائف ، يمكنك التحقق من التفاصيل في system_stm32f4xx.c.
يحتوي المشروع على الحد الأدنى من الملفات المطلوبة لعملية تشغيل STM32. قد ترغب في تجربتها بنفسك للتحقق من إخراج ARM-NONE-OEBI-OBJDUMP والخطوة مع GDB .
مطلوب ARM GNU Toolchain لبناء المشروع.
يوصى بتثبيت حزمة أدوات أوامر STM32CUBECLT الكل في واحد مع أدوات ARM-NONE-OEBI-GCC و STM32_Programmer_CLI و ST-LINK_GDBSERVER .
بناء المشروع باستخدام CMake :
mkdir build ; cd build
cmake ../ -DPROGRAMMER_CLI=/opt/ST/STM32CubeCLT_1.15.1/STM32CubeProgrammer/bin
-DGDB_SERVER=/opt/ST/STM32CubeCLT_1.15.1/STLink-gdb-server/bin
make VERBOSE=1لاحظ أن الأمر الأخير هو مرحلة الارتباط. إذا قمنا بتجريد جميع أعلام المترجم الأخرى ، فقد يبدو الأمر كما يلي. هذا هو المكان الذي يتم فيه توجيه الرابط لاستخدام البرنامج النصي Linker الخاص بنا.
arm-none-eabi-gcc
...
-T " %SCRIPT_DIR%/STM32F446RETx_FLASH.ld "
...
" %OBJ_DIR%/%OBJECT_NAME%.c.obj "
...
-o stm32-boot-explained.elfتم تكوين STM32_Programmer_Cli مسبقًا لـ SWD Procotol ، فقط قم بتشغيل:
make flash قم بتدوين ملاحظة على إخراج المبرمج ، والذي يستخدم عنوان 0x08000000 كنقطة انطلاق:
...
Memory Programming ...
Opening and parsing file: stm32-boot-explained.elf
File : stm32-boot-explained.elf
Size : 1,46 KB
Address : 0x08000000
...إنه في الواقع عنوان البداية لذاكرة الفلاش ، التي نعرفها بالفعل.
هناك هدف مخصص تم تكوينه مسبقًا لتشغيل ST-Link_GDBServer :
# start ST-Link gdb server
make gdb-server
# connect with gdb debugger
gdb -ex ' target remote localhost:61234 ' ./stm32-boot-explained.elf