یکی از دلایل عدم کارکرد صحیح برنامه ها و یا عملکرد دور از انتظار یک دستگاه، برنامه نویسی غیر اصولی می باشد. در این مقاله تصمیم داریم چند مورد از اشتباهات رایج در زبان برنامه نویسی BASIC و کامپایلر BASCOM را بررسی نماییم.


۱- استفاده از Goto ممنوع

یکی از روش های ساده و غیر اصولی در برنامه نویسی استفاده از دستور Goto و پرش به Label های مختلف است. این روش بدلیل اینکه در آموزش طراحی الگوریتم برنامه ها مورد استفاده قرار میگیرد به اشتباه در برنامه های اولیه برنامه نویسان نیز استفاده می شود. مثلا در نوشتن الگوریتم برای دریافت عدد از کاربر و نمایش آن معمولا نوشته می شود:
۱- شروع
۲- دریافت عدد
۳- چاپ عدد
۴- برو به ۲
۵- پایان

این الگوریتم ممکن است در اولین پیاده سازی به همان صورتی که هست توسط برنامه نویس مبتدی نوشته شود و به دلیل جالب بودن و سادگی استفاده از دستور Goto (برو به) این دستور در سایر برنامه ها نیز توسط برنامه نویس بکار رود و نتیجه آن در برنامه های بزرگتر منجر به ایجاد کدی پیچیده گردد که به کد اسپاگتی (ماکارونی) مشهور است و چنین کدی موقع بررسی کاملاً ناخوانا بوده و اصلاح آن توسط فرد دیگر بسیار مشکل است.

علاوه بر Goto گاهی اوقات از دستورات اسمبلی JMP و RJMP هم استفاده می شود که کمی از Goto خطرناک تر می باشد؛ چرا که این دستورات تا مقدار خاصی از حافظه قدرت پرش داشته و اگر فاصله برچسب از این دستورات از حد خاصی بیشتر باشد منجر به خطاهای غیر عادی خواهد شد. در کل توصیه ما اینست که تا حد امکان از دستورات پرش در برنامه خود استفاده نکنید و بجای نوشتن کدهای پیوسته برنامه را به قطعاتی معنا دار به نام زیر برنامه، برچسب و تابع تبدیل کنید و آن ها را در مواقع خاص فراخوانی نمایید.

۲- برچسب (Label) و دستور فراخوانی Gosub:

یکی از روش های ساده برای نوشتن برنامه های ساختیافته یا ماژولار استفاده از برچسب برای مجموعه کدهای خاص و فراخوانی آن در بخش های مختلف برنامه است. این کار در درجه اول خوانایی برنامه را بیشتر کرده و در درجه دوم به دلیل استفاده اشتراکیِ سایر بخش های برنامه از یک کد واحد از طول برنامه می کاهد و برنامه را کوچکتر و سبک تر می کند. برای نوشتن یک برچسب در BASCOM بایستی از ساختار زیر استفاده کنید:


Label:
your code
Return

مثال:

Write_msg:
 Cls
 Home
 Lcd "salam"
Return

در این ساختار Label یک نام دلخواه است که بایستی با یک حرف شروع شده و حداکثر طول آن 32 کاراکتر باشد. تا حد امکان نام برچسب را با آندرلاین _ شروع نکنید. همچنین نام برچسب نباید با عدد شروع شود و استفاده از کاراکترهای خاص مثل + و - و * و / و . و ... هم ممنوع است. ضمناً از نام های رزرو شده کامپایلر مثل Print و ... که معمولا به محض نوشتن به رنگ آبی پررنگ تبدیل می شود استفاده نکنید. نام هایی مثل Ali، my_prog1، s4 و غیره نام های مناسبی است.

your code کد مورد نظر شماست که قرار است کار خاصی را انجام دهد.

Return نیز به معنای بازگشت بوده و سبب بازگشت خودکار برنامه به جایی می شود که از آنجا پرش کرده است.

نکته مهم در خصوص لیبل آنست که این برچسب ها بایستی پس از دستور End برنامه نوشته شده و حتماً با دستور Gosub فراخوانی گردند. لیبل ها برای اجرای کدهایی استفاده می شوند که کار خاصی را انجام داده و نه متغیر به عنوان ورودی می پذیرند و نه خروجی خاصی را باز میگردانند. هرگز برچسب های منتهی به Return را با دستورات Goto و Call فراخوانی نکنید.

۳- سابروتین (Sub) و دستور فراخوانی Call:

