فرض کنید کاربر آستانهٔ یک دستگاه را تغییر میدهد و میخواهد پس از قطع برق همان مقدار بازیابی شود. متغیر معمولی RAM برای این کار کافی نیست. در این درس، بخشی از Flash داخلی STM32F303CB را برای نگهداری تنظیمات کنار میگذاریم و یک روش ذخیرهٔ ترتیبی میسازیم. هدف، شناخت محدودیتهای حافظه و ساخت یک مثال قابل بررسی است؛ این روش تکصفحهای، تضمین بازیابی پس از هر نوع قطع برق ندارد.
وضعیت اعتبارسنجی: کامپایلشده؛ پروژهٔ واقعی بدون خطا و هشدار ساخته شده است. اجرای روی برد، پروگرام و اندازهگیری فیزیکی هنوز انجام نشدهاند.
مثال برای STM32F303CBT6 با بستهٔ LQFP48 روی برد GebraBit GB100ST V2.0 است. قطعهکدها گزیدهٔ پروژهٔ کاملاند و به تعریفها و فایلهای همراه وابستهاند.
پیشنیاز: درس 31.
Flash چه تفاوتی با RAM دارد؟
RAM محل کار موقت برنامه است و پس از قطع تغذیه نباید روی حفظ محتوای آن حساب کرد. Flash حافظهٔ غیرفرار است؛ هم کد برنامه و هم دادهٔ ماندگار میتوانند در آن قرار بگیرند. با این حال، تغییر یک مقدار در Flash مثل انتساب مقدار به یک متغیر RAM نیست.
واحد نوشتن مثال ما نیمکلمه، یعنی 16 بیت یا 2 بایت است. حالت پاکشدهٔ آن 0xFFFF است. برنامهریزی بیتهای 1 را به 0 تبدیل میکند؛ برای بازگرداندن بیتها به 1 باید کل صفحه پاک شود. در این پروژه، فقط نیمکلمهٔ پاکشده را مینویسیم و محل نیمهنوشته را دوباره استفاده نمیکنیم.

برای همین، اگر هر تغییر کوچک را با پاککردن همان صفحه ذخیره کنیم، هم زمان زیادی صرف میشود و هم شمار چرخههای مجاز صفحه سریعتر مصرف میشود. ابتدا سازمان حافظه و سپس روش کاهش دفعات پاککردن را بررسی میکنیم.
سازمان حافظه و رزرو صفحهٔ آخر
تراشهٔ دقیق این دوره 128 KiB حافظهٔ Flash دارد. اندازهٔ هر صفحه 2048 بایت است؛ بنابراین 64 صفحه، از شمارهٔ 0 تا 63، داریم:
128 × 1024 / 2048 = 64 pages
صفحهٔ آخر از 0x0801F800 تا 0x0801FFFF است. در Linker Script، ناحیهٔ برنامه به 126 KiB محدود و ناحیهٔ SETTINGS با اندازهٔ 2 KiB جدا شده است. شرطهای پیوند نیز مرزها را بررسی میکنند. اگر برنامه رشد کند، پیونددهنده باید پیش از ورود آن به صفحهٔ تنظیمات خطا بدهد.

رزرو صفحه در اسکریپت، مانع همپوشانی چیدمان همین برنامه میشود؛ از داده در برابر پاککردن کامل تراشه با ابزار پروگرام یا نوشتن در نشانی اشتباه محافظت سختافزاری نمیکند. بخش SETTINGS از نوع NOLOAD است و تصویر اولیهٔ داده در فایل برنامه ندارد. این رزرو را نباید با مصرف SRAM جمع کرد.
خواندن، بازکردن قفل، نوشتن و قفلکردن
برای خواندن تنظیمات، به بازکردن قفل Flash نیاز نداریم؛ پردازنده از نشانی نگاشتشدهٔ حافظه میخواند. برای تغییر آن، HAL_FLASH_Unlock قفل کنترل را باز میکند. در پایان هر مسیرِ تلاش برای تغییر، HAL_FLASH_Lock دوباره فراخوانی میشود.
پاککردن با HAL_FLASHEx_Erase و پارامترهای FLASH_TYPEERASE_PAGES، نشانی صفحهٔ آخر و NbPages=1 انجام میشود. متغیر PageError پیش از فراخوانی مقداردهی میشود؛ در صورت خطای صفحه، مقدار آن نشانی صفحه است، نه شمارهٔ 63.
برای نوشتن، HAL_FLASH_Program با حالت FLASH_TYPEPROGRAM_HALFWORD استفاده شده است. تابع کمکی پروژه، محدوده، تراز زوج، پاکبودن مقصد و نتیجهٔ بازخوانی را بررسی میکند. خطای کنترل نرمافزاری را نباید همواره خطای PGERR سختافزار فرض کرد.

توابع HAL خودشان نمیدانند کدام صفحه برای دادهٔ ما رزرو شده است؛ کنترل محدوده وظیفهٔ کد برنامه است. کد این درس نه Option Bytes را تغییر میدهد و نه فرمان پاککردن کامل تراشه اجرا میکند.
طبق الزامات این خانواده، HSI باید هنگام برنامهریزی و پاککردن Flash روشن بماند. تنظیم کلاک پروژه HSI را روشن نگه میدارد، هرچند کلاک هسته از PLL با ورودی HSE میآید. برای تغییر آزمایشی تنظیمات کلاک در دیباگر، این شرط را از قلم نیندازید.
یک دفتر ثبت با 128 جایگاه
به جای پاککردن صفحه در هر ذخیره، آن را به جایگاههای 16 بایتی تقسیم میکنیم. هر ذخیره یک رکورد جدید در جایگاه کاملاً خالی مینویسد:
2048 / 16 = 128 slots
رکورد شامل نشان شناسهٔ 0x5A32، نسخهٔ قالب 1، شمارهٔ ترتیبی، مقدار دوره، آستانه، یک مقدار رزروشده، جمع کنترلی و نشان نهایی 0xA55A است. جمع کنترلی، مکمل 16 بیتی جمع 6 نیمکلمهٔ اول است؛ این کنترل ساده، CRC یا ابزار امنیتی نیست.

ابتدا بدنه نوشته و بازخوانی میشود؛ نشان نهایی در آخر نوشته میشود. هنگام بارگذاری، همهٔ 128 جایگاه بررسی و آخرین رکورد معتبر با بیشترین شماره انتخاب میشود. رکورد ناقص یا دارای جمع نامعتبر کنار گذاشته میشود و جایگاه نیمهنوشته دوباره استفاده نمیشود. اگر هیچ رکورد معتبری نباشد، مقادیر پیشفرض 500 و 2048 انتخاب میشوند.
شمارهها از 1 شروع میشوند؛ با رسیدن به حد 0xFFFE، ذخیرهٔ بعدی رد میشود و شمارنده به صفر نمیچرخد. صفحهٔ پر نیز با STORE_FULL گزارش میشود. برنامه برای ایجاد جا، تنها نسخهٔ ماندگار تنظیمات را خودکار پاک نمیکند.
نشان نهایی، نوشتن را در برابر قطع برق به یک عملیات تجزیهناپذیر تبدیل نمیکند. اعتبارسنجی بسیاری از رکوردهای ناقص را رد میکند، اما قطع تغذیه خارج از شرایط مجاز و قطع برق حین پاککردن میتواند دادهها را از بین ببرد. طرح دوصفحهای با قواعد بازیابی جداگانه، موضوع توسعهٔ بعدی است و در این پروژه پیاده نشده است.
طول عمر و وقفهٔ اجرای کد
جدول 48 از DS9118 Rev 14 برای پسوند دمایی 6، مانند CBT6، حداقل 10000 چرخه را در بازهٔ دمایی مشخصشدهٔ منفی 40 تا مثبت 85 درجهٔ سلسیوس بیان میکند. ماندگاری داده نیز به تعداد چرخه و دما وابسته است؛ یک عدد ثابتِ عمر برای همهٔ کاربردها نداریم. نوشتن ترتیبی تعداد پاککردنهای لازم را کاهش میدهد، ولی گردش فرسایش چندصفحهای نیست.
طبق جدول 47، زمان برنامهریزی 16 بیت، 40 تا 60 میکروثانیه با مقدار تیپیک 53.5 است؛ زمان پاککردن صفحهٔ 2 KiB برابر 20 تا 40 میلیثانیه، در شرایط جدول، بیان شده است. برای 8 نیمکلمه، جمع زمان تیپیک برنامهریزی 428 میکروثانیه است؛ این محاسبه، زمان کل تابع یا اندازهگیری روی برد نیست.
8 × 53.5 µs = 428 µs
Flash این تراشه تکبانک است. هنگام مشغولبودن آن، دسترسی برای خواندن دستور یا داده از Flash میتواند متوقف بماند. این به معنی توقف همهٔ واحدهای جانبی نیست. اگر کد وقفه و SysTick هم در Flash باشند، شمارندهٔ زمان نرمافزاری در همان مدت پیش نمیرود؛ پس timeout در HAL یک ساعت مستقل برای تضمین کران زمانی پاککردن نیست. این مثال ادعای سرویسدهی بیوقفه حین نوشتن ندارد.
اجرای پروژه و فرمانهای عمدی در دیباگر
پروژه با HSE برابر 8 MHz و PLL با ضریب 9، کلاک هستهٔ 72 MHz، گذرگاه APB1 برابر 36 MHz و APB2 برابر 72 MHz ساخته شده است. Flash Latency روی 2WS است. ورودی این مثال، متغیر فرمان در دیباگر است؛ UART و دکمهٔ PA3 در برنامهٔ حاضر راهاندازی نشدهاند.

تغییر دستی لینکِر برای رزرو داده؛ این قطعه خروجی خودکار CubeMX معرفی نمیشود. کد C تولیدشدهٔ کلاک در پروژهٔ کامل موجود است.
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 126K
SETTINGS (r) : ORIGIN = 0x0801F800, LENGTH = 2K
.settings (NOLOAD) :
{
__settings_start = .;
. += LENGTH(SETTINGS);
__settings_end = .;
} >SETTINGS
پس از بارگذاری، مقادیر requested_period_ms در محدودهٔ 100 تا 5000 و requested_threshold در محدودهٔ 0 تا 4095 را در دیباگر تعیین کنید. برای ذخیره، flash_command را روی 0x53415645 بگذارید و اجرا را ادامه دهید. فرمان پیش از عملیات مصرف و صفر میشود؛ اجرای دوبارهٔ حلقه به معنی ذخیرهٔ دوباره نیست.
قطعههای دقیق Core/Src/main.c، همگی داخل USER CODE 4؛ وابسته به تعریفها و توابع پروژهٔ کاملاند. قطعهٔ آخر فقط شاخهٔ ثبت نشانگر در Settings_Save است، تابع مستقل نیست.
static HAL_StatusTypeDef Program_Checked(uint32_t address, uint16_t value)
{
if ((address < STORAGE_BASE) || (address > STORAGE_END - 2U) ||
((address & 1U) != 0U) || (*(volatile uint16_t *)address != 0xFFFFU))
{
last_failed_address = address;
return HAL_ERROR;
}
HAL_StatusTypeDef result = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,
address, value);
if ((result == HAL_OK) && (*(volatile uint16_t *)address != value))
{
result = HAL_ERROR;
}
if (result != HAL_OK) { last_failed_address = address; }
return result;
}
for (uint32_t i = 0U; i < 7U; ++i)
{
status = Program_Checked(address + 2U * i, record.word[i]);
if (status != HAL_OK) { result = STORE_PROGRAM_ERROR; break; }
}
if (result == STORE_OK)
{
/* Only after all body halfwords pass readback, write commit LAST. */
status = Program_Checked(address + 14U, record.word[7]);
if (status != HAL_OK) { result = STORE_PROGRAM_ERROR; }
}
پاککردن فقط با فرمان جداگانهٔ 0x45524153 انجام میشود و همهٔ تنظیمات همین صفحه را حذف میکند. این فرمان برای نگهداری آزمایشی صفحه است؛ آن را بهجای SAVE نفرستید. در روشنشدن معمولی، برنامه فقط رکوردها را میخواند.
LED روی PB6 وقتی دستکم یک رکورد معتبر موجود است روشن میشود. مقدار دورهٔ ذخیرهشده در این مثال صرفاً یک تنظیم قابل بازیابی است؛ LED با آن چشمک نمیزند و آستانه نیز ورودی ADC را کنترل نمیکند. هدف این مرحله آزمایش حافظه است.
آزمایش و عیبیابی
پیش از ذخیره، valid_records و مقادیر بارگذاریشده را یادداشت کنید. سپس مقادیر مجاز دلخواه را SAVE کنید، نتیجه را بررسی و با کلید RESET دوباره راهاندازی کنید. انتظار داریم آخرین رکورد معتبر بازیابی شود. نتیجهٔ واقعی را در کاربرگ بنویسید؛ این مراحل روی برد هنوز اجرا نشدهاند.

