برنامه نویسی حرفه ای میکروکنترلر تلاشی است برای بیان نکات خاص و سودمند جهت نوشتن برنامه ای بهینه تر مخصوصا برای پلتفرم هایی که به لحاظ حافظه با محدودیت های جدی روبرو هستند نظیر انواع میکروکنترلر ! شاید فکر کنید صرفه جویی چند بایت یا چند سیکل حافظه چه مقدار میتواند ارزش داشته باشد ، در صورت بروز محدودیت با تعویض میکروکنترلر ؛ به سادگی با آن مقابله خواهیم کرد ، البته این هم راهکاری صحیح است ولی اگر شما طراحی را برای یک تولید انبوه انجام داده باشید ، یک ریال هم یک ریال است ! چرا که یک ریال به تعداد دستگاه های تولید شده تکرار می شود. پس توصیه میکنیم حتما این دست مقالات ( برنامه نویسی حرفه ای میکروکنترلر ) را دنبال کنید و البته نظر و پیشنهادات خود را در خصوص بهتر شدن آن حتما مطرح نمایید. برای مطاله مطالب پیشین میتوانید ” برنامهنویسی حرفه ای” را جستجو کنید. در ادامه با سیسوگ همراه باشید.
عملیات بیتی موضوع برنامه نویسی حرفه ای میکروکنترلر
انجام عملیات بیتی بر روی متغییر ها و رجیستر ها یکی از پرکاربرد ترین نوع عملیاتی است که در برنامه نویسی با میکروکنترلر مورد استفاده قرار می گیرد. اما واقعا با استفاده از چه روش هایی میتوان این عملیات را انجام داد. این عملیات چنان مهم و کاربردی است که در برخی پردازنده ها حتی دستور اسمبلی برای آن وجود دارد و در برخی پردازنده های ARM حتی سخت افزاری برای ان تعبیه شده است. قبلا در قسمت دوم مقاله برنامه نویسی حرفه ای از این قابلیت گفتیم (Bit-Banding) و نحوه استفاده از آن را بررسی کردیم. اما اگر بخواهیم فارغ از سخت افزار به آن نگاه کنیم همچنان از اهمیت بسیار بالایی برخوردار است. در آموزش برنامه نویسی حرفه ای میکروکنترلر از AVR استفاده خواهیم کرد و البته تمام کد های پیاده شده در تمام دیگر پردازنده ها به سادگی قابل استفاده خواهند بود !
دقیقا منظور از عملیات بیتی چیست ؟
همانطور که میدانید ؛ هر بیت حاوی داده یک یا صفر است ، و جز این مقداری دیگری نمیتواند بپذیرد (البته به جز در کامپیوتر های کوانتومی) ، منظور از عملیات بیتی دقیقا مقدار دهی آنها و خواندن مقدار آنها خواهد بود ، البته عملیات های And و Or و Xor و Not و .. هم عملیات بیتی است ، اما منظور ما در این مقاله مقدار دهی و خواندن مقدار یک بیت است. چرا که انجام عملیات منطقی به سادگی بر روی بایت ها قابل اجراست و مشکلی از بابت وجود ندارد.
اما چرا داریم به مقوله مقدار دهی بیت ها می پردازیم ؛ همانطور که میدانید حافظه به صورت بایت ، بایت در دسترس قرار داد و برای دسترسی به مقدار یک بیت از یک بایت باید تعدادی عملیات منطقی انجام داد ، اما این که این کار را به چه صورت انجام دهیم تاثیر بسزایی در سرعت اجرای برنامه خواهد داشت.
کدویژن چکار می کند ؟
قبلا طی مقاله ای تحت عنوان “چرا کدویژن نه” به نقد این نرم افزار پرداختیم و با تکیه بر شواهد و دلایل منطقی توصیح دادیم که چرا نباید از این نرم افزار استفاده کرد. اما از آنجایی که هر نرم افزار و سرویسی طرف دار های دو آتیشه ای هم دارد ، مکررا با این استدلال مواجه شدم که کدام نرم افزاری قابلیت دسترسی به بیت های یک پورت را دارد. البته این استدلال صحیح است ، نرم افزار کدویژن برای دسترسی به بیت های یک پورت امکانات ویژه ای دارد. به عنوان مثال برای مقدار دهی بیت دوم از پورت B کافی است خط زیر را در برنامه وارد کنید.
1 | PORTB.2 = 1; |
برای خواندن کافی است خط زیر را وارد کنید
1 | bit = PORTB.2; |
البته خود این نحوه نوشتار به خوبی نشان میدهد که تا چه اندازه کدویژن از یک کامپایلر مطلوب فاصله دارد. حتی اگر PORTB را یک Struct یا union بدانیم باز هیچ اسم متغییری نمی تواند با اعداد شروع شود!
و همیشه به این موضوع می بالند که بله کدویژن چنین کار را ساده کرده ! اگر مقاله را تا انتها دنبال کنید ، در خواهید یافت که GCC و روش اصولی برنامه نویسی هیچ تفاوتی با این نوع برنامه نویسی بی قائده نخواهد داشت.
در GCC چطور این کار را خواهیم کرد ؟
این روش دقیقا مثل روشی است که در کدویژن مورد استفاده قرار می گیرد اما به سبک استاندارد شده. برای این کار به سادگی یک Struct با 8 عضو می سازیم که هر عضو یک بیت طول داشته باشد. اما چطور چنین کاری ممکن است ، یه تعریف زیر دقت کنید
1 2 3 | struct { type [member_name] : width ; }; |
همانطور که تعریف Struct مشاهده می کنید به سادگی میتوان طول هر عضو را با قرار دادن دو نقطه ” : ” مشخص کنید. مطمئنا اکثر افراد از چنین مساله ای بی اطلاع هستند ، تنها به این دلیل که تسلط کافی به زبان و شکل نوشتاری زبان ندارد. برای همین مساله است که همیشه تاکید میکنم حتما به اندازه کافی به زبان مورد استفاده تسلط پیدا بکنید. اما متاسفانه همیشه با این تفکر پیش میرویم که برنامه نویسی چیز بیشتری برای آموختن ندارد !
برای اطلاع بیشتر در خصوص تعریف بیتی ساختار می توانید به این آموزش مراجعه کنید. اما با توجه به نحوه تعریف ساختار بیتی ، ساختار مورد نظر را به شکل زیر تعریف میکنیم:
1 2 3 4 5 6 7 8 9 10 11 | typedef struct { unsigned bit0:1; unsigned bit1:1; unsigned bit2:1; unsigned bit3:1; unsigned bit4:1; unsigned bit5:1; unsigned bit6:1; unsigned bit7:1; } PORT_REG; |
هر عضو از Struct بالا یک بیت طول دارد که تعریف 8 عضو یعنی 8 بیت و طول کل هم یک بایت خواهد بود ، اگر رجیستر ها یا پورت های میکروکنترلر 16 یا 32 بیتی است می توان به همان تعداد عضو تعریف کرد. برای استفاده تنها کافی است که آدرس (اشاره گر) پورت را رجیستر مورد بحث را به این ساختار ارجاع دهیم :
1 | PORT_REG *PortB = (PORT_REG*)&PORTB; |
در حال حاظر متغیری به اسم PortB داریم که به بیت های آن دسترسی مستقیم خواهیم داشت ، برای مقدار دهی می توان به صورت زیر عمل کنیم
1 2 3 | PortB->bit2 = 1; PortB->bit2 = 0; PortB->bit2 = ~PortB->bit0; |
دقیقا تمام عملیاتی که بر روی یک متغیر امکان پذیر است بر روی این بیت ها هم امکان پذیر است. چرا که اعضای یک Struct هستند.
مجموعه مقالات برنامه نویسی حرفه ای میکروکنترلر را در سیسوگ دنبال کنید.
سلام
عالی بود این اموزش
ممنون از شما 🙂
با این روش میشه چنتا پین از پورت های مختلف رو ترکیب کرد و یه پورت جدید ساخت؟
تو یه پروژه ممکنه چند پایه از پورت های مختلف بمونه و بخوایم از اونا برای کار دیگه استفاده کنیم و کار یکم سخت میشه
چطور میشه این پین های باقی مونده رو به یه پورت جدید تبدیل کرد و مثل بقیه پورت ها باهاش رفتار کرد،
مثلا بنویسیم :
NewPort = 0xff;
سلام خواهش میکنم 🙂
ولی فکر کنم برای تعریف پورت جدید لازمه که آدرس های حافظه پشت هم قرار گرفته باشند که معمولا این گونه نیست
سلام
من قبلا تو کدویژن اگه بخواستم اسم یه پایه رو تغییر بدم خیلی ساده انجام می شد مثل
define Led=PORTD.0#
اما الان که میخوام با AtmelStudio برنامه بنویسم به مشکل بر خوردم!!!
از چه روشی باید استفاده کنم که تو برنامه کدها خوانا تر باشه.
ممنون
سلام دوست عزیز ؛ خوب برای این کار توی کامپایلر gcc میتونید به دو روش عمل کنید ؛ روش اول استفاده از دستور اسمبلی sbi و cbi است ؛ برای راحت شدن استفاده از به شکل ماکرو باز نویسیش کردم
#define SBI(port,bit) asm("sbi %0, %1" : : "I" (_SFR_IO_ADDR(port)), "I" (bit))
#define CBI(port,bit) asm("cbi %0, %1" : : "I" (_SFR_IO_ADDR(port)), "I" (bit))
یا این که به شکل عملیات بیتی باز نویسیش کنید
#define SBI(port,bit) port |= (1 << bit)
#define CBI(port,bit) port &= ~(1 << bit)
برای استفاده هم کافیه که به شکل زیر استفاده کنید
SBI(PORTA,1);
سلام
ممنون
ولی این جواب من نبود!!!
مثلا من اگه بخوام led رو روشن کنم مینوشتم
led=1;
ولی با روش شما من تو دستورات باید اسم پین و پورت led ی که متصل شده رو وارد کنم.
اگه این طور باشه خب می نویسم PORTA.0=1
با تشکر
سلام
خواهش میکنم
من الان توجه دغدغه شما نشدم ؛ الان سه روش مجزا برای خاموش و روشن کردن یک پین گفتم ؛ روش اول که توی پست هست استفاده از استارکپر (همونطور که قبلا توی کدویژن استفاده می کردید.) روش اسمبلی و روش عملیات منطقی 😐
اگر دغدغه شما اینه که برای روشن کردن یه led بنویسید led=1 خوب از روشی که توی همین پست توضیخ داده شده استفاده کنید. و led رو دیفاین کنید همونطوری که توی کدویژن دیفاین می کردید.
ممنون بابت زحماتنون
سلام.باز هم ممنونم
میشه لطفا در مورد اشاره گر ها و union و structer توضیح بدین
من از چنتا منبع مختلف خوندم اما کاملا متوجه نشدم
اگر امکان داره شما همراه با چنتا مثال عملی و واقعی توضیح بدین
همچنین همراه با فوایدشون
بسیار ممنونم
سلام دوست عزیز
ببینید اینا مواردی هست که توضیحشون توی کامنت مقداری سخت هست ؛ احتمالا یک سری آموزش در خصوص برنامه نویسی منتشر خواهیم کرد و به این موارد خواهیم پرداخت
اما میتونید به این لینک مراجعه کنید ، توضیحات خوبی با مثال گذاشته