یکی دیگر از روش های ایجاد زیر برنامه استفاده از سابروتین می باشد. سابروتین ها خاصیت ویژه های برای پذیرش متغیر داشته و معمولاً در زیربرنامه هایی که بایستی یک عدد یا متن را دریافت کرده و با استفاده از آن کار خاصی را انجام دهند مورد استفاده قرار می گیرد. برای نوشتن سابروتین بایستی قبل از فراخوانی آن و معمولاً در ابتدای برنامه دقیقاً پس از Regfile$ آن ها را معرفی نمود. معرفی Subroutine با استفاده از دستور Declare صورت می گیرد.

مثال:

$regfile = "m8def.dat"
$crystal = 1000000
$hwstack = 32
$swstack = 32
$framesize = 32

Declare sub my_prog(byval x as byte, byval y as byte)


call my_prog(2,3)
end

sub my_prog(byval x as byte, byval y as byte)
 local z as byte
 z = x + y
 cls
 home
 lcd "x+y=" ; z
end sub

در نمونه کد بالا یک سابروتین به نام my_prog تعریف کرده ایم که دو عدد را به عنوان ورودی گرفته و مجموع آنها را در خروجی چاپ می کند. توجه داشته باشید که سابروتین بر خلاف تابع چیزی را بر نمی گرداند و عملیات چاپ بایستی در داخل سابروتین انجام پذیرد.

همانطوریکه در کد بالا مشاهده می فرمایید در ابتدا نوع میکرو و فرکانس کاری مشخص شده است. سپس با دستورات بعدی اندازه پشته سخت افزاری، پشته نرم افزاری و فریم را تعیین کرده ایم. این موارد بسار مهم بوده و توضیح آنها در مقاله "AVR نویز پذیر نیست" شرح داده شده است. در خط بعدی با استفاده از دستور Declare یک سابروتین به نام my_prog تعریف کرده ایم که در جلوی آن یک جفت پرانتز باز و بسته وجود داشته و دو متغیر x و y با پیشوند byval به جای Dim تعریف شده اند. تعریف متغیر در داخل پرانتزهای سابروتین کاملاً اختیاری بوده و می توانید سابروتینهایی بدون متغیر و بدون ورودی بنویسید که در این صورت بایستی یک جفت پرانتز خالی در جلوی سابروتین قرار داده شود، هرچند در این صورت بهتر است از لیبل بجای سابروتین استفاده گردد چرا که ویژگی اصلی سابروتین در این است که می تواند ورودی گرفته و آن را به زیر برنامه انتقال دهد. برای تعریف متغیرهای ورودی سابروتین از پیشوندهای Byval و Byref بجای Dim استفاده می شود. Byval به معنای "مقدار" بوده و هنگامی که از این پیشوند استفاده می کنیم هر ورودی که به سابروتین داده می شود یک کپی از آن تهیه شده (در فضای Frame) و کپی متغیر به زیر برنامه ارسال می شود. در این صورت اگر به اشتباه یا به دلخواه متغیر مورد نظر در زیر برنامه تغییر کند، متغیر اصلی دست نخورده باقی می ماند. با استفاده از این روش براحتی می توانید اعداد ثابت را نیز به سابروتین ارسال کنید. توجه داشته باشید که این روش سربار زیادی برای برنامه داشته و بایستی اندازه Frame را به اندازه حداکثر مقدار ورودی های سابروتین های همزمان فعال برنامه (برحسب بایت) قرار دهید. در ضمن این روش ممکن است باعث کندی اجرای برنامه ها گردد.

روش دیگر استفاده از پیشوند Byref می باشد. Byref به معنای رفرنس یا ارجاع بوده و آدرس متغیر نوشته شده در دستور فراخوانی به زیر برنامه ارسال می گردد. در موقع استفاده از این روش نمی توانید سابروتین را با اعداد ثابت فراخوانی کنید و نوشتن دستوری مثل call my_prog(1,2) منجر به خطا خواهد شد. روش فوق برای مواقعی استفاده می شود که سرعت اجرا مهم بوده، بهم ریختن احتمالی متغیر ورودی چندان اهمیتی نداشته و سایز متغیر های ورودی بزرگ و تعداد آنها زیاد می باشد و نیز فضای SRAM کافی برای Frame در اختیار نیست. در هر صورت معمولاً از روش اول یعنی Byref استفاده می شود تا بتوان اعداد ثابت را نیز به سابروتین انتقال داد.

