هذه المقالة محمية ضمن الحقوق الفكرية لشركة In|Framez Technology Corp.، ومرخصة للعرض فقط ضمن الشبكة العربية لمطوري الألعاب مع الموافقة الصريحة من المؤلف وشركة In|Framez Technology Corp.. لا يسمح بإعادة نشر هذه المقالة أو تعديلها دون الرجوع للمؤلف. يمنع النسخ والاقتباس دون ذكر المصدر والموافقة من المؤلف.
مقدمة
ماذا تريد؟ إجراءات دخل/خرج عن طريق منافذ الحاسب الشخصي؟ مؤقتات دقيقة؟ تعدد مهام؟ كلها هنا! في هذا العدد من تطوير حلول::برمجة، نبين لك مزايا وقصور الأساليب المتعددة التي يستخدمها مهندسو الإلكترون في برمجة أجهزتهم. كل هذا المرح وأكثر في هذه الصفحة!
منذ سنة وأنا أتلقى أسئلة عديدة عن كيفية ربط الأجهزة الإليكترونية مع الحاسب الشخصي، وكيفية تزمين الدخل والخرج بدقة، وأسئلة أخرى مشابهة... دلني هذا الأمر على أهمية الموضوع، فقررت ألا أحرمكم من هذه المعلومات المهمة... إن جهاز الهاتف لدي يمكنه تسجيل المكالمات على أشرطة صغيرة تتسع لـ 30 دقيقة، يمكنك الإستماع إليها فيما بعد... وها هي علبة الأشرطة، بها حوالي 10 أشرطة تحتوي على أغلب المكالمات التي تتضمن معلومات فنية...
دعني أرى، صيانة Windows... كلا. قواعد بيانات... تؤ... وسائط متعددة وألعاب... همم.. هذا مفيد، ضعه على الطاولة... واحد آخر... برمجة مواقع... ليس مفيداً... آها! هاهو: برامج قيادة ومشغلات... حسناً هذا يكفي... تعال معي نستمع إلى هذا الشريط. ما هذا؟ أرى أن معك شريط ثالث! من أين أتيت به؟ سقط من العلبة؟ أرنيه لحظة... هممم... تعدد مَهام؟
هذا موضوع شيق... سنستمع إليه إن لم يدركنا الوقت... حسناً... هدوووء... (كليك... طنين الهاتف) :
☎ المكالمة الأولى: 2001/1/15– 05:00 عصراً :
أنا- آلو السلام عليكم.
؟؟- السلام عليكم، أنا أمجد.
أهلاً أمجد.. (سأختصر التحية والسلام نظراً لضيق الوقت).
أمجد- المهم... عندي سؤال سريع... ماهي أفضل لغة برمجة لكتابة برامج تحكم بأجهزة خارجية؟
أنت، ماذا تعرف؟
أنا أكملت دورة Visual...
Visual ماذا؟
Visual Basic... لماذا؟ أليست جيدة؟
Visual Basic؟ بل ممتازة! أفضل لغة... أحسنت الاختيار...
طيب، سلام...
☎ المكالمة الثانية: 2001/1/15– 05:30 عصراً :
أيوه؟
ألو مرحباً، وسام؟
أهلاً ياسر (...كلام فارغ...)، هاه، ماذا لديك؟
لا أبداً... سؤال على السريع... استلمت مؤخراً مشروع ربط مع الحاسب، فأية لغة برمجة أستخدم؟
أية لغة أنت تتقنها؟
Delphi وَ C++
Delphi ؟! ممتاز جداً... رائع... أحسن اختيار...
أحقاُ؟
طبعاً...
شكراً، سلام! (كليك)...
☎ المكالمة الثالثة: 2001/1/15– 05:31 عصراً :
ها؟
ألو وسام؟ نسيت أن أسألك شيئاً آخر...
تفضل؟
نسيت أن أخبرك أنني ماهر بالـ C++ أكثر من Delphi !
إذن استخدم C++!
ألم تقل لي أن Delphi أفضل؟
آسف، كنت شارداً!
حسناً، أريد أن أرسل عدة بايتات عبر مخرج الطابعة، كيف؟
الإجراء _outp() للخرج ويقابله الإجراء _inp() ... إبحث عنهما في الـ MSDN لمزيد من التفاصيل...
شكراً جزيلاً... سلام! (كليك)...
☎ المكالمة الرابعة: 2001/1/15– 07:30 مس��ءً (يبدو أنني أسكن في مقسم هاتف!):
ألو؟
أيوه؟...
السلام عليكم
(...)
نعم، سؤال محير...
تفضل...
عندما أدخُـل إلى إعدادات الـ BIOS في جهازي، أجد ما يدعى: Parallel-Port Mode... وأجد بجانبها ECP... ما معنى ذلك؟
هناك عدة انظمة لمنفذ الطابعة، تختلف عن بعضها في الأداء بشكل رئيسي... أقدمها SPP، وأحدثها ECP وَ EPP. تستطيع منافذ ECP نقل البيانات بسرعة تكافئ سرعة نقل الـ ISA !
يعني ECP أحسن؟
أكيد، كما أن منافذ EPP وَ ECP ثنائية الاتجاه (Bidirectional)؛ يعني in وَ out، ليس فقط out مثل SPP !
طيب، عفواً، كم سرعة نقل ISA ؟
بايت في الميكروثانية الواحدة.
مدهش!
يدعم ECP خاصية DMA أيضاً!
DMA ؟
Direct Memory Access ... هكذا تستطيع الوصول إلى محتويات الذاكرة دون الرجوع إلى المعالج أثناء نقل كتل كبيرة من البيانات...
ممتاز! شكراً للتوضيح... إلى اللقاء... (كليك)...
☎ المكالمة الخامسة: 2001/1/17 – 05:05 عصراً :
(...)
أمجد- أريد أن أسألك في موضوع، وأرجو أن تساعدني به...
إن شاء الله لن أقصر... تفضل...
كيف أستطيع معرفة عنوان المنفذ التسلسلي (Serial Port) في الحاسب؟
عندك Windows ؟
طبعاً... Windows ME !
جيد... اتبع الخطوات الآتية:
Control Panel > System Properties > Device Manager > Ports
حبة حبة! لقد أضعتني... أين Device Manager ؟
تجدها في أعلى صفحة System Properties ...
نعم صحيح... (يغمغم) Device Manager، Ports... أين أنتِ يا Ports... نعم! Ports!... ثم ماذا؟
اختر المنفذ الذي تريده، Communication Port 1 (COM1) في حالتك... ثم اضغط Properties !
نعم...
ستجد الكثير من الخيارات هنا... إذهب إلى صفحة Resources ...
أيوه... لحظة... نعم، إني أرى الآن العنوان والمقاطعة للمنفذ... شكراً!
طبعاً بنفس الطريقة يمكنك الحصول على عنوان أي منفذ في جهازك، حتى الـ USB !
حقاً؟ شكراً... سلام! (كليك)
☎ المكالمة السادسة: 2001/1/21 – 07:00 مساءً :
آلو ؟!
السلام... أنا ياسر...
أهلاً ياسر... كيف حال مشروعك؟
والله تمام، لقد بنيت برنامجاً بالـ C++ يتحكم بجهاز ربطته بمنفذ الطابعة، ولكن واجهتني مشكلة...
خير إن شاء الله؟
عندما أنفذ برنامجي في نظام Win2k فإنه مباشرة يظهر لي رسالة خطأ تخبرني أنني أقوم بعملية تحتاج إلى سماحيات لا أملكها... ما الحل؟
كلام منطقي. يتميز Win2k (وكل أنظمة WinNT) بأنه يمنع التطبيقات الاعتيادية من الوصول المباشر إلى منافذ وأجهزة الحاسب المختلفة، لذا فإن _outp() وَ _inp() سيولدان هذا الخطأ الذي رأيته، حيث لا يسمح لك بهذه العمليات...
ولم التعقيد؟
إن WinNT نظام آمن (أو هكذا يفترض)، وهذا الإجراء ضروري من أجل تأمين درجة من الثقة والثبات في العمل... طبعاً مثل هذه الأمور تزيد من صعوبة تعامل المبرمجين مع النظام، ولكنها تريح المستخدم النهائي كنتيجة نهائية...
امممم...
تخيل معي لو أنك صاحب شركة تعتمد على شبكة من الأجهزة التي تعمل بنظام WinNT، إنك طبعاً لا تريد لبرنامج ما أن يقوم بأداء عمليات في أجهزتك من شأنها أن تخل بثباتها في العمل...
وهل أكون قد تسببت بذلك إذا قمت بإخراج عدة بايتات بريئة عبر منفذ الطابعة؟
لا، ولكن ماذا لو وَجَّهْت هذه البايتات إلى مكان آخر بالخطأ؟ إلى "كرت الشاشة" مثلاً؟
طيب، والحل؟
هناك عدة حلول... تبدأ من ’غير سهلة‘، وتصل إلى ’معقدة‘...
تفضل...
بإمكانك فتح منفذ الطابعة (أو غيره) على أنه "ملف"، والتعامل معه على هذا المبدأ...
كيف؟
الإجراء CreateFile()، بدلاً من أن تمرر له اسم ملف، مرر له "LPT1:" ...
وماذا بعد؟
استخدم ReadFile() بدلاً من _inp()، وَ WriteFile() بدلاً من _outp() ... لاحظ أن هذه إجراءات عالية المستوى، وقد تفقدك بعض التحكم الذي تحصل عليه مع _inp() وَ _outp() ...
كلام جميل... هل هناك طرق أخرى؟
هناك طريقة معقدة، ولكنها لا تفقدك أية مرونة أو تحكم...
وهي؟
أكتب برنامج قيادة (تعريف) (Device Driver) لمنفذ الطابعة...
ماذا؟!
هدئ أعصابك... إن برامج القيادة هي الوحيدة القادرة على الوصول المباشر إلى أجهزة الحاسب في نظام WinNT... المشكلة أن كتابة برنامج قيادة هي عملية غير سهلة أبداً! فستضطر لخوض الكثير من الخبرات قبل أن تنجح في بناء برنامج قيادة لا يفعل شيئاً!
وتريدني أن أفعل كل ذلك؟ سأكون قد شِـبْت ريثما أُنهي كتابته!
لا تخشى شيئاً، فقد قام غيرك بهذه العملية، وبإمكانك الحصول على نتائج عملهم مقابل "القليل" من الدولارات.
لا شكراً... أنا لا أتعامل بالعملة الصعبة... سأجرب الحل الأول، إن لم ينجح، سأمنع برنامجي من العمل تحت الأنظمة المعتمدة بنية WinNT... سلام!
سلام (كليك)
كفى هذا الشريط، فلننتقل إلى الشريط الآخر... وسائط متعددة وألعاب... هه؟ تسألني ما علاقة الألعاب بهندسة الإلكترون؟ تحتاج الألعاب وبرامج الوسائط المتعددة (مثل مشغلات الأفلام Video Players) إلى التزمين بشكل دقيق (مثلاً الصوت مع الصورة)... وهي تَسْتَخدِم لذلك مؤقتات تصل دقتها إلى 100 نانوثانية (أو 0.1 ميللي ثانية)، لذا فقد تم وضع هذه الخدمات مع بقية الخدمات الخاصة بهذا النوع من التطبيقات. المهم دعنا من هذا الهراء ولنستمع قليلاً إلى الشريط قبل أن يراودنا الوقت: (كليك)
☎ المكالمة الأولى: 17/5/2001 – 11:05 صباحاً :
السلام عليكم... وسام؟
أيوه؟ أهلاً فارس...
(...كلام فارغ عن Outlook...)... المهم، برنامجي يقوم بتحميل مجموعة صور متتابعة تمهيداً لعرضها متتالية، مثل PowerPoint... فالسؤال: أريد عرض صورة جديدة كل ثانية، فكيف أفعل ذلك؟ حالياً، قمت بوضع حلقة تأخير تشغل المعالج لمدة ثانية تقريباً... حلقة for تعدّ من 1 إلى عدد كبير جداً...
وهل جربت برنامجك على أجهزة مختلفة؟
نعم...
ألم تلاحظ شيئاً غريباً؟
بلى، أن الزمن غير ثابت. بعض الأجهزة عَرَضَت الصور بسرعة، والبعض الآخر عرضها ببطء. كيف أحل الموضوع؟
يمكنك استخدام WM_TIMER لتحصل على زمن ثابت نسبياً...
كيف؟
WM_TIMER هو رسالة (message) ترسل إلى نافذتك، مثلها مثل WM_PAINT وَ WM_MOUSEMOVE...
حقاً؟ إنني بالفعل أعالج رسالة WM_PAINT من أجل الرسم، ولكن كيف أحدد الوقت؟
أنظر الإجراء SetTimer() في الـ MSDN، ويقابله KillTimer() ...
شكراً... سلام!
سلام...
☎ المكالمة الثانية: 21/6/2001 – 12:47 ظهراً :
مرحباً وسام!
أهلاً أمجد... كيف حال العمل؟
بخير والحمد لله. وصلت لمرحلة أحتاج لاستشارتك بها.
تفضل؟
أنا بحاجة إلى نظام توقيت دقيق...
كم دِقـَّـتـُه؟
بالميكروثانية...
هل جربت WM_TIMER ؟
جربته، لكنه غير دقيق.. منتظم لكنه غير دقيق تماماً... خاصة عندما يكون الجهاز مشغولاً بوظائف أخرى ثقيلة... كما أن طريقة التعامل معه لا تناسبني، فبرنامجي يسير يطريقة خطية (Linear Fashion)، ولا يعتمد على الـ messages ... أريد طريقة تـُوقِفْ عمل البرنامج لفترة محددة ثم تستكمل تنفيذه بعد انقضاء المدة.
فهمت الوضع... هل جربت Sleep() ؟
Sleep() ؟
نعم. لِـ Sleep() مُدْخَل واحد فقط، هو مدة "النوم" بالميكروثانية...
حسناً... سأجربه... شكراً... بالمرة، ألا يوجد حلول أخرى؟
بلى، يمكنك استخدام الإجراء WaitForSingleObject() للحصول على نفس النتيجة، وعلى ما أظن فإن WaitForSingleObject() أدق من Sleep()، ولكنه يتطلب منك أن تمتلك HANDLE !
HANDLE ماذا؟ HWND مثلاً؟
كلا، كلا... HANDLE لأي شيء يمكن التزمين معه...
مثل؟
HANDLE لِـملف مفتوح، HANDLE لِـ Thread، Event، Critical Section، Named Pipe، Console، وأمور أخرى...
نعم نعم، كالعادة، أَنظر في الـ MSDN لمزيد من التفاصيل.
شكراً ذكرتني! أنظر في الـ MSDN لمزيد من التفاصيل!
هذا ما قلته أنا الآن!
آسف، لم أستطع منع نفسي. على كلٍّ، إستخدم الإجراء CreateEvent() لتحصل على HANDLE لِـ Event ... وهذه أقصر الطرق لاستخدام WaitForSingleObject()
شكراً جزيلاً... سأرى ما يمكنني عمله هنا... سلام!
سلام...
☎ المكالمة الثالثة: 23/6/2001 – 8:02 مساءً :
ألو وسام؟
أهلااان معن!
كيف حالك؟ سمعت آخر نكتة؟
يا أخي اختصر، والله المساحة محدودة!
حسناً... أنا بحاجة لطريقة أحسب بها كم من الوقت مضى لتنفيذ عملية ما...
هممم... يعني الموضوع دقيق وحساس...
بالضبط!
ما هي الدقة التي تحتاجها؟
ميكروثانية، وإن كان هناك أدق، فهو أفضل!
همم... هناك ثلاثة طرق: TGT وَ QPC وَ RDTSC ... تختلف عن بعضها البعض بالدقة، السرعة والنتائج!
إني أسمعك...
سأبدأ بالحديث عن TGT ... الأحرف الثلاثة TGT هي اختصار للكلمات الثلاث: timeGetTime() وهو اسم الإجراء الرئيسي المستخدم في هذا النوع من المؤقتات. لا يستقبل timeGetTime() أي نوع من المدخلات، ويعيد قيمة DWORD تمثل كم من الوقت مضى منذ بدء تشغيل النظام (Windows في حالتنا هذه)...
يعني التاريخ؟
كلا كلا! هذه القيمة هي كم ميكروثانية مرت منذ بدء تشغيل Windows.
بالميكروثانية؟
نعم، بالميكروثانية!
شيء جميل... وكيف أستخدمه؟ أرجوك لا تقل لي "أنظر في الـ MSDN" !
لا تخف... لن أقول لك "أنظر في الـ MSDN"...
تباً!... لقد قلتها لتوِّك!
آسف... المهم، استدعِ الإجراء timeGetTime()، واحتفظ بالقيمة التي يعيدها جانباً، ثم نفذ العمليات التي تريد قياس زمن تنفيذها، ثم استدعِ timeGetTime() ثانيةً... إطرح النتيجة من القيمة الأولى لتحصل على الزمن المطلوب...
(يغمغم) استدعِ، نفذ... استدعِ... اطرح... تماماً! فهمت!
لاحظ أن الدقة الافتراضية لِـ timeGetTime() هي 10 ميكروثانية...
ماذا يعني؟
يعني إذا قمت باستدعاء timeGetTime() مرتين متتاليتين ضمن فترة أقل من 10 ميكروثانية، فإنه سيعيد ذات القيمة، أما بعد 10 ميكروثانية، فإنه سيعمل بالشكل الطبيعي...
وإذا كانت العمليات التي أريد قياسها بالفعل تستغرق أقل من 10 ميكروثانية؟
عندها أنت بحاجة إلى زيادة دقة TGT... وذلك يتم عن طريق الإجراءات timeGetDevCaps()، timeBeginPeriod() وَ timeEndPeriod() ...
حسناً، سأنظر في الـ MSDN لمزيد من التفاصيل!
هذا هو تلميذي النجيب!
سأتصل بك بعد قليل، فأنا مدعو على العشاء الآن... سلام!
سلام...
☎ المكالمة الرابعة: 23/6/2001 – 11:15 ليلاً :
أيوه وسام؟
هل غذيت نفسك جيداً؟
الحمد لله... أين توقفنا في المرة الماضية؟ أكمل لي القصة.
أية قصة؟ ليلى والذئب؟
عمَّ تتحدث؟ قصة QPC وَ RDTSC ...
آه نعم، تذكرت... يُستَخدَم كلاً من QPC وَ RDTSC بنفس الطريقة التي يستخدم فيها TGT. أي: قياس، تنفيذ، قياس، طرح...
وأين الفرق إذن؟
QPC أكثر دقة من TGT... وَ RDTSC أكثر دقة من كليهما!
وماذا تعني QPC ؟
كما في TGT، QPC هي الأحرف الثلاث الأولى من اسم الإجراء الرئيسي في هذا النظام، ويُدعى QueryPerformanceCounter() !
جميل...
لاحظ أن QPC ليس مدعوماً في جميع الأجهزة، بعض الأنظمة القديمة قد لا تدعم هذا المؤقت...
مثل؟
شخصياً، لم أرَ جهازاً لا يدعمها بعد...
هل هناك تعليمات معينة لاستعماله؟
أولاً، استدع الإجراء QueryPerformaceFrequency() من أجل معرفة الدقة العظمى المسموحة على الجهاز الحالي...
وإلى أي حد قد تصل؟
ممكن أن تصل إلى أدق من 100 نانوثانية على الأجهزة الحالية...
رائع!
يعيد لك الإجراء QueryPerformaceFrequency() معلومات مفيدة تساعد في تحويل القيم التي يعيدها QueryPerformanceCounter() إلى ميكروثانية. لأن QPC لا يعيد قيم بالميكروثانية، إنما يعيد الزمن بوحدة تختلف من جهاز لآخر، لذا فإن هذه الخطوة ضرورية...
لكنه نظام دقيق بالفعل...
هناك نقطة سوداء في صفحة QPC ...
ماهي؟
في بعض الأحيان، يُعِيد QPC قيم خاطئة!
ماذا؟
إنها نادرة جداً، لكنها تحدث!
وكيف تعرف أنها خاطئة؟
استدع QPC كلّ ثانية مثلاً... ستلاحظ أن قراءاته ثابتة، وفجأة، تجد إحدى القراءات تشير إلى فرق يوم بدلاً من ثانية، ولكن كما قلت لك... الأمر نادر الحدوث جداُ!
والحل؟
لم تمنع هذه المشكلة المبرمجين من استخدامه... فلتفادي ذلك، يقومون بالتحقق من منطقية القيمة التي يعيدها QPC من آن لآخر، وإذا وجدوا قراءة خاطئة، يعيدون استدعاء الإجراء ثانيةً للحصول على قيمة سليمة وهكذا...
هممم... وماذا عن RDTSC ؟
مثل TGT وَ QPC، يُستخدَم RDTSC لقياس فرق الزمن بين كل استدعائين.
وطبعاً RDTSC اختصار لاسم الإجراء الرئيسي...
تقريباً.
دعني أحزر... اممم... اختصار لِـ (Random Data Type Super Computer)، صح؟
كفى بلاهة! RDTSC هي اختصار لِـ (Read Time Stamp Counter)، وهو مؤقـِّت طورَته Intel لقياس وتقييم معالجاتها، ويعتمد عليه برنامج VTune بشدة للحصول على نتائج دقيقة...
يبدو أنه مؤقت من الوزن الثقيل إذن! وكيف أستدعيه؟
RDTSC هو ليس اسم إجراء يمكنك استدعاؤه مباشرة، إنما هو تعليمة من تعليمات لغة التجميع (Assembly).
يا سلام! يعني أنا مضطر أن أكتب برنامجي بالـ Assembly كي أستطيع قياسه بـ RDTSC ؟
وبـِمَ تكتب برامجك؟
بالـ Visual C++ ...
وهل نسيت أن مترجم C++ يدعم التعليمة asm، والتي تتيح لك كتابة تعليمات Assembly ضمن برنامجك؟
(صوت ضرب يد بجبهة) آه نعم! لقد نسيت ذلك حقاً!
إن RDTSC لا يعيد قيمة زمنية مثل QPC وَ TGT ...
إذن ماذا يعيد؟
RDTSC يقوم بقياس كم دورة (Cycle) قام بها المعالج حتى الآن...
(يصفر منبهراً)
بهذه الطريقة يمكنك قياس كم دورة استغرق برنامجك من المعالج...
يا رجل!
ألا ترى معي مشكلة ما هنا؟
أين المشكلة؟
كيف تحصل على الوقت! تذكر، نحن نريد الوقت وليس عدد دورات المعالج!
هممم بالفعل! كيف؟
فكـِّر قليلاً...
عجزت!
حتى ولو أعطيتك سرعة المعالج؟!
هممم... نعم.. لو قسمت عدد الدورات على سرعة المعالج، أحصل على الزمن المستغرق لأداء هذه العمليات بالثانية... صح؟
عليك نووور... إذن يصبح السؤال: كيف أحصل على سرعة المعالج؟!
كيف يا شاطر؟
أيضاً عن طريق تعليمات Assembly ...
كل هذه اللفة! من أجل قياس زمن code بسيط؟
لا شيء يأتي بسهولة كما تعلم... إذا كنت تريد هذه الدقة الفائقة، اتعب على نفسك قليلاً...
حسناً حسناً... هل لديك أية مراجع عن هذا الموضوع؟ أرجوك لا تقل لي MSDN !
لا تخف، لن أقول ذلك... أنظر في موقع Intel لمزيد من المعلومات هذه المرة!
لاااااااه ... (كليك)
(ماذا حدث له؟ هل قلت شيئاً غلط؟)
حسناً... كفانا من هذا الشريط أيضاً... ترى كم بقي لدينا من الوقت؟ (ينظر لساعته)... تباً! لقد وصلنا للصفحة 63 دون أن نشعر! يبدو أننا لن نستطيع الاستماع إلى الشريط الثالث اليوم. ربما في وقت لاحق... حسناً، يمكنك الاحتفاظ به لتستمع إليه وحدك فيما بعد... (تررن تررن)... عن إذنك لحظة... يبدو أن الهاتف يرن... (تررن تررن)... إنه يرن بالفعل... لا مشكلة إذن، لقاؤنا في العدد القادم بإذن الله... (تررن تررن) طيب طيب! ... (ألو؟ أهلااااان معن! ...... ماذا؟ فشل المشروع؟!).