Skip to content

Project Report

Moein Arabi edited this page Feb 10, 2024 · 7 revisions

گزارش پروژه درس ریزپردازنده

دانشگاه شهید بهشتی - دکتر عطارزاده

محمدمعین عربی – محمدعلی آقایانی

game

flappy bird

مقدمه

در این پروژه یک بازی با نام flappy-bird پیاده‌سازی شده است. این بازی با زبان اسمبلی x86 مبتنی بر سیستم‌عامل DOS و به صورت گرافیکی ساخته شده است. یک دسته کنترلر سخت‌افزاری هم به همراه آن برای نمایش امتیاز طراحی شده است. در این بازی کاربر یک پرنده در حال سقوط را کنترل می‌کند. کاربر می‌تواند با زدن یکی از دکمه‌های روی کیبوردش پرنده را به بالا ببرد. زمانی که پرنده از صفحه بازی خارج شود یا به یکی از موانع برخورد کند، بازی تمام می‌شود.

ساختار کلی کد

از زمان شروع بازی، کد در یک حلقه بی‌نهایت عملیات‌های زیر را انجام می‌دهد:

  1. تاخیر: یک تاخیر چند میلی‌ثانیه‌ای ایجاد می‌شود تا سرعت بازی برای کاربر مناسب‌سازی شود.
  2. ورودی کیبورد: اگر کاربر دکمه‌ای از کیبورد را بزند پرنده به سمت بالا پرواز می‌کند.
  3. جایگاه پرنده و مکان موانع بازی در هر چرخه تغییر می‌کند و به سمت پرنده می‌روند و موانع جدیدی تولید می‌شود.
  4. امتیاز کاربر در هر چرخه افزوده می‌شود و نمایش داده می‌شود.
  5. برخورد پرنده با موانع بررسی می‌شود که اگر برخورد کرده باشد، بازی تمام می‌شود.  

بررسی ورودی کیبورد

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

وقفه‌های مورد استفاده

keyboard interrupt

وقفه‌ی 0x16 قابلیتی برای چک کردن بافر کیبورد در اختیار قرار می‌دهد. اگر بافر پر باشد فلگ Zero را 0 می‌کند. سپس اگر بافر پر باشد به کمک این نوع وقفه می‌توان بافر را خواند و خالی کرد.

keyboard interrupt

صفحه گرافیکی در DOS

تعیین حالت صفحه به کمک وقفه‌های شماره 0x10 صورت می‌گیرد. داس مودهای گرافیکی زیادی دارد ولی در این پروژه ما از مود شماره 0x13 استفاده کرده‌ایم:

graphic mode

دلیل انتخاب رزولوشن 320 در 200، جا شدن تمام پیکسل‌های آن در یک سگمنت 64 کیلوبایتی از حافظه است و این باعث راحتی کار با آن می‌شود. طبق توضیحات دستورالعمل، سگمنت مورد نظر از آدرس 0xA000 شروع می‌شود. دسترسی به پیکسل‌ها و تغییر رنگ آن‌ها به این صورت محاسبه می‌شود:

$$ offset=320\times y+x $$

دلیل ضرب در 320 آن است که در هر ردیف 320 پیکسل وجود دارد و هر پیکسل یک مقدار متناظر 1 بایتی در حافظه دارد. به طور مثال اگر مقدار خانه مورد نظر حافظه برابر 0 باشد، پیکسل متناظر با آن بدون رنگ (خاموش) می‌شود یا اگر مقدار آن 4 باشد، رنگ پیکسل قرمز می‌شود.

نوشتن کاراکتر در صفحه گرافیکی

فونت‌هایی به صورت پیش‌فرض وجود دارند که یک کاراکتر را به پیکسل‌های صفحه نظیر می‌کند و آن را نشان می‌دهد. برای نمایش امتیاز کاربر در بالای صفحه، ابتدا باید امتیاز عددی را به کارکتر تبدیل کنیم و سپس به کمک وقفه شماره 0x10 نمایش دهیم. جزئیات وقفه مورد نظر در پایین قابل مشاهده است:

print character print character

با قرار دادن کاراکتر مورد نظر در رجیستر AL و صدا زدن وقفه، کارکتر در صفحه چاپ می‌شود. برای پاک کردن همین کارکترها از صفحه می‌توان کاراکتر back-space را نوشت تا کارکترها تک به تک پاک شوند.

فیزیک سقوط و پرش پرنده

در حرکت پرنده از چند قانون فیزیک استفاده کردیم. فرمول مکان-زمان:

$$ x_1=x_0+vt+0.5at^2 $$

فرمول سرعت-زمان:

$$ v_1=v_0+at $$

جهت مثبت را رو به پایین فرض شده و هر بار موقعیت جدید پرنده و سرعت آن را به کمک این فرمول محاسبه می‌شود. اگر کاربر از کیبورد برای بالا بردن پرنده استفاده کند، سرعت پرنده، یک عدد منفی می‌شود. منفی شدن سرعت به معنای تغییر جهت آن به سمت بالا است و بعد از مدتی به خاطر مثبت بودن شتاب، بردار سرعت پرنده دوباره رو به پایین قرار می‌گیرد.

اعداد تصادفی

