AVR, توصیه شده, مقاله

میکروکنترلر مقصر نیست مقصر برنامه نویسی است

حتما برای شما هم پیش آمده که در پروژه ای به دلایل زیادی از قبیل کم بود حافظه فلش ، کمبود حافظه رم ، سرعت پایین و… مجبور شده اید میکروکنترلر را عوض کنید و زحمت برنامه نویسی مجدد را به جان بخرید در این موارد معمولا تمام کاسه کوزه ها سر میکروکنترلر شکسته می شود و محدودیت های آن مورد سرزنش و شماتت قرار می گیرد! در صورتی که همیشه اینچنین نیست. در اغلب اوقات با تغییرات جزیی در مدار یا سبک برنامه نویسی به راحتی میتوان مشکلات پیش رو حل کرد ؛

متاسفانه خیلی از دوستان و همکاران گرامی به این موضوع اشراف ندارند و به اشتباه میکروکنترلر را مقصر می دانند؛ به عنوان مثال خیلی از دوستان ، میکروکنترلر AVR را یک میکروکنترلر صنعتی نمی دانند ، در عوض میکروکنترلر PIC یا ARM را صنعتی می دانند، برای شخصی که تجربه کافی در خصوص طراحی مدارات دیجیتال و صنعتی داشته باشد ، این استدلال نه تنها بی پایه و اساس است بلکه خنده دار هم خواهد بود؛ محدودیت ها و باید و نباید های هر پردازنده ای در منوال آن ذکر شده است ، با رعایت این نکات و البته طراحی صحیح هر میکروکنترلری را می توان در هر جایی که مورد نیاز باشد استفاد کرد، البته منکر این مهم نیستم که برخی میکروکنترلر ها برای مقاصد خاصی طراحی و تولید می شوند. چه مدارات صنعتی که بر پایه همین میکروکنترلر AVR طراحی شده اند ؛ مهم نکاتی است که باید در طراحی لحاظ کرد. در این مقاله قصد داریم نشان دهیم که چقدر مدل برنامه نویسی می تواند در عملکرد یک میکروکنترلر دخیل باشد و در خیلی از هزینه ها صرفه جویی کنید پس با سیسوگ همراه باشد.

معرفی میکروکنترلر و نحوه آزمایش سبک برنامه نویسی

بورد میکروکنترلر مورد استفاده تست مبتنی بر آردوینو

در این تست از میکروکنترلر ATMEGA328 در فرکانس 16 مگاهرتز (برد آردوینو البته از Gcc برای کامپایل کد استفاده خواهیم کرد نه ابزار آردوینو) استفاده خواهیم کرد و با اتصال یک عدد LCD رنگی به صورت سریال راه اندازی می‌شود قصد داریم سرعت رسم فریم های تصویر را اندازه گیری کنیم. برای این کار مدت زمان رسم ده فریم از تصویر را اندازه گیری می‌کنیم و نمایش می‌دهیم. LCD مذکور از انتقال 9 بیت سریال برای رسم تصویر استفاده می کند و واحد SPI موجود در میکروکنترلر نهایتا در حالت 8 بیتی کار می کند و نمی توانیم از آن استفاده کنیم پس این قسمت از برنامه را  مجبوریم که با کدنویسی پیاده سازی کنیم. در ادامه بررسی خواهیم کرد که مدل های برنامه نویسی چه تاثیری در سرعت اجرای آن خواهند داشت.

برنامه اول ، برنامه‌ای که همه می نویسند

برای نوشتن برنامه ای که 9 بیت را به صورت سریال انتقال دهد یکی از مرسوم ترین راه ها استفاده از حلقه for می باشد. در اولین قدم ما نیز برنامه را به روش رایج می نویسیم:

همانطور که می بینید ابتدا پایه CS را صفر میکنیم ، بعد با استفاده از حلقه For 9 بیت داده را انتقال می دهیم و سپس پایه CS را یک می‌کنیم!

مساله ای که برای ما حائز اهمیت است ، سرعت اجرای این کد است ، فرض کنید قصد داریم تصویر متحرکی را روی صفحه نمایش دهیم ، اگر سرعت رفرش شدن صفحه از حدی بیشتر شود ، اصلا امکان چنین کاری نیست ، با اجرای این کد خروجی زیر را خواهیم داشت

سرعت اجرای برنامه اول نوکیا

همانطور که در تصویر مشخص است ، اجرای ده فریم از تصویر (یعنی ده بار پر کردن صفحه) زمانی حدود 20465 میلی ثانیه نیاز داشته است ، به عبارتی هر بار پر کردن صفحه دوثانیه طول می کشد. در این مرحله عده ای نا امید می شوند و تقصیر را به گردن میکروکنترلر می اندازند و سراغ میکروکترلر قوی تری میروند ! اگر دید درستی به سخت افزار و نرم افزار داشته باشیم به سادگی میشه حدس زد که مشکل از کجاست و چطور میشه حلش کرد!