برای فراخوانی سابروتین ها حتماً بایستی از دستور Call استفاده گردد. متاسفانه در بسیاری از کدهای نوشته شده با بسکام مشاهده میکنیم که سابروتین ها با دستور Gosub فراخوانی شده اند! این روش کاملاً اشتباه بوده و ممکن است منجر به خطاهای ظریف منطقی گردد.

در نمونه کد بالا پس از تعریف و فراخوانی سابروتین، دقیقاً پس از دستور end کد اصلی سابروتین که به آن بدنه سابروتین نیز گفته می شود نوشته شده است. ترتیب نوشتن سابروتین ها مهم نبوده و می توان آنها را به هر ترتیب دلخواهی پس از دستور End نوشت. بدنه یک سابروتین با دستور sub شروع شده و در جلوی آن نام سابروتین به همراه تعریف متغیرها قرار می گیرد. می توانید تعریف سابروتین را از ابتدای برنامه کپی کرده و Declare را با Sub عوض کنید و آن را در این قسمت Paste نمایید. هر سابروتین با دستور end sub پایان می یابد. یکی از ویژگیهای خاص سابروتین ها امکان تعریف متغیر در داخل آنها می باشد. ما در این سابروتین متغیری به نام z از نوع Byte تعریف کرده و مجموع متغیرهای x و y را در آن ذخیره نموده ایم و در نهایت آن را نمایش داده ایم. برای تعریف متغیر در داخل سابروتین بایستی از پیشوند Local بجای Dim استفاده گردد. Local به معنای محلی بوده و متغیرهای اینچنینی فقط موقع فراخوانی سابروتین ایجاد می گردند و با رسیدن به دستور End Sub از حافظه پاک می شوند. توجه داشته باشید که تعریف آرایه و بیت به عنوان متغیر لوکال یا محلی ممنوع است. در کل با استفاده از این قابلیت می توان مدیریت بهتری بر روی SRAM داشت و با میکروهای کم حافظه و کوچک کارهای بزرگی انجام داد.

۴- تابع (Function) و فراخوانی بدون نیاز به دستور

آخرین روش برای نوشتن زیر برنامه های خاص محاسباتی که اعداد یا حروفی را به عنوان ورودی گرفته و یک مقدار به عنوان خروجی بر می گردانند استفاده از تابع است. توابع یا فانکشن ها تقریباً شبیه به سابروتین ها می باشند اما قادرند که یک مقدار را به عنوان خروجی بازگردانند.

مثال:

$regfile = "m8def.dat"
$crystal= 1000000
$hwstack=32
$swstack=32
$framesize=32

Declare Function Addnum(byval X As Byte , Byval Y As Byte) As Byte

Dim I As Byte

Cls
Home
= Addnum(, 2)
Lcd I

end

Function Addnum(byval X As Byte , Byval Y As Byte) As Byte
 local z as byte
 z = x + y
 Addnum = Z
End Function

نمونه کد بالا نحوه تعریف یک تابع به نام Addnum را نمایش می دهد که دو عدد ورودی را با یکدیگر جمع کرده و نتیجه را در متغیر Addnum که همان نام تابع می باشد ذخیره می کند. نحوه تعریف تابع تقریباً مشابه Sub است با این تفاوت که به جای Sub از Function استفاده شده است. همچنین پس از پرانتزهای جلوی تابع مجدداً عبارت as byte نوشته شده است. این عبارت آخر نوع خروجی تابع را مشخص می کند و نام تابع به عنوان متغیر خروجی شناخته می شود.

برای فراخوانی تابع به هیچ پیشوندی نیاز نیست. در نمونه کد بالا می بینیم که نام تابع به همراه ورودی های آن که اعداد 1 و 2 است به طور مستقیم فراخوانی شده و نتیجه آن در متغیر I قرار داده شده و سپس متغیر I در جلوی LCD قرار داده شده است. در این برنامه موقع اجرا، نام تابع با عدد 3 جایگزین شده و در نمایشگر کریستال مایع نشان داده می شود.

بدنه تابع نیز تقریباً شبیه به سابروتین می باشد با این تفاوت که با کلمه کلیدی function و پس از آن نام تابع و متغیرها و نوع تابع شروع شده و با End Function پایان می یابد. در تابع نیز می توان از متغیر های Local یا محلی با همان شرایط سابروتین استفاده کرد. نکته مهم در توابع اینست که نتیجه نهایی بایستی در نام تابع ذخیره شده و بدین صورت به خروجی بازگردد و بهتر است که این دستور در انتهای تابع و قبل از اتمام بدنه تابع نوشته شود.

