در AVR و در اکثر میکروکنترلرها قسمتی از حافظه وجود دارد که به آن بوت لودر میگویند. بوت لودر قسمتی در آخر حافظه Flash است که وظیفه اصلی آن ذخیره برنامهای جهت پروگرام کردن میکروکنترلر است. به عنوان مثال میتوان برنامهای در بوت لودر avr نوشت که برنامه اصلی را از طریق سریال USART یا SPI گرفته و بر روی حافظه پروگرام کند.
نکته: در این آموزش مقادیر آدرس حافظه و همچنین حجم حافظه بوت، برای میکروکنترلر Atmega8 محاسبه شده است. اما توابعی که در ادامه معرفی شدهاند برای تمامی AVR ها یکسان هستند.
تنظیمات بوت لودر و فعال سازی آن
در آموزش فیوز بیت های atmega8 و لاک بیت ها، 3 فیوزبیت مربوط به بوت لودر را شرح دادیم. برای اینکه با وصل شدن تغذیه میکرو و یا ریست کردن آن، ابتدا بوت لودر اجرا شود، بایستی فیوزبیت BOOTRST پروگرام گردد. از طرفی اندازه حافظه بوت لودر با فیوزبیتهای BOOTSZ0 و BOOTSZ1 مشخص میشود که در ATmega8 به صورت زیر است.
با توجه به عکس بالا آدرس شروع حافظه بوت لودر بستگی به مقادیر BOOTSZ0 و BOOTSZ1 دارد. به عنوان مثال اگر مقدار این دو فیوز برابر 00 باشد، بر اساس اطلاعات داخل دیتاشیت، مقدار بوت لودر 2048 بایت است. بنابراین چون اندازه کلی حافظه فلش 8 کیلوبایت یا 8192 بایت است، آدرس شروع بوت لودر برابر 6144 = 2048 – 8192 است. اگر 6144 بایت را به فرمت هگزا دسیمال تبدیل کنیم، مقدار آن 0x1800 خواهد شد.
بر اساس توضیحات فوق، اگر فیوزبیت BOOTRST را فعال کنیم، میکرو به جای شروع برنامه از آدرس 0x0000، از آدرس 0x1800 کار خود را آغاز میکند (اگر BOOTSZ برابر 00 باشد). نکتهای که حائز اهمیت است، تولید فایل hex برای بوت لودر شرایطی دارد که باید در نرم افزار CodeVision یا Atmel Studio تعیین شود.
آدرس بوت لودر avr در Atmel Studio
پس از ایجاد پروژه در Atmel Studio برای اینکه فایل هگز (Hex) تولیدی مختص بوت لودر باشد، باید آدرس شروع برنامه بوت لودر مشخص شود. در Atmel Studio بر روی پروژه راست کلیک کرده و گزینه Properties را انتخاب کنید.
با انتخاب Properties صفحه زیر ظاهر میشود.
در صفحه فوق با انتخاب تب Toolchain و سپس انتخاب گزینه Memory Settings، قسمتی باز میشود که میتوان در بخش Flash Segment با اضافه کردن عبارت text=0x0C00. به کامپایلر فهماند که برنامه کامپایل شده باید در آدرس 0x0C00 قرار گیرد.
نکته: اگر BOOTSZ برابر 00 باشد، آدرس شروع بوت لودر 0x1800 است. اما از آنجایی که آدرس دهی سخت افزاری به صورت 16 بیتی یا همان 2 بایتی است، باید آدرس فوق را تقسیم به 2 نمود که میشود 0x0C00.
آدرس بوت لودر avr در CodeVision
در کدویژن نیازی به محاسبه آدرس بوت لودر نیست. از تب Project گزینه Configure را انتخاب کنید.
با انتخاب Configure صفحه تنظیمات باز میشود.
در صفحه فوق، در تب C Compiler و سپس در تب Code Generation گزینهای به نام Program Type وجود دارد که 5 حالت ممکن دارد. اولین حالت مربوط به برنامه اصلی است و پیش فرض بر روی این گزینه است. 4 حالت دیگر مربوط به بوت لودر است که هر کدام اندازههای متفاوتی دارند. چون فیوزبیتهای BOOTSZ را 00 در نظر گرفتیم، اندازه بوت لودر 2048 بایت یا 1024 ورد میشود. با انتخاب Bootloader – 1024w برنامه برای بوت لودر نوشته شده و فایل هگز تولیدی نیز مختص بوت لودر خواهد شد.
توابع کار با بوت لودر در Atmel Studio
پس از آماده سازی نرم افزارها برای نوشتن در بوت لودر avr، با استفاده از توابع اماده میتوان حافظه اصلی را به هر نحوی که بخواهیم پروگرام کنیم. در Atmel Studio با وارد کردن کتابخانه avr/boot.h توابع زیر به برنامه الحاق میشوند. دقت شود که از این توابع فقط میتوان در بوت لودر استفاده کرد نه در برنامه اصلی.
تابع boot_page_erase
این تابع یک پارامتر ورودی میگیرد و وظیفه آن پاک کردن یک page از حافظه Flash است. مفهوم Page چیست؟ در هر کدام از میکروکنترلرهای AVR حافظه به بخش های بزرگتری تقسیم میشوند که به هر کدام Page میگویند. مثلا در Atmega8 مقدار هر Page برابر 64 بایت است. یعنی 64 بایت اول Flash را page 0 میگویند، 64 بایت دوم را Page 1 میگویند و … . در Atmega16 هر page برابر 128 بایت است.
به عنوان مثال اگر تابع Boot_page_erase(0) را با پارامتر 0 صدا بزنیم، 64 بایت اول حافظه فلش پاک میشود. اگر با پارامتر 64 صدا بزنیم 64 بایت دوم پاک میشود و اگر با مقدار 256 صدا بزنیم، 64 بایت پنجم حافظه پاک میشود.
نکته: تعداد بایت های هر Page در ثابتی به نام SPM_PAGESIZE ذخیره شده است (SPM_PAGESIZE در واقع یک define# است).
تابع boot_page_fill
با فرض اینکه میکرو Atmega8 باشد (SPM_PAGESIZE = 64)، مکانیزم نوشتن یا پروگرام کردن به این صورت است که ابتدا باید 64 بایت اول را در حافظه temporary نوشت؛ سپس 64 بایت دوم و همینطور 64 بایت سوم و تا آخر … .
حافظه موقت یا بافر موقت (temporary page buffer)، حافظه ای موقت است که در Atmega8 برابر 64 بایت بوده (دقیقا برابر SPM_PAGESIZE) و وظیفه اصلی آن انتقال بایتهای برنامه به حافظه فلش است. به عبارتی دیگر، برای پروگرام کردن برنامه ای که 388 بایت حافظه اشغال میکند، باید در مجموع 7 بار بر روی حافظه موقت نوشت: 388 = 4 * 1 + 64 * 6.
تابع boot_page_fill وظیفه پر کردن حافظه موقت را دارد. این تابع 2 پارامتر ورودی دریافت میکند. پارامتر اول آدرس بایت و پارامتر دوم یک مقدار 16 بیتی یا 2 بایتی است که باید بر روی حافظه موقت ریخته شوند (در مثالی که جلوتر آمده است روش کار با این تابع کاملا شفاف توضیح داده خواهد شد). به عنوان مثال اگر حافظه موقت 64 بایتی باشد، باید در یک حلقه که 32 بار تکرار میشود مقادیر را بصورت 2 بایتی بر روی حافظه موقت نوشت. سپس با صدا زدن تابع boot_page_write مقادیر حافظه موقت را به حافظه فلش منتقل کرد.
تابع boot_page_write
این تابع وظیفه انتقال مقادیر موجود در حافظه موقت به حافظه فلش را دارد. تنها یک پارامتر ورودی دریافت میکند که آدرس قسمتی از حافظه فلش است که مقادیر بافر موقت به آنجا منتقل میشوند.
تابع boot_spm_busy_wait
از آنجا که توابع قبلی، مدت زمانی طول میکشد تا عملیات خود را انجام دهند، با صدا زدن این تابع میتوان منتظر ماند تا کارهایی نظیر نوشتن در بافر موقت یا انتقال اطلاعات از بافر به حافظه فلش، تمام شود. این تابع هیچگونه پارامتر ورودی ندارد.
تابع boot_rww_enable
پس از اتمام پاک کردن و یا پروگرام کردن حافظه که توسط توابع فوق انجام میشود، در صورتی که بخواهیم حافظه را دوباره فعال کنیم که برنامه نوشته شده قابل اجرا باشد، باید تابع boot_rww_enable صدا زده شود.
مثال 1 : اجرا برنامه چشمک زن ساده در حافظه بوت
میخواهیم برنامهای بنویسیم که در حافظه بوت اجرا شود و یک LED متصل به پایه B.0 را به فواصل 0.5 ثانیه خاموش و روشن کند. هدف از این مثال، یادگیری نحوه تولید فایل هگز برای حافظه بوت و همچنین تنظیم فیوزبیتهای میکروکنترلر است.
اولین مرحله مشخص کردن وضعیت فیوزهای BOOTSZ1، BOOTSZ0 و BOOTRST است. چون میخواهیم برنامه از اول حافظه بوت شروع شود، باید فیوزبیت BOOTRST را پروگرام کرد (یعنی 0 کرد). از آنجا که برنامه چشمک زن حافظه خیلی کمی اشغال میکند، از مقادیر مختلف حافظه بوت ( 256 و 512 و 1024 و 2048 بایت) میتوان گزینه اول یعنی 256 بایت را انتخاب کرد. بنابراین مقدار فیوزهای BOOTSZ برابر “11” خواهد شد.
دومین مرحله مشخص کردن آدرس شروع بوت لودر در Atmel Studio است. چون مقدار حافظه بوت لودر 256 بایت و کل حافظه 8192 بایت است، بنابراین آدرس شروع میشود: 7936 = 256 – 8192. اما با توجه به توضیحات اول آموزش، مقدار به دست آمده باید تقسیم به 2 شود؛ یعنی 3968 = 2 / 7936؛ که مقدار 3968 در مبنای هگز دسیمال میشود 0x0F80. در نتیجه باید در تنظیمات Memory Setting مقدار شروع حافظه را 0x0F80 تعیین کنیم.
و در نهایت نوشتن یک برنامه چشمک زن ساده که بصورت زیر است.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#define F_CPU 8000000L #include <avr/io.h> #include <util/delay.h> int main(void) { DDRB = 0x01; while (1) { PORTB = 0x01; _delay_ms(500); PORTB = 0x00; _delay_ms(500); } } |
با کامپایل کردن برنامه فوق، فایل هگزی تولید میشود که مختص بوت لودر atmega8 با سایز 256 بایت است.
مثال 2 : پروگرام کردن برنامه چشمک زن از حافظه بوت بر روی حافظه اصلی
برنامهای مینویسیم که یک LED را به مدت 100 میلی ثانیه خاموش روشن کند. حال فایل هگز این برنامه را درون یک برنامه بوت بارگذاری میکنیم و با استفاده از توابعی که شرح داده شد، بر روی میکرو پروگرام خواهیم کرد. از آنجا که حجم برنامه بوت لودر در این مثال بیشتر از مثال قبلی خواهد شد، باید اندازه حافظه بوت بوت لودر را بیشتر انتخاب کرد. اگر هر دو فیوز بیت BOOTSZ0 و BOOTSZ1 را برابر 0 کنیم، اندازه بوت لودر 2048 بایت میشود. بنابراین در تنظیمات Atmel Studio در قسمت Memory Setting مقدار text=0x0C00. را وارد کنید. محاسبات به صورت زیر است.
Memory Setting => 8192 – 2048 = 6144 => 0x1800 / 2 = 0x0C00
برنامه چشمک زن بصورت زیر است.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#define F_CPU 8000000L #include <avr/io.h> #include <util/delay.h> int main(void) { DDRB = 0x01; while (1) { PORTB = 0x01; _delay_ms(100); PORTB = 0x00; _delay_ms(100); } } |
با کامپایل کردن برنامه فوق، ساختار فایل هگز تولید شده بصورت زیر میشود.
در شکل بالا محتوای درون خطوط قرمز برنامه چشمک زن است که باید بر روی میکروکنترلر پروگرام شود. در مورد ساختار فایل هگز، در آموزش قبلی به صورت مفصل صحبت شد. در حال حاضر تنها مسئلهای که مد نظر است، قرار دادن این مقادیر درون یک برنامه بوت و پروگرام کردن آنها به واسطه توابع قبلی میباشد. برنامه بوت لودر بصورت زیر است.
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 |
#define F_CPU 8000000L #include <avr/io.h> #include <avr/boot.h> #include <util/delay.h> uint8_t prog[106] = { 0x12, 0xC0, 0x19, 0xC0, 0x18, 0xC0, 0x17, 0xC0, 0x16, 0xC0, 0x15, 0xC0, 0x14, 0xC0, 0x13, 0xC0, 0x12, 0xC0, 0x11, 0xC0, 0x10, 0xC0, 0x0F, 0xC0, 0x0E, 0xC0, 0x0D, 0xC0, 0x0C, 0xC0, 0x0B, 0xC0, 0x0A, 0xC0, 0x09, 0xC0, 0x08, 0xC0, 0x11, 0x24, 0x1F, 0xBE, 0xCF, 0xE5, 0xD4, 0xE0, 0xDE, 0xBF, 0xCD, 0xBF, 0x02, 0xD0, 0x18, 0xC0, 0xE4, 0xCF, 0x81, 0xE0, 0x87, 0xBB, 0x88, 0xBB, 0x2F, 0xEF, 0x30, 0xE7, 0x92, 0xE0, 0x21, 0x50, 0x30, 0x40, 0x90, 0x40, 0xE1, 0xF7, 0x00, 0xC0, 0x00, 0x00, 0x18, 0xBA, 0x2F, 0xEF, 0x30, 0xE7, 0x92, 0xE0, 0x21, 0x50, 0x30, 0x40, 0x90, 0x40, 0xE1, 0xF7, 0x00, 0xC0, 0x00, 0x00, 0xEB, 0xCF, 0xF8, 0x94, 0xFF, 0xCF }; void program_page(uint8_t *buffer, uint16_t size, uint32_t address) { uint16_t i; uint16_t prog_word; uint16_t max_size = size + (size % 2); uint16_t page_address = address % SPM_PAGESIZE; for (i=0; i<max_size; i+=2) { prog_word = *buffer++; prog_word |= *buffer++ << 8; if (size == (i + 1)) { prog_word = *buffer++; prog_word |= 0xFF00; } boot_page_fill(page_address + i, prog_word); } boot_page_write(address); boot_spm_busy_wait(); } void erase_page(uint16_t start, uint16_t lenght) { for (uint16_t i=start; i<(start + lenght); i++) { boot_page_erase(i * SPM_PAGESIZE); boot_spm_busy_wait(); } } int main(void) { erase_page(0, 2); program_page(prog, 64, 0); program_page(prog + 64, 42, 64); boot_rww_enable(); DDRB = 0x01; for (int i=0; i<3; i++) { PORTB = 0x01; _delay_ms(1000); PORTB = 0x00; _delay_ms(1000); } asm("ldi r30, 0x00;"); asm("ldi r31, 0x00;"); asm("ijmp;"); } |
توضیح توابع برنامه بوت لودر
در اول برنامه کتابخانههای مورد نیاز فراخوانی شدهاند که وظیفه هر کدام کاملا واضح است. سپس یک آرایه 106 بایتی تعریف شده که مقادیر برنامه چشمک زن را درون خود دارد. بنابراین باید دقیقا این مقادیر را بر روی میکروکنترلر پروگرام کنیم. در برنامه 2 تابع تعریف کردیم. یکی به نام program_page و دیگری erase_page که اولی برای پروگرام کردن یک page از میکرو و دیگری برای پاک کردن تعدادی page از حافظه.
تابع erase_page دارای 2 آرگومان ورودی است که ورودی اول مقدار page شروع شونده را میگیرد و ورودی دوم تعداد page هایی که قرار است پاک شود. چون هر page در Atmega8 برابر 64 بایت است، در نتیجه با دستور erase_page(0,2) مقادیر 128 بایت اول حافظه پاک میشوند.
نکته: اندازه هر page دقیقا برابر SPM_PAGESIZE است.
تابع program_page سه آرگومان ورودی دارد. اولی آرایهای است که مقادیر آن خوانده شده و به فلش منتقل میشود. دومی تعداد بایتهایی است که باید منتقل شوند و این مقدار نباید از 64 (SPM_PAGESIZE) بیشتر شود. سومی آدرس شروع فلش است که مقادیر آرایه به آنجا انتقال مییابد.
توضیح برنامه main
با توجه به توضیحات فوق، برای پروگرام کردن برنامه قرار گرفته در آرایه prog. ابتدا باید 128 باید اول حافظه را پاک کنیم.
1 2 |
erase_page(0, 2); |
دستور فوق از page شماره 0 به اندازه 2 تا page حافظه را پاک میکند. یعنی 128 بایت اول حافظه. سپس باید مقادیر آرایه پروگرام شوند:
1 2 3 |
program_page(prog, 64, 0); program_page(prog + 64, 42, 64); |
با صدا زدن تابع program_page در خط اول، مقادیر از اول آرایه prog به اندازه 64 بایت در آدرس 0 حافظه فلش پروگرام میشود.
با صدا زدن تابع program_page در خط دوم، مقادیر از بایت 64 آرایه به اندازه 42 بایت در آدرس 64 حافظه فلش پروگرام میشود. این یعنی در دو مرحله تمامی طول برنامه که 106 بایت است توسط 2 دستور فوق بر روی حافظه پروگرام خواهند شد.
1 2 |
boot_rww_enable(); |
پس از اتمام پروگرام کردن، با صدا زدن تابع فوق حافظه فلش دوباره فعال شده و برنامه قابلیت اجرا دارد. در ادامه توسط یک حلقه for یک LED متصل به پین B0 در فواصل 1 ثانیه به تعداد 3 بار خاموش و روشن میشود. و در نهایت دستورات اسمبلی زیر اجرا خواهد شد.
1 2 3 4 |
asm("ldi r30, 0x00"); asm("ldi r31, 0x00"); asm("ijmp"); |
برای پرش به هر مکان دلخواه از حافظه از دستور ijpm استفاده کردیم که این دستور به مکانی از حافظه پرش میکند که رجیستر Z (همان R30 و R31) به آن اشاره کرده باشد و از آنجا که در دو دستور اول رجسترهای R30 و R31 با مقدار 0x00 پر شدهاند، پس از اجرای دستور ijpm پردازشگر یا CPU به آدرس 0 پرش خواهد کرد و برنامه نوشته شده که یک چشمک زن است اجرا خواهد شد. رجیسترهای R30 و R31 جزو 32 رجیستر مخصوص همگانی (general purpose registers) هستند که به بخش ALU میکروکنترلر متصل بود و عملیات ریاضی در این رجیسترها ذخیره میشود.
سخن آخر
در این بخش از آموزش سعی بر این بود که نحوه کار با بوت لودر avr در Atmel Studio بیان شود و چگونگی کارکرد توابع و همچنین مکانیزم پروگرام کردن و قسمت بندی حافظه فلش بیان شود. در قسمت بعدی هر دو مثال فوق را در نرم افزار Code Vision شرح و همچنین روش پاک کردن حافظه و پروگرام کردن میکرو و کار با بوت لودر را توضیح خواهیم داد. هرگونه سوال یا ابهامی وجود داشت، کامنت کنید.
3 پاسخ
سلام.
دستورات اسمبلی جهش توسط atmel studio خطا گرفته میشه. لطفاً اصلاح کنید تا ما هم استفاده کنیم .
ممنون.
سلام
ممنون از آموزشهای خوبتون
لطفا اگه ممکنه برای آموزشهای چند قسمتی یک دسته یا گروه درست کنید
آدم قسمت سه آموزش رو میبینه بعد هرچی سایت رو زیر ورو میکنی قسمت یکش رو پیدا نمیکنی
یه مقدار نظم آموزشهای چند قسمت کمه و بنظر شلخته میاد
سلام از نظر شما ممنونیم انشالله طی آپدیت های آینده این موارد حل خواهد شد