اخیرا اگر سیسوگ رو دنبال کرده باشید، حتما در جریان مسابقه اورکلاک سیسوگ قرار گرفته اید، با این که تعداد شرکت کننده ها کم بود ولی به جرات می توان گفت رقابت خیلی بالایی بین دوستانی که در این چالش شرکت کرده اند شکل گرفت که نکات خیلی خوبی را پوشش داد، این چالش برخلاف چیزی که در نگاه اول ممکن است به نظر برسد تنها یک چالش سخت افزاری نبوده و اتفاقا بحث های نرم افزاری نقش مهمی را در آن ایفا می کردند، بحث هایی که انتظار می رفت دنبال کننده های سیسوگ با آن آشنایی داشته باشند؛ چرا که در قبلا مقالاتی در خصوص آنها منتشر کرده بودیم، مقالاتی مثل «میکروکنترلر مقصر نیست مقصر برنامه نویسی است» که در آن به روش های سریع تر کردن حلقه ها اشاره کردیم.
در این مقاله سعی میکنیم به بررسی روش هایی برای بالابردن هرچه بیش تر کلاک و در نهایت بالابردن سرعت صفر و یک کردن یک پایه، بپردازیم.
کلاک چگونه و از کجا میآید؟
اولین چیزی که لازمه برای برنده شدن در این چالش بدانیم، آگاهی از سازوکار تامین کلاک در این خانواده است، برخلاف میکروکنترلر های قدیمی تر که لازم بود برای تغییر کلاک اوسیلاتور خارجی را دست کاری کنم، در این خانواده با وجود PLL خیلی کار راحت شده است و تنها نیاز است که چند رجیستر را مقدار دهی کنیم و نهایتا ببینیم آیا میکروکنترلر قادر است در این فرکانس کار کند یا نه، همانطور که در تصور زیر مشخص است، تمام کلاک های درونی به جز واحدهای USB, RTC, WDG از کلاک CPU تامین میشود که البته هم خوبه و هم بده!
بنظر من بیش ترین بدی این قضیه از اونجا میاد که شما برای مدیریت توان نیاز داری مدیریت سرعت پردازنده رو داشته باشی و از اونجایی که با تغییر کلاک پردازنده کلاک تمام قسمت ها دستخوش تغییر می شن، یکم چالش ساز می شه و خوبه چون ساده تره 🙂
کلاک را تا کجا میتوان بالا برد؟
وقتی که کلاک در بازه ایدهآل تنظیم شده، یعنی کلاک پردازنده ۷۲ مگاهرتز و کلاک باس ها هم همه در حالت مناسب و بالاترین مقدار ممکن قرار گرفته است قرار نیست که چالش خاصی داشته باشم ولی برای آورکلاک تنها نیاز است که فرکانس را تا جای ممکن که تجهیزات داخلی به کار خود ادامه دهند (بدون مشکل) بالا ببریم، برای این کار تنها نیاز است PLLMul را افزاریش دهیم، متاسفانه در این خانواده نهایتا تا عدد ۱۶ می توان کلاک را بالا برد که با فرکانس اسیلاتور خارجی ۸ مگ میتوانیم به فرکانس ۱۲۸ مگاهرتز دست پیدا کنیم.
تجربه ثابت کرده که اغلب میکروکنترلرها در این فرکانس به راحتی می توانند به کار خود ادامه دهند و جز مساله حرارتی و مصرف توان بالاتر مشکل خاصی وجود نخواهد داشت! ولی آیا می توان فرکانس را بالاتر برد، باید بگم با این سخت افزار خیر ولی اگر فرکانس اسیلاتور خارجی را به ۱۶ مگاهرتز قادر خواهیم بود که فرکانس را بالاتر ببریم.
با وجود چنین شرایطی نهایتا در بهترین حالت قادر خواهیم بود که فرکانس را تا ۱۴۴ مگاهرتز بالا برریم! اما بالاتر رفتن از فرکانس شرایط خاصی را نیاز دارد مثل سرد کردن یا تغییر در ولتاژ اعمال شده به میکروکنترلر که می تواند ریسک های خاص خودش را به همراه داشته باشد.
اما چه میشود که فرکانس را نمیتوان بالاتر برد؟ به نظر محدودیت فرکانسی حافظه فلش است، همیشه کندترین بخش مدار همان حافظه فلش است که طبق گفته دیتاشیت در حالت کلاک مستقیم (بدون اعمال wait state) قادر است تنها تا ۲۴ مگاهرتز را پوشش دهد! یا شاید هم دلایل دیگری مثل محدودیت فرکانسی خود PLL یا محدودیتهای انتشار در گیت ها یا دلایل دیگری از این دست!
البته روشهایی مثل سرد سازی چیپ، دست کاری ولتاژ ورودی باعث می شه که شما در شرایط کنترل شده قادر باشی فرکانس رو بیشتر از آنچه قابل تصور هست بالا ببرید و یکی از سرگرمیهای هکرها همین بحث آورکلاک کردن سیستمها است و فکر میکنم مادربردهای جدید به شما اجازه انجام چنین تنظیماتی رو میدن و فکر می کنم مسابقاتی در این خصوص نیز انجام میشود.
در نهایت من با اعمال ولتاژ بالاتر در حدود ۴ تا ۴٫۲ و البته سرد سازی به فرکانس های بالاتر دست پیدا کردم حدود ۲۰۰ مگاهرتز که در نهایت منجر به سوختن میکروکنترلر شد!، چرا میگم حدود فرکانس ۲۰۰ مگاهرتز مگر نه این که ضرایب اعداد ثابتی هستند پس فرکانس هم باید دقیقا مشخص باشد؟
دلیل آن است که برای اعمال اورکلاک در ابتدا با فرکانس بالا شروع مدار را راه اندازی نمیکنیم و به تدریج ولتاژ و فرکانس را بالا میبریم تا آستانه تحمل مدار به دست آید، برای همین موضوع هم مقدار دقیق رو نتونستم اندازه گیری کنم.
اما فرکانس هستهای که بیش تر دوستان به آن دست پیدا کردند و میکروکنترلر بدون آسیب می تواند برای مدت حداقل چند دقیقه به کار خود ادامه دهد فرکانس ۱۴۴ مگاهرتز است. این مساله را بارها و با حدود ۵ میکروکنترلر مختلف و با زمانهای حدود ده دقیقهای اندازهگیری شد!
البته از آنجایی که نمونههای تقبلی STM در بازار خیلی زیاد شده است و به خصوص اگر بلوپیل را به شکل ماژول آماده تهیه کرده باشید این احتمال بیش تر هم هست که میکروکنترلر اصلی نباشد و در فرکانس های بالاتر از حد مشخصی به درستی کار نکند بالا میرود.
اما چرا پایه C14 انتخاب شد؟
خیلی از پایه های میکروکنترلر STM32F103C8 داری این قابلیت هستند که توسط واحدهای سخت افزاری مثل تایمرها و مقایسهکنندهها و دیگر واحدها کنترل شوند که در این صورت آستانه فرکانس ایجاد شده در آنها به مراتب بالاتر خواهد بود، حتی پس از کانفیگ PLL روی فرکانس بالاتر و هنگ کردن هسته در صورت انجام تنظیمات صحیح قادر خواهند بود که به کار خود ادامه دهند! ما در این چالش نیاز داشتم پردازنده را درگیر کنیم تا در صورت هنگ کردن آن بتوان از آن طلاع پیدا کنیم. در واقع قصد ما از انتخاب این پایه به چالش کشیدن قابلیت های نرم افزاری دوستان هم بوده است. در ابعادی این چالش یک چالش ترکیبی از درک سخت افزاری و نرم افزاری است.
اما در ادامه خواهیم دید که دوستان شرکت کننده در چالش علاوه بر میکروکنترلر حتی چالش رو هم هک کردن!
خوب بیایید شروع کنیم و برنامه رو بنویسیم!
حالا که کلاک رو بردیم روی آستانه قابل تحمل سخت افزار الان دیگه باید فکر کنیم چطور میتوانیم بهترین برنامه ممکن رو بنویسم که قادر باشه با بالاترین سرعت ممکن پایه رو صفر و یک کنه، برای شروع بیاید همین کاری رو کنیم که خیلی ها انجام میدن، پایه رو تاگل کنیم توی یه حلقه بی نهایت این کار رو انجام بدیم و ببینیم چقدر سریع میشه این کار رو انجام داد.
1 2 3 4 | while (1) { LL_GPIO_TogglePin(GPIOC,LL_GPIO_PIN_14); } |
و بعد از اندازه گیری با تصویر زیر رو به رو می شیم:
که برای کلاک ۱۴۴ مگاهرتز هسته این فرکانس خیلی فرکانس پایینی میاد! بذارید داخل تابع LL_GPIO_TogglePin رو نگاه کنیم ببینم دقیقا داره چکار می کنه…
1 2 3 4 5 6 | __STATIC_INLINE void LL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint32_t PinMask) { uint32_t odr = READ_REG(GPIOx->ODR); uint32_t pinmask = ((PinMask >> GPIO_PIN_MASK_POS) & 0x0000FFFFU); WRITE_REG(GPIOx->BSRR, ((odr & pinmask) << 16u) | (~odr & pinmask)); } |
این چه فاجعه ای است! برای تاگل کردن کلی عملیات روی حافظه داره انجام میشه خوب خیلی بهتر می تونیم اینو خودمون باز نویسی کنیم ولی بگذارید قبل از اون توابع set و reset رو هم ببینیم، اگر به جای تاگل کردن از ست و ریست استفاده کنیم چه تاثیری در عملکرد سرعتی خواهیم داشت؟
1 2 3 4 5 | while (1) { LL_GPIO_SetOutputPin(GPIOC,LL_GPIO_PIN_14); LL_GPIO_ResetOutputPin(GPIOC,LL_GPIO_PIN_14); } |
انگار وضع بدتر شد و فرکانس افت کرد روی ۱٫۶ مگاهرتز اگر به توابع ست و ریست نگاه نکنید متوجه دلیل این افت سرعت خواهید شد، اجازه بدید برگردیم تاگل کردن رو خودمون بنویسیم، برای این کار مقدار رجیتسر ODR رو با خودش و مقدار 0x1UL << (14U) که به معنی همون بیت ۱۴ ام هست xor میکنیم.
1 2 3 4 | while (1) { GPIOC->ODR ^= (1<<14); } |
توی این حالت به فرکانس ۵٫۳۳ مگاهرتز می رسیم ولی خوب هنوز خیلی خیلی فاصله داریم با اونچه که توانایی های میکرو هست، حالا اگر به جای این که یک بار این کار روی توی حلقه تکرار کنیم میایم و چندین بار پشت سر هم اون رو تکرار میکنیم مثل کد زیر:
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 | while (1) { GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); GPIOC->ODR ^= (1<<14); } |
و جواب جالبه!
فرکانس رسیده به ۸ مگاهرتز ولی اون سیکل اخر طولانی شده تقریبا دوبرابر بقیه ! فکر می کنید چرا این اتفاق افتاده؟
بله درسته، حلقه! در انتهای حلقه ما باید به ابتدای اون حرکت کنیم، همین دستور پرش دو سیکل ماشین مصرف می کنه و البته در شرایطی باعث پاک شدن پایپ لاین می شه!
همان طور که در کد اسمبلی بالا میبینید برای هر دستور تاگل کردن ما نیاز داریم که سه دستور اسمبلی رو اجرا کنیم، در واقع رجیستر رو بخوانیم، xor کنیم و دوباره بنویسیم!
اگه برنامه رو ببریم توی رم چی می شه؟
همان طور که قبلا گفتیم طبق گفته دیتاشیت حافظه فلش به شکل مستقیم قادره با فرکانس ۲۸ مگاهرتز کار کنه و وقتی فرکانس کلاک بالاتر می ره برای این که مشکلی توی روند کاریش پیش نیاد ما باید wait state هایی رو بهش اضافه کنیم، ولی چنین موضوعی در خصوص حافظه ram مطرح نیست، خوشبختانه با توجه به معماری ARM ما قادر هستیم که برنامه رو از روی حافظه رم نیست اجرا کنیم، توضیحات ارائه شده در این بخش برای کامپایلر GCC است و اگر از کامپایلر دیگری استفاده می کنید لازمه که داکیومنتیشن مربوط به کامپایلرتون رو مطالعه کنید.
برای این که بتونیم تابعی رو توی رم قرار بدیم باید ویژگی (__attribute__) مدنظر رو توی حافظه رم تعریف کنیم، برای همین لازمه که فایل لینکر رو دست کاری کنیم و مثلا ویژگی code_in_ram را بهش اضافه کنیم، برای همین در سکشن data بخش زیر رو اضافه می کنم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | .data : ALIGN(4) { FILL(0xFF) /* This is used by the startup code to initialise the .data section */ _sdata = . ; /* STM specific definition */ __data_start__ = . ; *(.data_begin .data_begin.*) *(.code_in_ram) *(.data .data.*) *(.data_end .data_end.*) . = ALIGN(4); /* This is used by the startup code to initialise the .data section */ _edata = . ; /* STM specific definition */ __data_end__ = . ; } >RAM AT>FLASH |
بعد از این هر فانکشنی رو که بخوای توی این بخش قرار بگیره (که همون رم هست) باید با ویژگی code_in_ram تعریفش کنیم، مثل تابع زیر:
1 | __attribute__ ((long_call, section (".code_in_ram"))) void xRAM(void) |
به همین سادگی، حالا همون برنامه که اجراش روی فلش خروجی ۸ مگاهرتز رو برای ما ایجاد می کرد و توی این فانکشن قرار میدیم! و همانطور که در تصویر زیر میبینید آدرس کدهای اسمبلی در محدوده حافظه رم است و این به خوبی نشان می دهد که تنظیمات رو ما به درستی انجام داده ایم.
و نتیجه قراره که غافل گیر کننده باشه:
جالبه که فرکانس کاهش پیدا کرد به جای این که بهش اضافه بشه، اما دلیل این موضوع چی می تونه باشه؟ به عکس زیر توجه کنید:
پردازنده به صورت مستقیم با حافظه فلش با یک باس اختصاصی در ارتباط هست، ولی برای دسترسی به حافظه RAM باید از باس مولتیپلکس شده استفاده کنه که همین موضوع باعث کاهش سرعت دسترسیش میشود! البته در میکروکنترلرهای قدیمی تر مثل هسته های ARM7 این مشکل وجود ندارد و وقتی کد را بر روی رم قرار دهید واقعا سریعتر اجرا خواهد شد.
من تلاش کردم تا با منتقل کردن کامل برنامه بر روی رم و از دسترس خارج کردن فلش بتوانم کلاک بالاتری رو برای اجرای برنامه پیاده کنم، چیزی بیش تر از ۱۴۴ مگاهرتز ولی موفق نشدم، اینتراپت ها و فانکشن های مربوطه همه به حافظه رم منتقل شدند ولی به محض این که کلاک از ۱۴۴ مگاهرتز فراتر می رود پردازنده دچار HardFault می شود.
در خصوص چگونگی بالابردن کلاک خروجی در خود مسابقه توضیحات خیلی کاملی ارائه شده است که توصیه می کنم حتما مطالعه کنید. [لینک مسابقه]
واقعا چالش های جالبی مطرح می کنید. درود بر شما 👌🏻👌🏻👌🏻
مطلب جذاب و خیلی قشنگی بود 🌹🌹
میشه توضیح بدید که بالا بردن کلاک بالاتر از فرکانس توصیه شده توسط سازنده، در دنیای واقعی چه مزیتی داره؟
چرا ما باید به جای استفاده از خانواده های قدرتمند تر و پرسرعت تر مثل stm32f4 بیاییم و یک stm32f1 رو به شکل غیر استاندارد سریع کنیم؟
شاید اگر قرار بود ما هم مثل کشورهای پیشرفته در صنعت نیمه هادی، سازندگان این قبیل محصولات باشیم، در آنصورت چنین آزمایشات و تحقیقاتی می تونست مفید باشه.
ولی حالا که ما ۱۰۰ سال تا ساخت همین stm32f1 با اونا فاصله داریم، اگر بخواهیم حداقل مهندس های طراح خوبی باشیم، باید از توصیه سازنده تبعیت کنیم.
فارغ از اینکهبه نظرم این مسابقه خروجی مثبتی در آینده خوانندگان نداره، ولی متوجه شدم که ادمین محترم بحث های تخصصی و سطح بالایی رو انجام می دهند و با صرف زمان حاصل تجربیات خودشون که از سطح علمی مناسب برخوردار هست را با دیگران به اشتراک می گذارند.
لذا از ایشان خواهشمندم که این انرژی را در مواردی مصرف کنند که خوانندگان بتوانند بهره بیشتری در کارهای آینده خود ببرند و بتوانند از معلومات شما و استراک نظرات دیگران استفاده مفید کنند.
فضیه بیشتر جنبه اگاهی و سرگرمی داره، هرچند که نکات خیلی خوبی برای یادگیری در خصوص این چالش وجود داره
درک بهتره عملکرد PLL
محدودیت های موجود در خصوص فرکانس کاری فلش
محدودیت های موجود در خصوص باس های داخلی و درک عمقتر از عملکرد اونها
این که چطور کد رو میشه روی رم قرار داد و به آگاه شدن از این که کار باعث نمیشه کد سریعتر اجرا بشه
و ….