تا اینجای کار، ما فقط از لینکر با تنظیمات پیش فرض استفاده کردهایم. اما مواقعی لازم است عملکردهای پیشرفتهتری را نسبت به حالت پیش فرض انجام دهید.
یکی از مشکلات مدل حافظه پیش فرض C، ریست دوباره تمام دادههای شما با راهاندازی مجدد برنامه است. در مورد STM32، این بدان معناست که ریست کردن دستگاه باعث میشود تمام دادهها را از دست بدهد. فرض کنید میخواهید برخی از دادههای پیکربندی را بین راهاندازیهای مختلف نگهداری کنید. تنظیمات پیش فرض اجازه انجام این کار را نمیدهند. چطور این کار را انجام دهیم؟
بیایید با برنامه سریال “Hello World” از فصل ۹ شروع کنیم. ما قصد داریم شمارندهای اضافه کنیم که تعداد دفعات بوت شدن سیستم را نشان میدهد و سپس پیامی با تعداد ریست شدن را به دستگاه سریال ارسال کنیم.
طراحی ما بسیار ساده است. قصد داریم ۴ کیلوبایت انتهایی حافظه فلش را برداشته و برای ذخیره اطلاعات پیکربندی استفاده کنیم. یک اسم برای این بخش انتخاب میکنیم: CONFIG (تنظیمات). همچنین یک بخش حافظه جدید به نام .config تعریف خواهیم کرد که در آن متغیر شمارنده ریست را قرار میدهیم.
کد C برای انجام این کار به این صورت است:
|
1 |
static uint32_t resetCount __attribute__((section(.config))) = 0; |
حالا باید اسکریپت لینکر را برای مدیریت بخش جدید خودمان ویرایش کنیم. با تقسیم حافظه فلش به دو بخش شروع میکنیم. بخش اول همان حافظه فلش سنتی است که قبلا دربارهاش صحبت کردیم. بخش دوم، CONFIG، دادههای پیکربندی ما را نگه میدارد، بنابراین باید فایل LinkerScript.ld را ویرایش کنیم و کد زیر را:
|
1 2 3 4 5 6 7 |
MEMORY { FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 64K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 8K } |
با این کد جایگزین میکنیم:
|
1 2 3 4 5 6 7 8 9 |
MEMORY { FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 60K CONFIG (rw) : ORIGIN = 0x8000000 + 60K, LENGTH = 4K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 8K } |
با این کار حافظه فلش ۴ کیلوبایت کوچکتر میشود تا بتوانیم از این فضای جدید برای بخش حافظهای به نام CONFIG استفاده کنیم.
توجه داشته باشید که فلش با حافظه معمولی متفاوت است. در فلش، تنها یک بار میتوانید اطلاعات را بنویسید و بعد از آن برای نوشتن دوباره باید کل بخش (صفحه) را پاک کنید. در مورد STM32، بخش CONFIG ما باید حداقل اندازهای برابر ۱ کیلوبایت داشته باشد (چون در این میکروکنترلر سایز هر صفحه یا page برابر 1KB یا همان 1024 byte است) و اندازهاش باید ضریبی از ۱ کیلوبایت باشد. ما ۴ کیلوبایت را انتخاب کردهایم چون احتمالاً بعدها بخواهیم دادههای پیکربندی بیشتری در آن ذخیره کنیم.
حالا باید به لینکر دستور دهیم تا بخش .config را درون بلوک حافظه به نام CONFIG قرار دهد. این کار با اضافه کردن کد زیر به بخش SECTIONS فایل LinkerScript.ld انجام میشود:
|
1 2 3 4 5 6 7 |
{ . = ALIGN(4); *(.config*) } >CONFIG |
تغییر این متغیر به سادگی نوشتن کد زیر نیست:
|
1 |
++resetCount; |
برای برنامهریزی مجدد چیپ به مجموعهای از مراحل نیاز است. ما تمام این مراحل را درون تابعی به نام updateCounter قرار دادهایم که در کد ۱۱-۴نمایش داده شده است.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
/** * Update the resetCounter. * * In C this would be ++resetCount. Because we are dealing * with flash, this is a much more difficult operation. */ static HAL_StatusTypeDef updateCounter(void) { // Allow flash to be modified. HAL_FLASH_Unlock(); uint32_t newResetCount = resetCount + 1; // Next value for reset count uint32_t pageError = 0; // Error indication from the erase operation // Tell the flash system to erase resetCounter (and the rest of the page). FLASH_EraseInitTypeDef eraseInfo = { .TypeErase = FLASH_TYPEERASE_PAGES, // Going to erase one page .PageAddress = (uint32_t)&resetCount, // The start of the page .NbPages = 1 // One page to erase }; // Erase the page and get the result. HAL_StatusTypeDef result = HAL_FLASHEx_Erase(&eraseInfo, &pageError); if (result != HAL_OK) { HAL_FLASH_Lock(); return (result); } // Program the new reset counter into flash. result = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, (uint32_t)&resetCount, newResetCount); HAL_FLASH_Lock(); return (result); } |
حافظه فلش در میکروکنترلر STM32 به منظور حفاظت از اطلاعات، به طور پیش فرض قفل است. برای نوشتن اطلاعات به این حافظه، ابتدا باید آن را با استفاده از تابع HAL_FLASH_Unlock بازگشایی کنیم. این تابع دو رمز عبور را به سیستم حفاظت فلش ارسال میکند تا امکان نوشتن در آن را فراهم کند.
با وجود بازگشایی حافظه فلش، همچنان نمیتوانیم مقدار متغیر resetCount را که در این حافظه ذخیره شده است، به طور مستقیم تغییر دهیم. به جای این کار، مقدار resetCount را به یک متغیر موقت با نام newResetCount (که از نوع داده معمولی است) منتقل میکنیم، سپس میتوانیم مقدار newResetCount را به دلخواه تغییر دهیم.
پیش از نوشتن مقدار جدید newResetCount در حافظه فلش، باید حافظه محل ذخیره این متغیر را پاک کنیم. کوچکترین واحد قابل پاک کردن در حافظه فلش، “صفحه” (Page) نامیده میشود. برای پاک کردن صفحه موردنظر، ابتدا باید یک ساختار داده را مقداردهی کنیم، سپس این ساختار را بهعنوان پارامتر به تابع HAL_FLASHEx_Erase ارسال کنیم تا حافظه صفحه موردنظر پاک شود.
پس از پاک شدن حافظه، میتوانیم مقدار جدید newResetCount را در آن بنویسیم. با توجه به اینکه newResetCount یک مقدار 32 بیتی است، در حالی که حافظه فلش فقط میتواند 16 بیت را در هر بار نوشتن ذخیره کند، باید از تابع HAL_FLASH_Program برای نوشتن این مقدار به حافظه استفاده کنیم.
کد کامل این فرایند در کد زیر نمایش داده شده است.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
/** * @brief Write the number of times the system reset to the serial device. */ #include <stdbool.h> #include "stm32f0xx_nucleo.h" #include "stm32f0xx.h" const char message1[] = "This system has been reset "; // Part 1 of message const char message2[] = " times\r\n"; // Part 2 of message const char many[] = "many"; // The word many // Number of times reset has been performed uint32_t resetCount __attribute__((section(".config.keep"))) = 0; int current; // The character in the message we are sending UART_HandleTypeDef uartHandle; // UART initialization /** * @brief This function is executed in case of error occurrence. * * All it does is blink the LED. */ void Error_Handler(void) { /* Turn LED2 on. */ HAL_GPIO_WritePin(LED2_GPIO_PORT, LED2_PIN, GPIO_PIN_SET); while (true) { // Toggle the state of LED2. HAL_GPIO_TogglePin(LED2_GPIO_PORT, LED2_PIN); HAL_Delay(1000); // Wait one second. } } /** * Send character to the UART. * * @param ch The character to send */ void myPutchar(const char ch) { // This line gets and saves the value of UART_FLAG_TXE at call time. // This value changes, so if you stop the program on the "if" // line below, the value will be set to zero because it goes away // faster than you can look at it. int result __attribute__((unused)) = (uartHandle.Instance->ISR & UART_FLAG_TXE); // Block until the transmit empty (TXE) flag is set. while ((uartHandle.Instance->ISR & UART_FLAG_TXE) == 0) continue; uartHandle.Instance->TDR = ch; // Send character to the UART. } /** * Send string to the UART. * * @param msg Message to send */ static void myPuts(const char *const msg) { for (unsigned int i = 0; msg[i] != '\0'; ++i) { myPutchar(msg[i]); } } /** * Initialize LED2 (so we can blink red for error). */ void led2_Init(void) { // LED clock initialization LED2_GPIO_CLK_ENABLE(); GPIO_InitTypeDef GPIO_LedInit; // Initialization for the LED // Initialize LED. GPIO_LedInit.Pin = LED2_PIN; GPIO_LedInit.Mode = GPIO_MODE_OUTPUT_PP; GPIO_LedInit.Pull = GPIO_PULLUP; GPIO_LedInit.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(LED2_GPIO_PORT, &GPIO_LedInit); } /** * Initialize UART2 for output. */ void uart2_Init(void) { // UART initialization // UART2 -- one connected to ST-LINK USB uartHandle.Instance = USART2; uartHandle.Init.BaudRate = 9600; // Speed 9600 uartHandle.Init.WordLength = UART_WORDLENGTH_8B; // 8 bits/character uartHandle.Init.StopBits = UART_STOPBITS_1; // One stop bit uartHandle.Init.Parity = UART_PARITY_NONE; // No parity uartHandle.Init.Mode = UART_MODE_TX_RX; // Transmit & receive uartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; // No hw control // Oversample the incoming stream. uartHandle.Init.OverSampling = UART_OVERSAMPLING_16; // Do not use one-bit sampling. uartHandle.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; // Nothing advanced uartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; /* * For those of you connecting a terminal emulator, the above parameters * translate to 9600,8,N,1. */ if (HAL_UART_Init(&uartHandle) != HAL_OK) { Error_Handler(); } } /** * Update the resetCounter. * * In C, this would be ++resetCounter. Because we are dealing * with flash, this is a much more difficult operation. */ static HAL_StatusTypeDef updateCounter(void) { HAL_FLASH_Unlock(); // Allow flash to be modified. uint32_t newResetCount = resetCount + 1; // Next value for reset count uint32_t pageError = 0; // Error indication from the erase operation // Tell the flash system to erase resetCounter (and the rest of the page). FLASH_EraseInitTypeDef eraseInfo = { .TypeErase = FLASH_TYPEERASE_PAGES, // Going to erase 1 page .PageAddress = (uint32_t)&resetCount, // The start of the page .NbPages = 1 // One page to erase }; // Erase the page and get the result. HAL_StatusTypeDef result = HAL_FLASHEx_Erase(&eraseInfo, &pageError); if (result != HAL_OK) { HAL_FLASH_Lock(); return (result); } // Program the new reset counter into flash. result = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, (uint32_t)&resetCount, newResetCount); HAL_FLASH_Lock(); return (result); } int main(void) { HAL_Init(); // Initialize hardware. led2_Init(); uart2_Init(); myPuts(message1); HAL_StatusTypeDef status = updateCounter(); switch (status) { case HAL_FLASH_ERROR_NONE: // Nothing, this is correct. break; case HAL_FLASH_ERROR_PROG: myPuts("HAL_FLASH_ERROR_PROG"); break; case HAL_FLASH_ERROR_WRP: myPuts("HAL_FLASH_ERROR_WRP"); break; default: myPuts("**unknown error code**"); break; } // A copout to avoid writing an integer to an ASCII function if (resetCount < 10) myPutchar('0' + resetCount); else myPuts("many"); myPuts(message2); for (;;) { continue; // Do nothing. } } /** * Magic function that's called by the HAL layer to actually * initialize the UART. In this case, we need to put the UART pins in * alternate mode so they act as UART pins and not like GPIO pins. * * @note: Only works for UART2, the one connected to the USB serial converter * * @param uart The UART information */ void HAL_UART_MspInit(UART_HandleTypeDef *uart) { GPIO_InitTypeDef GPIO_InitStruct; if (uart->Instance == USART2) { /* Peripheral clock enable */ __HAL_RCC_USART2_CLK_ENABLE(); /* * USART2 GPIO Configuration * PA2 ------> USART2_TX * PA3 ------> USART2_RX */ GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // Alternate function -- that of UART GPIO_InitStruct.Alternate = GPIO_AF1_USART2; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } } /** * Magic function called by HAL layer to de-initialize the * UART hardware. Something we never do, but we put this * in here for the sake of completeness. * * @note: Only works for UART2, the one connected to the USB serial converter * * @param uart The UART information */ void HAL_UART_MspDeInit(UART_HandleTypeDef *uart) { if (uart->Instance == USART2) { /* Peripheral clock disable */ __HAL_RCC_USART2_CLK_DISABLE(); /* * USART2 GPIO Configuration * PA2 ------> USART2_TX * PA3 ------> USART2_RX */ HAL_GPIO_DeInit(GPIOA, GPIO_PIN_2 | GPIO_PIN_3); } } |
فرض کنید میخواهیم بیش از یک متغیر پیکربندی را در حافظهی فلش ذخیره کنیم. مشکل اینجاست که حافظهی فلش، حافظهی معمولی نیست. بعد از اینکه مقداری را در یک متغیر حافظهی فلش ذخیره کردید، دیگر نمیتوانید آن را به صورت مستقیم تغییر دهید. برای تغییر، باید کل صفحهی حاوی آن متغیر را پاک کنید.
این روش زمانی که شما یک متغیر را در هر صفحه ذخیره میکنید (که روشی بسیار غیر بهینه است) کاربرد دارد، اما برای ذخیرهی چندین متغیر پیکربندی در یک حافظهی فلش و بهروزرسانی تنها یک مورد از آنها، به رویکردی متفاوت نیاز داریم. این فرایند که در ادامه آمده کمی پیچیدهتر است:
این فرایند باعث میشود تا بتوانیم چندین متغیر پیکربندی را در یک حافظهی فلش ذخیره کرده و تنها مقدار موردنظر را بهروزرسانی کنیم.
کد زیر خلاصهای از کد برای تعریف یک ساختار پیکربندی در بخش .config و بهروزرسانی یک مقدار در آن ساختار را نشان میدهد.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
struct config { char name[16]; // Name of the unit uint16_t sensors[10]; // The type of sensor connected to each input uint32_t reportTime; // Seconds between reports // ... Lots of other stuff }; // The configuration instance placed in a specific flash section struct config theConfig __attribute__((section(".config"))); static void updateReportTime(const uint32_t newReportTime) { // <Prepare flash> // Copy the current configuration from flash to RAM struct config currentConfig = theConfig; // Update the specific field currentConfig.reportTime = newReportTime; // <Erase flash> // Write the updated configuration back to flash // Note: &theConfig is the destination address in flash writeFlash(&theConfig, ¤tConfig, sizeof(currentConfig)); // <Lock flash> } |
استفاده از حافظهی Flash به شکل گفته مشکلاتی نیز به همراه دارد. همانطور که قبلا ذکر شد، اولین مشکل پاک شدن کل صفحه برای نوشتن یک کلمه است. نوشتن یک صفحه در فلش زمانبر است، و این امکان وجود دارد که سیستم در حین نوشتن خاموش یا ریست مجدد شود. اگر این اتفاق بیفتد، نوشتن ناقص میماند و دادههای پیکربندی شما خراب میشوند.
یک راه حل برای این مشکل، داشتن دو بخش پیکربندی است، اصلی و پشتیبان، که هرکدام شامل یک کد checksum (کد اعتبارسنجی) هستند. برنامه ابتدا سعی میکند پیکربندی اصلی را بخواند. اگر کد اعتبارسنجی خراب باشد، برنامه بخش دوم را میخواند. از آنجایی که تنها یک پیکربندی در یک زمان نوشته میشود، میتوانید مطمئن باشید که حتما یکی از پیکربندی اصلی یا پشتیبان صحیح هستند.
مشکل دیگر فلش، فرسودگی حافظه است. تعداد دفعاتی که میتوانید چرخهی برنامهریزی/پاک کردن را بر روی یک سلول حافظهی فلش انجام دهید، محدود است. بسته به نوع فلش، این تعداد میتواند بین ۱۰۰،۰۰۰ تا ۱،۰۰۰،۰۰۰ چرخه باشد. بنابراین، استفاده از حافظهی فلش برای ذخیرهی پیکربندیای که انتظار میرود ماهی یک بار تغییر کند، مناسب است. اما استفاده از آن برای چیزی که چندین بار در ثانیه تغییر میکند، به سرعت حافظه را فرسوده خواهد کرد.
راههایی برای برنامهنویسی در جهت دور زدن محدودیتهای حافظهی فلش وجود دارد. همچنین میتوانید تراشههای حافظهی خارجی را به سیستم خود اضافه کنید که محدودیتهای طراحی فلش را ندارند.
فرض کنید در شرکتی کار میکنیم که دزدگیر تولید میکند. این دزدگیرها به شرکتهای خدمات آلارم فروخته میشوند که این شرکتها وظیفهی نصب آنها را در محل نهایی، یعنی خانه یا محل کار مشتریان، بر عهده دارند.
حال، تصور کنید شرکتی به نام “آژیرِ مهدی” که در کنار تولید دزدگیر، مغازهی ماهیگیری هم دارد، از دیدن لوگوی شرکت “تولیدکنندهی آژیرِ Acme” هنگام راهاندازی پنل دزدگیری که نصب کرده، راضی نیست. “آژیرِ مهدی” به برندسازی خود اهمیت میدهد و تمایل دارد لوگوی خودش روی پنل نمایش داده شود.
بنابراین، ما باید به مشتریان خود راهی برای شخصیسازی لوگوی داخل دستگاههایشان ارائه دهیم.
میتوانیم بخشی از حافظهی دستگاه را به طور اختصاصی برای ذخیرهسازی لوگو در نظر بگیریم:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
MEMORY { FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 52K LOGO (r) : ORIGIN = 0x8000000 + 52K, LENGTH = 8K CONFIG (rw) : ORIGIN = 0x8000000 + 60K, LENGTH = 4K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 8K } |
یک روش انجام دادن کار در کارخانه است. اما این روش معایبی دارد. به عنوان مثال، هر بار که دستگاهی را به مشتری ارسال میکنیم، باید جعبه را باز کنیم، دستگاه را به برق وصل کنیم، لوگو را برنامهریزی کنیم و دوباره آن را در جعبه قرار دهیم. این کار پرهزینه و زمانبر است.
راه حل جایگزین اجازه دادن به مشتریان برای برنامهریزی لوگو توسط خودشان است. در این روش، میتوانیم یک کابل و نرمافزار به آنها بدهیم. این قابلیت را میتوانیم به عنوان یک ویژگی پولی به مشتریان ارائه دهیم تا در صورت نیاز، لوگوی دستگاه خود را بهروزرسانی کنند.
برنامهریزی لوگو میتواند با استفاده از همان سختافزار و نرمافزاری انجام شود که برای بارگذاری کد خودمان در حافظهی فلش استفاده میکنیم.
همچنین میتوانیم یک برنامهی داخلی در دستگاه بنویسیم که دادهها را از پورت سریال دریافت کند و آنها را در حافظهی لوگو ذخیره کند.
تعویض لوگو یک کار سادهی شخصیسازی است. حتی اگر در فرایند جایگزینی مشکلی پیش بیاید و لوگو به درستی ذخیره نشود، سیستم از کار نمیافتد. اما تعویض فریمور (firmware) موضوع دیگری است و باید با دقت و احتیاط بیشتری انجام شود.
بهروزرسانی فریمور زمانی که در حال اجراست، کمی پیچیده میباشد، اما راههای مختلفی برای انجام این کار وجود دارد.
یکی از سادهترین روشها تقسیم کردن حافظهی فلش به سه بخش است:
بوتلودر یک برنامهی بسیار کوچک است که هرگز بهروزرسانی نخواهد شد. کار آن نسبتاً ساده است، بنابراین امیدواریم که در همان بار اول آن را به درستی طراحی و پیادهسازی کنیم.
دو بخش دیگر حافظهی فلش که با عنوان «بخش ۱ برنامه» و «بخش ۲ برنامه» شناخته میشوند، حاوی یک نسخهی کامل از برنامهی اصلی هستند. این بخشها همچنین شامل شمارهی نسخهی برنامه و یک کد اعتبارسنجی (checksum) هستند.
وظیفهی بوتلودر تصمیمگیری دربارهی اینکه از کدام بخش برنامه استفاده شود است. بوتلودر کد اعتبارسنجی هر دو بخش را بررسی میکند و سپس بر اساس محاسبات زیر انتخاب میکند که از کدام بخش استفاده کند:
این توضیح، ایدهی کلی بهروزرسانی سیستمعامل است، البته ما برخی از مراحل نگهداری و مدیریت اطلاعات را نادیده گرفتهایم. برای مثال، جدول وقفه (interrupt table) در بخش .isr_vector نیاز به تغییر دارد تا تمام وقفهها به محل مناسب هدایت شوند.
حافظه، به ویژه در برنامهنویسی امبدد، یک منبع محدود است. برای اینکه بتوانید از برنامهی خود بیشترین بازدهی را بگیرید، باید به طور دقیق بدانید که حافظهی شما کجا قرار دارد و چگونه آن را مدیریت کنید.
کار لینکر این است که تکههای مختلف برنامهی شما را با هم ترکیب کند و یک برنامهی نهایی قابل بارگذاری در حافظه را تولید کند. برای برنامههای ساده، پیکربندی پیشفرض لینکر به خوبی کار میکند. اما با پیچیدهتر شدن سیستمها، برای اینکه یک برنامهنویس مؤثر در حوزهی برنامهنویسی امبدد باشید، نیاز به کنترل دقیقتر بر نحوهی استفاده از منابع محدود حافظهی خود خواهید داشت. بنابراین، درک نحوهی عملکرد لینکر برای موفقیت شما ضروری است.
1. کد پیکربندی را طوری تغییر بده که سگمنت config از ابتدای یک صفحهی حافظه (page boundary) شروع نشود. چه اتفاقی میافتد؟
2. کد پیکربندی را طوری تغییر دهید که به جای چاپ یک عدد یکرقمی برای بازنشانی، کل عدد را چاپ کند.
3. اسکریپت لینکر تعدادی سمبل (symbol) را برای نشان دادن شروع و پایان یک ناحیهی حافظه تعریف میکند. با بررسی اسکریپت لینکر یا نقشهی لینکر، نمادهایی را که شروع و پایان ناحیهی text را تعریف میکنند، پیدا کنید. با استفاده از این نمادها، اندازهی ناحیهی text را چاپ کنید. از دستور arm-none-eabi-size برای تأیید نتیجهی خود استفاده کنید.
4. از همین تکنیکها برای چاپ مقدار فضای استک (stack) اختصاصیافته استفاده کنید.
5. (پیشرفته) مقدار فضای استک باقیمانده را چاپ کنید.برای این کار لازم است مقدار فعلی ثبات stack (رجیستر SP) را با استفاده از کلیدواژهی asm داخل یک متغیر بخوانید.
درک محتوای یک فایل باینری میتواند بسیار مفید باشد، و ابزار گنو (GNU toolchain) دارای تعدادی برنامه برای انجام این کار است. مستندات دستورات زیر را بررسی کنید:
سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.