به عنوان یک نکته مهم در خصوص سابروتین ها و توابع سعی کنید که از فراخوانی تو در تو پرهیز کنید. این کار موجب پر شدن سریع پشته نرم افزاری شده و برنامه به سرعت هنگ می کند. هر تابع یا سابروتین باید پس از فراخوانی به طور کامل اجرا شده و به انتها برسد و سپس تابع یا سابروتین بعدی اجرا گردد. همچنین توجه داشته باشید که اگر تابع یا سابروتینی را فراخوانی کردید و در داخل آن تابع دیگری را صدا زدید پس از اجرای تابع دوم مجدداً دستور به جایی که از آن قسمت فراخوانی شده بود (در داخل تابع اول) باز میگردد و سپس به جایی که تابع اول فراخوانی شده بود برمی گردد. یعنی در واقع با هر فراخوانی، آدرس تابع در فضایی به نام Stack که همان پشته نرم افزاری نام دارد قرار داده شده (مثل چیدن بشقاب ها روی یکدیگر) و پس از پایان یافتن تابع، آدرس آن از پشته حذف می شود، (مثل برداشتن بشقاب ها از روی یکدیگر).

یک برنامه ساختیافته باید از الگوی زیر پیروی کند:

$refile
$...
$...

Declare sub s1(byval i as byte, byval j as byte)
Declare Function f1(byval i as byte, byval j as byte) as byte


Do
حلقه اصلی برنامه
call s1()
gosub test
x = f1()
..
.

Loop
End

sub s1(byval i as byte, byval j as byte)
...
end sub

function f1(byval i as byte, byval j as byte) as byte

...
f1 = ...
end function

test:
...
return

در الگوی بالا قبل از دستور End یک حلقه نامحدود نوشته شده است که برنامه کنترلی داخل آن نوشته می شود. برنامه کنترلی عموماً یک برنامه کوچک بوده و وظیفه آن صدا زدن توابع، سابروتین ها و برچسب ها می باشد و عموماً با استفاده از دستورات شرطی کارهای خاصی را انجام می دهد. اشکال زدایی برنامه کوچی مانند آن بسیار راحت تر از برنامه های پشت سر هم بوده و سرعت بررسی و تغییر برنامه به مراتب بیشتر می شود. مثلاً در یک برنامه ساعت در داخل حلقه وضعیت فشردن کلید های تنظیم بررسی می شود و سابروتین افزایش ثانیه، هر ثانیه یکبار توسط اینتراپت تایمر فراخوانی می شود. در این صورت برنامه به طور دائم در داخل حلقه Do..Loop گردش داشته و "زنده" است. اگر Do..Loop نوشته نشود برنامه به محض اجرا به End رشیده و متوقف می شود و در این صورت نه اینتراپت تایمر جواب می دهد و نه می توان کار دیگری را انجام داد. پس همیشه قبل از دستور End بایستی یک حلقه نامحدود نوشته شود تا CPU را در وضعیت فعال نگه دارد. برنامه کنترلی نیز در داخل این حلقه قرار داده می شود و بایستی بسیار واضح بوده و دارای کامنت و توضیحات کامل باشد.

با نوشتن برنامه ها بصورت ماژولار براحتی می توانید برنامه خود را عیب یابی کرده و کد خوانایی طراحی کنید تا هر فردی بتواند آن را بررسی و ویرایش نماید. همچنین موقع نوشتن برنامه های جدید براحتی می توانید از ماژول های از پیش نوشته شده در برنامه های خود استفاده کنید و سرعت نوشتن برنامه های جدید را دو چندان نمایید. توجه داشته باشید که برنامه نویسی ساختیافته یا پیمانه ای یکی از روش های جدید برنامه نویسی می باشد که بسیار بهتر از روش های کلاسیک و پشت سر هم است. روش دیگری نیز در برنامه نویسی وجود داشته که به برنامه نویسی شی گرا یا OOP مشهور است. در این نوع برنامه نویسی قسمت های مختلف به اشیاء تبدیل شده و برای هر شی یک کلاس نوشته می شود. متاسفانه در کامپایلری مثل BASCOM این قابلیت پشتیبانی نشده و فقط می توان از برنامه نویسی ساختیافته استفاده نمود.




© 2009-2016 AVR64.com