در این مثال بگذارید اول بررسی کنیم ، برای پر کردن صفحه از رنگی خاص چقدر زمان CPU صرف میشه ، صفحه نمایشگر ما 160 در 120 است یعنی 19200 پیکسل باید دیتا دریافت کنند ، برای پر کردن هر پیکسل نیازه که دوبار این تابع قراخوانی بشه ، میشه در واقع 38400 بار باید این تابع رو فراخوانی کنیم ! حالا چطور میتونیم برنامه رو بهینه تر کنیم ، داخل for رو نگاه کنید ! با توجه به این که دستورات درون for با هربار اجرای تابع 9 مرتبه اجرا می شوند ، برای پر کردن صفحه لازمه

345600 بار اجرا شوند. یعنی صرفه جویی در یک سیکل ماشین درون روتین حلقه ، به تعداد 345600  سیکل ما را سریعتر می کند ، با توجه به فرکانس کاری 16 مگ ، هر سیکل صرفه جویی ، معادل 21.6 میلی ثانیه خواهد بود! پس اصلا دست کم نگیریدش ! اما چطور میشه برنامه رو بهینه تر نوشت ؟

برنامه دوم ، برنامه‌ای که بعد از تفکر می نویسید

بعد از کمی فکر کردن در خصوص این که چطور میتونید برنامه رو بهینه کنید ، با مقداری هوش – برنامه رو این چنین خواهید نوشت :

اما چه تغییری کرد برنامه ؟ ، در واقع فرایند مقایسه را بهینه کردیم ، در برنامه قبل عملیات مقایسه به صورت زیر بود

در خط فوق برای هر بیت ، اول 8 را از متغیر i کم می کردیم ، بعد عدد 1 به تعداد حاصل شیفت می دادیم به سمت چپ و بعد با مقدار data اند می کردیم و بر اساس آن خروجی را تنظیم می کردیم ! این فرایند ها را برای پر کردن صفحه 345600  بار تکرار می کردیم ! اگر بتوانید این فرایند را حذف کنیم ، قطعا تعداد سیکل خیلی زیادی صرفه جویی می‌کنیم. اما چطور ؟ خیلی ساده است با تعریف یک جدول مقایسه (lookup table) !

یعنی حاصل عبارتی را میکروکنترلر باید یک بار تفریق و یک بار شیفت را برایش انجام می‌داد صورت دستی محاسبه کنیم و در یک آرایه ذخیره کنیم ، و در شرط مقدار مورد نظر را از آن استخراج کنیم ، اما ببینم این کار تا چه اندازه اثر بخش خواهد بود ؟

برنامه بهینه و سرعت اجرای برنامه دوم

بله ، همین کار ساده سرعت اجرای برنامه را تقریبا 2 برابر افزایش داد ؛ یعنی برای رسم هر فریم نیاز به 1 ثانیه زمان بیشتر نخواهید داشت ! اما آیا باز میشود کد را بهینه تر نوشت ؟

برنامه سوم ، برنامه‌ای که با خلاقیت و شناخت سخت افزار می نویسید

در برنامه قبلی با استفاده از یک تکنیک ساده سرعت اجرای برنامه دو برابر شد ، اما آیا راه دیگری هست که باز هم بشود برنامه را بهینه کرد ؟، اینجاست که دید شما نسبت به سخت افزار و آشنایی با معماری پردازنده به کمک شما میآد ، بیایید نگاهی به کد اسمبلی تولید شده بیندازیم

اسمبلی میکروکنترلر AVR

اگر دقت کنید در خط 624 و 625 دو دستور اسمبلی داریم با نام های LD ، کار این دستور ها لود کردن مقداری است که Z به اون اشاره میکنه (انتخاب اینکدس مورد نظر از آرایه تعریف شده ShiftBit) ، مطابق اونچه داخل دیتاشیت AVR موجوده هر کدام از این دستورات به 2 سیکل ماشین نیاز داره ، دستور brne که هم برای پیاده سازی if استفاده شده هم برای پیاده سازی for ، اجراش نیاز به 1 یا 2(در صورتی که پرش انجام بشه) سیکل ماشین داره ، خود پیاده سازی حلقه هم نیاز به cpc و cpi داره که هر کدوم یک سیکل ماشین رو مصرف می‌کنن !

با توصیفات بالا مشخص میشه که حذف حلقه و انتخاب از ایندکس آرایه خیلی در سیکل های ماشین مصرف شده صرفه جویی خواهد کرد ، پس برنامه را به شکل زیر تغییر می دهیم