برای نمایش موانع از اعداد تصادفی استفاده شده است. به این صورت که موانع جدید با طول، ارتفاع و جایگاه (بالا یا پایین صفحه) متفاوت به صورت تصادفی ایجاد می‌شوند. اعداد تصادفی به کمک وقفه 0x1A ایجاد می‌شوند. این وقفه ساعت سیستم را در رجیسترهایی ذخیره می‌کند و ما از آن برای تولید عدد تصادفی استفاده کرده‌ایم:

random number

کاهش چشمک زدن آبجکت‌ها

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

نحوه بررسی برخورد پرنده با مانع

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

سخت‌افزار کنترلر بازی

در این قسمت یک دسته کنترلر به کمک STM32 پیاده‌سازی شده است. این کنترلر از یک LCD برای نمایش امتیاز کاربر در بازی استفاده می‌کند. ارتباط بین محیط DOSbox و کنترلر به کمک ارتباط سریال UART انجام می‌شود.

آماده‌سازی پورت سریال در DOSbox

در فایل کانفیگ DOSbox باید مشخص شود که از کدام پورت COM موجود در سیستم جهت ارتباط باید استفاده کند. مانند زیر:

serial1=directserial realport:COM1

سپس به کمک وقفه‌های شماره 14 باید تنظیمات UART را انجام دهیم.

init uart

از رجیستر AL برای مشخص کردن baudrate، parity، تعداد stop بیت و طول دیتا استفاده می‌کنیم. چون از serial1 استفاده کردیم، رجیستر DX را برابر صفر قرار می‌دهیم.

ارسال و دریافت UART در DOSbox

از دستورات IN و OUT برای ارسال و دریافت دیتا استفاده می‌شود. آدرس پورت COM1 برابر 3F8 است و دیتا در این آدرس IO خوانده و نوشته می‌شود. در DOS وقفه‌هایی برابر ارسال و دریافت وجود دارد ولی متاسفانه به خوبی کار نمی‌کردند!

ارتباط DOSbox و پروتئوس

از یک نرم‌افزار مجازی‌ساز پورت COM مثل com0com برای ایجاد دو پورت مجازی در سیستم استفاده کردیم. به طور مثال پورت با نام COM1 به DOSbox متصل می‌شود و پورت دیگر با نام COM9 به پروتئوس. اتصال به پورت COM در پروتئوس به کمک COMPIM انجام می‌شود. تنظیمات لازم مثل baudrate باید روی COMPIM انجام شود. پین TXD باید به پین Transfer بورد یا ترمینال متصل شود و پین RXD به پین Receiver بورد یا ترمینال متصل شود.

compim

ارتباط STM32 با DOSbox

پس از آنکه از صحت کار کردن UART در DOSbox مطمئن شدیم، یک پروژه جدید با CubeMX ایجاد کردیم و تنظیمات مربوط به پورت سریال را انجام دادیم. از طریق همان COMPIM در پروتئوس، دو سیم RX و TX را متصل کردیم. به کمک کدهای موجود از آزمایشات قبلی یک LCD برای نمایش دیتای دریافتی استفاده کردیم. برای کاهش تاخیر ناشی از کار با IO از سمت DOSbox تنها امتیازات با مضرب 10 ارسال می‌شوند. امتیازات کاربر به صورت یک عدد 16 بیتی در دو بسته 8 بیتی از سمت DOSbox ارسال می‌شود. به ازای دریافت هر بسته یک بایتی، یک شماره بسته (مثلا 1 یا 2) به عنوان یک بسته ACK ارسال می‌شود تا 8 بیت دوم امتیاز کاربر ارسال شود. این کار ارسال یک بسته ACK به دلیل این است که از وقفه‌های خود DOS برای ارسال استفاده نشده است در نتیجه از بیت‌های وضعیت UART نمی‌توان استفاده کرد و مطمئن شد که یک بایت دیتا ارسال شده است یا نه. پس از دریافت یک بسته 2 بایتی، عدد تبدیل به رشته کاراکتر می‌شود و در LCD نمایش داده می‌شود.

lcd

دریافت بسته‌های UART در STM32 مبتنی بر وقفه پیاده‌سازی شده است تا هر موقع بسته جدیدی آمد، در interrupt handler اقدامات لازم صورت بگیرد. یک سوییچ هم به عنوان دکمه‌ی پرش پرنده استفاده شده است. این سوییچ مبتنی بر وقفه کار می‌کند و هر زمان که کاربر این دکمه را بزند، در زمانی که دریافت امتیاز انجام نمی‌شود، یک بایت دیتای مشخص ارسال می‌شود. این دیتا توسط بازی خوانده می‌شود و پرش پرنده را انجام می‌دهد.

switch

LCD

کارکرد LCD به این صورت است که 8 پین ورودی دیتا دارد و 3 پین کنترلی. پین RS مشخص می‌کند که در حال حاضر قرار است یک بایت دیتای کاراکتر به LCD منتقل شود یا یک بایت دیتای دستور (دستوراتی مثل پاک کردن صفحه نمایش، جابجایی cursor و ...). پین RW آن تعیین کننده خواندن یا نوشتن در LCD است که در این آزمایش ما فقط از حالت نوشتن آن استفاده کرده‌ایم. پین EN زمانی 1 می‌شود که دیتا روی 8 پین LCD پایدار شده و آماده دریافت توسط LCD است و پس از حدود یک میلی‌ثانیه دوباره صفر می‌شود.

منابع

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