| وضعیت | بررسی مرتبط با کد |
|---|---|
| valid_records برابر 0 | هیچ رکورد معتبری پیدا نشده؛ پیشفرضها بارگذاری میشوند. این مقدار را با خطای خواندن HAL اشتباه نگیرید. |
| ذخیره رد شده است | last_store_status و محدودهٔ مقادیر ورودی را بررسی کنید؛ رد ورودی نامعتبر پیش از بازکردن قفل رخ میدهد. |
| STORE_FULL یا STORE_SEQUENCE_LIMIT | برنامه ذخیره را متوقف کرده است؛ پاککردن خودکار انجام نمیشود. |
| خطای عملیات یا بازخوانی | last_hal_status، last_flash_error و last_failed_address را با هم بخوانید. خطای کنترل یا بازخوانی ممکن است بدون پرچم سختافزاری باشد. |
| خطای پاککردن صفحه | last_page_error نشانی است. تنظیمات حفاظت را فقط بررسی کنید؛ تغییر خودکار Option Bytes جزو این آزمایش نیست. |
برای بررسی فایل ساخت، مصرف واقعی این نسخه 10520 بایت Flash و 1672 بایت RAM اصلی است. رزرو 2048 بایتی SETTINGS جزو RAM نیست. گزارش جزئی بخشها، مدل رکوردهای ناقص و آزمون مرزها در فایلهای همراه آمدهاند؛ نتیجهٔ مدل، جای آزمایش قطع برق روی سختافزار را نمیگیرد.
پرسشهای خودآزمایی
1. چرا نمیتوان یک متغیر Flash را مثل RAM بازنویسی کرد؟
برنامهریزی و پاککردن واحدها و قواعد متفاوت دارند؛ بازگرداندن بیتها به 1 نیازمند پاککردن کل صفحه است.
2. رزرو صفحه در Linker Script چه چیزی را تضمین میکند؟
چیدمان برنامهٔ پیوندشده از مرز ناحیهٔ تعیینشده عبور نمیکند؛ این کار مانع پاککردن با ابزار پروگرام یا نشانی اشتباه API نیست.
3. چرا نشان نهایی آخر نوشته میشود؟
تا رکوردی که بدنهٔ آن کامل و بازخوانی نشده، بهسادگی معتبر تلقی نشود؛ این کار تضمین اتمی بودن در قطع برق نیست.
4. صفحهٔ پر چه میشود؟
STORE_FULL برمیگردد و برنامه خودکار صفحه را پاک نمیکند.
5. آیا مقدار صفر HAL_GetError یعنی ذخیره حتماً موفق بوده؟
خیر؛ کنترل نرمافزاری محدوده، ورودی یا بازخوانی هم میتواند شکست بخورد. وضعیت برنامه و HAL باید کنار هم بررسی شوند.
6. آیا انتقال یک تابع به RAM همهٔ توقفهای Flash را حذف میکند؟
خیر؛ تابعهای فراخوانیشده، ثابتها و بردارها یا کد وقفه نیز ممکن است به Flash دسترسی داشته باشند.
تمرینها
1. پایه؛ بازیابی پس از ریست
یک تنظیم مجاز ذخیره کنید و بدون ERASE، برد را ریست کنید.
معیار بررسی: مقدار بازیابیشده، شمارهٔ رکورد و وضعیت ذخیره را قبل و بعد ثبت کنید.
2. متوسط؛ مشاهدهٔ دفتر ثبت
چند مقدار متفاوت ذخیره و جایگاههای صفحه را در پنجرهٔ حافظه بررسی کنید.
معیار بررسی: رکوردهای قبلی تغییر نکرده باشند؛ نشان پایانی و اندازهٔ 16 بایتی هر رکورد را مشخص کنید.
3. پیشرفته؛ طراحی بازیابی
مدل همراه را بررسی و طرحی برای نگهداری دوصفحهای پیشنهاد کنید.
معیار بررسی: توضیح دهید پیش از پاککردن هر صفحه کدام نسخهٔ معتبر باقی میماند و پس از قطع برق چگونه انتخاب میشود؛ این طرح را آزمودهشده معرفی نکنید.
منابع و نسخهٔ ساخت
| مرجع | کاربرد |
|---|---|
| DS9118 Rev 14 | ظرفیت دقیق تراشه، پایهها و مشخصات حافظه |
| RM0316 Rev 10 | نقشهٔ حافظه و سازوکار واحدهای خانواده؛ بخشهای مرتبط بررسی شدهاند |
| ES0204 Rev 11 | محدودیتهای وابسته به بازنگری سیلیکون |
| GebraBit GB100ST V2.0 | مدار برد، پایهها و انتخاب بوت |
| GNU ld: MEMORY | تعریف ناحیهها و محدودیت فضای پیوند |
| GNU ld: Output Section LMA | تفاوت نشانی بارگذاری و اجرا و مسئولیت مقداردهی |
| stm32f3xx_hal_flash.c | رفتار دقیق بازکردن قفل، برنامهریزی، پاککردن و خطاها در commit ثبتشده |
| stm32f3xx_hal_flash_ex.c | رفتار دقیق بازکردن قفل، برنامهریزی، پاککردن و خطاها در commit ثبتشده |
ساخت واقعی با CubeMX 6.14.0 و DB 6.0.140، بستهٔ CubeF3 V1.11.6 و GCC 13.2.1 در مسیر CMake انجام شده است. گزینههای Debug شامل -O0 -g3 -Wall -Werror هستند. شناسهٔ بسته 663d2ba و HAL 953955a است؛ شناسههای کامل و گزارشها در فایلهای همراه آمدهاند. رابط گرافیکی IDE و اجرای فیزیکی برد آزمایش نشدهاند.
دانلود فایلهای همراه درس 32
بسته شامل پروژهٔ واقعی با IOC و سورس، خروجیهای ELF/MAP و گزارش ساخت، مدل محاسباتی، کاربرگ آزمایش، مجوزها و 6 تصویر همراه اسکریپت قابل ویرایش است. ابتدا ZIP را استخراج و README را بخوانید. اجرای روی برد و اندازهگیریها هنوز انجام نشدهاند.