بله یا یک روش ساده و حذف حلقه ؛ برنامه به شکل فوق در خواهد آمد !

 

برنامه آخر بهینه سازی آخر

همانطور که میبیتید با این تغییر سرعت اجرا باز دوبرابر شد  یعنی رسم هر فریم تصویر تقریبا 550 میلی ثانیه زمان خواهد برد و این یعنی چهار برابر سریعتر از برنامه اول !

می بینید که به سادگی و فقط یا تغییر در مدل کد نویسی می توان نتیجه خیلی بهتری را از سخت افزار مورد نظر گرفت ، تاجایی که دیگر لازم به تغییر میکروکنترلر نباشد!

مهم نیست از چه میکروکنترلری استفاده می کنید؛ ARM یا AVR مهم این است که چقدر در کار با آن تجربه و تبحر دارید!

مقایسه سرعت بروزرسانی تصویر

 

چالش برنامه نویسی آخر

خوب تا اینجا دیدیم که چقدر نحوه برنامه نویسی می‌تونه توی نتیحه خروجی(حداقل در این مورد خاص سرعت) تاثیر گذار باشه ! به عنوان چالش برای شما ، همین برنامه را با همین ساختار باز بهینه کردم ، ولی این باز شما حدس بزنید به چطور میشه این کار رو انجام داد.

چالش برنامه نویسی

 

البته با استفاده از تکنیک های دیگر برنامه نویسی همچنان میشه سرعت را زیادتر کرد

 

نوشته های مشابه

6 دیدگاه در “میکروکنترلر مقصر نیست مقصر برنامه نویسی است

  1. علی گفت:

    با سلام
    اگر در تعریف lookup table از کلمه کلیدی const استفاده میشد بهتر نبود؟ چراکه در اینصورت در حافظه فلش ذخیره میشد. هرچند که مطمئن نیستم که آیا این باعث کندتر خواندنش میشود یا خیر.

    1. زئوس Zeus زئوس Zeus گفت:

      خوب ، البته بسته به مدل طراحی پردازنده میتونه قضیه متفاوت باشه ، ببینید حافظه RAM سریعتر است و پردازنده به صورت مستقیم معمولا بهش دسترسی داره اما حافظه Flash بسته به ساخناری که داره کندتر از RAM هست و توی برخی معماری ها پردازنده دسترسی مستقیم بهش نداره مثل خانواده های ARM پس تعریف const میتونه باعث کاهش سرعت بشه

  2. محمد گفت:

    با عرض سلام مجدد
    این برنامه رو هم یکم بهینه تر میشه نوشت البته باز هم بدون استفاده از تابع یعنی:
    cs_clr();

    if(data & 0x100)) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & 0x80)) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & 0x40)) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & 0x20)) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & 0x10)) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & 0x08)) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & 0x04)) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & 0x02)) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & 0x01)) sda_set(); else sda_clr();clk_set();clk_clr();

    cs_set();

    1. زئوس Zeus زئوس Zeus گفت:

      سلام
      پیشنهاد خوبیه ولی اگر کد اسمبلی رو نگاه کنید موقع کامپایل خود کامپایلر کد قبلی رو به همین حالت تغییر میده ! 🙂
      ببینید به عنوان راهنمایی باید بگم باید دیگه اسمبلی کدها رو بنویسید و در بهترین حالت قرار بدید

  3. محمد گفت:

    درود
    به نظر من اگه واسه این کار از تابع استفاده نشه و به جای تابع lcd_send از خود دستورات به جای فراخوانی تابع استفاده بشه سرعت بالاتر خواهد رفت چون دستور پرش به یک زیر برنامه و بازگشت به برنامه اصلی چند سیکل رو توی برنامه تلف میکنه و راه حل استفاده نکردن از تابع برای دستیابی به حداکثر سرعت هستش.
    static uint16_t ShiftBit[] = {0x100,0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
    cs_clr();

    if(data & ShiftBit[0])) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & ShiftBit[1])) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & ShiftBit[2])) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & ShiftBit[3])) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & ShiftBit[4])) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & ShiftBit[5])) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & ShiftBit[6])) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & ShiftBit[7])) sda_set(); else sda_clr();clk_set();clk_clr();
    if(data & ShiftBit[8])) sda_set(); else sda_clr();clk_set();clk_clr();

    cs_set();

    1. زئوس Zeus زئوس Zeus گفت:

      درود
      پیشنهاد خوبیه ، این کار بی تاثیر نیست ، شاید در حدود 20 میلی ثانیه نه 200 میلی ثانیه
      ولی همین اقدامات کوچیک باعث این بهبود ها میشه 🙂

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *