أذكر محاولتي الأولى منذ سنوات عندما حاولت فهم ناقل اليو إس بي، وأذكر ضياعي بين كثير من المصطلحات المبهمة هنا وهناك، وعلى الرغم من أن تعاريف المصطلحات كانت موجودة بالفعل إلا أنها متشعّبة ولم أفلح بربطها بفهم عملي. لذلك سأحاول في هذه المقالة فكْفكة تعقيدات المصطلحات وربطها بشيء عملي، ولن تكون هذه المقدمة دليل كامل وإنما خطوة فقط أتمنى أن يستطيع القارئ بعدها الاطلاع على المقالات والكتب المتقدمة حول اليو إس بي بذخيرة أولية جيدة من معرفة المصطلحات الأساسية.
عوضاً عن شرح كل مصطلح على حدى، فإن المقال سيقوم بدمج مجموعة مصطلحات وربما بشكل استباقي للخطوات المعهودة في بعض الكتب والمقالات التي تؤسس أولاً لمفهوم وتشرح بشكل مسهب ثم تنتقل لمفهوم آخر.
مقدمة للتحمية
يعرّف التوصيف Specification للناقل التسلسلي العام USB النسخة 2.0 بأنه ” ناقل سلكي يدعم تبادل البيانات بين الحاسب المضيف Host ومجموعة متنوعة من الطرفيات في الوقت نفسه”. الناقل التسلسلي العام أو Universal Serial Bus واختصاراً USB يقدّم موصّل connector واحد يدعم توصيل أجهزة مختلفة الوظائف باستخدام نفس الناقل والموصّل. إن الجهة التي تقف وراء هذا النقال وتقوم بتنظيمه وتطويره هم الهيئة التنفيذية للناقل التسلسلي العام أو USB-IF (USB Implementers Forum).
يستخدم بروتوكول USB التوصيل النجمي التي تبدأ من عقدة أولى تسمى المضيف Host أو Root Hub ومنها يتفرع بقية العقد. يطلق على العقدة الأبن إذا تفرع منها عقد أخرى بالعقدة المحور الموزّع Hub بينما تسمى العقدة التي ينتهي عندها التفرّع بالعقدة الوظيفة أو Func وهي فعلياً الجهاز Device.
إن المحاور الموزّعة Hub وبالتعريف هي عبارة عن عقد توفّر نقاط اتصال إضافية للناقل USB، بينما العقد الوظيفية Functions فهي تقدم الإمكانيات للنظام مثل الفأرة أو لوحة المفاتيح.
يحتاج كل جهاز يعمل عبر الناقل USB إلى معرّفين فريدين unique identifiers يسميان VID أو معرّف المورّد Vendor ID و PID أو معرّف المنتَج Product ID.
يتم الحصول على معرّف المورِّد VID من USB-IF التي تعتمد قيمة مميزة لكل جهة، فمثلاً الرقائق المصمّمة من قبل Microchip لها معرّف مختلف عن رقائق شركة NXP، بينما يتم إعطاء المعرّف PID من المصنِّع وليس USB-IF.
بالتالي يمكن وعبر معرفة كل من VID و PID أن يتم التعرّف على الشركة المصنّعة وعلى الجهاز، على سبيل التجريب: لدي فأرة لاسلكية من شركة Logitech، وللحصول على المعرّفين يمكن الوصول لهما من إدارة الأجهزة في ويندوز أو باستخدام التعليمة lsusb في نظام لينكس. المهم، حصلت على الزوج التالي 0x046d/0xc534 وباستخدام محرك بحث في قاعدة بيانات مأخوذة من USB-IF وجدت نتيجة مطابقة للمتوقع:
اليو إس بي هو ناقل سلكي ومن المهم معرفة الأشكال المختلفة للموصلات المستخدمة، مع العلم أنها كلها تحوي الإشارات الأساسية: +5 فولت و الأرضي و خطي البيانات D+ و D-.
لا شيء يمنعك من اختيار شكل الموصّل الذي تريده في تصميمك وإن كان غير موافق للمعايير، على سبيل المثال: موصل من نمط ‘A’ في الطرفين أو موصّل من النمط ‘A’ من طرف المضيف Host ونمط ‘B’ في طرف الجهاز أو موصّل من نمط ‘B’ من طرف المضيف و ‘A’ من طرف الجهاز أو حتى نمط ‘B’ من الطرفين، ولكن يجب العلم أن شكل الموصل يدل على جهة حركة البيانات data stream إما صعوداً Upstream أو نزولاً Downstream. وهذا يفسر لماذا الطابعة لها دوماً الموصّل النمط ‘B’ (لأنها الجهاز وليست المضيف) و النمط ‘B’ اصطلح على كونه رمز لحركة البيانات من المُضيف نزولاً إلى الجهاز والطرف الآخر من الكبل الموصل من النمط ‘A’.
إذاً … لا يوجد سبب يمنعك من استخدام الشكل الذي تريد في أي تطبيق كان، ولكن من المهم معرفة أن الشكلين الفيزيائيين المختلفين تم إيجاده للتيميز بين الجهاز والمضيف.
إن الناقل يو إس بي معروف باستخدامه في وسائط التخزين وذلك لدعمه سرعات عالية. يتوفّر في اليو إس بي النسخة الثانية 3 سرعات:
- السرعة العالية high-speed بمعدل نقل 480 Mb/s.
- السرعة الكاملة full-speed بمعدل نقل 12 Mb/s.
- السرعة الدنيا low-speed بمعدل نقل 1.5 Mb/s.
تستخدم مقاومة رفع pullup resistor على خط D+ أو D- وذلك لتحديد بأي سرعة يعمل الجهاز. إذا كانت مقاومة الرفع موصولة على D+ فإن السرعة Full-speed وإذا كانت موصولة على D- فهو يعمل بالسرعة المنخفضة low-speed. يقوم المضيف Host بتحسس سويات الفولت للخطين ويعرف من خلالها سرعة الجهاز.
يحوي اليو إس بي لأربع أنواع أساسية من نقل البيانات بناءً على نوع التطبيق:
- Control Transfers تناقلات التحكم : وتستخدم لضبط الجهاز في وقت الربط attache time.
- Bulk Data Transfers تناقلات بالجملة/ كتلية: وتستخدم عند نقل كميات كبيرة من المعطيات
- Interrupt Data Transfers تناقلات المقاطعة: وتستخدم في تطبيقات حساسة زمنياً مثل الكيبورد والماوس.
- Isochronous Data Transfers تناقلات غير متزامنة: وتستخدم عند النقل الذي لا يتطلب فحص للأخطاء. يستخدم لتطبيقات البث التدفقي بالزمن الحقيقي Real-time steaming.
وبناءً على أنواع النقل، عرّفت USB-IF مجموعة من التصنيفات classes:
- HID (Human Interface Device) / Interrupt.
- MSD (Mass storage Device) / bulk.
- CDC (Communication Device Class) / interrupt + bulk.
تصنيفات أخرى ومحدّثة يمكن مطالعتها في الموقع الرسمي لـUSB-IF في صفحة ترميزات التصنيفات.
خطوة باتجاه اليو إس بي عملياً
بمجرّد اتصال الجهاز اليو إس بي بالمضيف Host، فإن واحد من أول الأشياء التي يجب أن يقوم بها هي التعريف عن نفسه للمضيف، هذا يتم عبر ما يسمى الموصّفات Descriptors ويتم تناقله عبر ما يسمى النقطة النهائية صفر Endpoint Zero. إن النقطة النهائية Endpoint بالتعريف ” جزء قابل للعنونة addressable من جهاز اليو إس بي وتستخدم لتصدير أو استقبال المعلومات في دفق اتصال بين الجهاز والمضيف”. لكل نقطة نهائية عنوان وجهة (دخل أو خرج).
- IN: من الجهاز إلى المضيف.
- OUT: من المضيف إلى الجهاز.
يوفر أنبوب الاتصال التحكمي Default Control Pipe مع النقطة النهائية صفر Endpoint Zero إمكانية الولوج للجهاز ومعلومات ضبطه ويسمح بالولوج للتحكم وحالة اليو إس بي USB status.
إن واحد من المصطلحات التي ستجدها في أي برنامج مضمّن لجهاز هو الموصّف Descriptor وكما يوحي الاسم ويعرّفه التوصيف الرسمي لليو إس بي “فإن الأجهزة تستخدم الموصفات كتقرير عن خصائصهم وهي عبارة عن بنية هيكلية structure ببنية محدّدة. كل موصّف يبدأ بالعدد الكلي للبايتات التي يحويها متبوعاً بحقل لتحديد نوع الموصّف”.
المصطلح الأخير الوارد في هذه الفقرة المقدِّمة هو الأنبوب Pipe، وهو بالتعريف “ارتباط بين النقطة النهائية في الجهاز والبرنامج في المضيف. تمثل الأنابيب القدرة على نقل المعطيات بين البرنامج على المضيف عبر ذاكرة buffer والنقطة النهائية في الجهاز”.
إن اليو إس بي ليس بروتوكول طبقة واحدة كعديد من البروتوكولات الأخرى وفي الصورة التالية توضيح للطبقات المشاركة:
تعرِّف USB-IF مجموعة من الموصِّفات المرجعيّة standard descriptors وهي:
- Device.
- Device_Qualifier.
- Configuration.
- Other_Speed_Qualifier.
- Interface.
- Endpoint.
- String.
هذا مثال عن الموصّف Interface descriptor :
إن الحقل bDescriptorType في الموصّف Interface descriptor هو ‘4’ بحسب جدول قيم bDescriptorType
لمشاهدة موصّف بشكل واقعي يمكن استخدام برنامج Wireshark الشهير مع إضافة USBPcap وهي أداة تجسس على منافذ اليو إس بي في الجهاز. لدي -كمثال- ماوس لاسلكية من شركة Logitech وهذا الموصف Device Descriptor الخاص بها:
نلاحظ خانات مثل idvendor و idproduct وهي توافق الأرقام التي شاهدناها في أول المقال عندما تفحصنا أرقام الجهاز من مكان آخر ونلاحظ أيضاً أنه يوافق الموصّف descriptor وتقسيماته التي تظهر في الجدول التالي:
قد يطلب المضيف بعض الموصّفات الإضافية لتوصيف الجهاز عبر جمل strings وهي تسمى موصفات string descriptors وهي ليست موصّفات إلزامية حيث يمكن للجهاز الذي لا يدعم هذه الموصفات أن يشير لها برقم صفر. حيث الرقم الموجود في الموصّف هو الرقم index للسلاسل المخزنة في الجهاز ولذلك نجد في الموصّف Device Descriptor حقل مثل iProduct حيث يحوي رقم فقط وهو index الموصف الترميزي string descriptor.
برنامج بسيط للتواصل مع جهاز يو إس بي عبر الحاسب
لنضيف المزيد من الممارسة العمليّة، فإننا سوف نكتب برنامج بسيط بلغة CPP وذلك لإجراء مسح لمعرفة الأجهزة المتصلة مع الحاسب عبر اليو إس بي، ثم طباعة المعرّفين VID و PID لكل جهاز، وثم إظهار موصّف string descriptor لأحد الأجهزة وهي فأرة يو إس بي. إن رقم الموصّف index الذي سنقوم بطلبه موجود في خانة iProduct في الموصّف device descriptor. سنستخدم المكتبة الشائعة Libusb. هي مكتبة بلغة السي وتوفّر نفاذ لأجهزة اليو إس بي وهي متعددة المنصات cross-platform أي تعمل على الويندوز وماك ولينكس.
لاستخدام المكتبة يجب تحميل مصدر المكتبة وثم بناؤه وذلك لتوليد ملف LIB أو ملف DLL. على العموم يتوفر بإصدار رسمي نسخة جاهزة للاستخدام وتحوي الملفات LIB و DLL وهناك نسختين داخل هذا الإصدار، نسخة مبنية بسلسلة أدوات MinGW وأخرى بسلسلة أدوات مايكروسوفت MSVC.
ولاستخدام Libusb يجب إخبار الرابط Linker أين هو ملف الـ LIB وذلك عند استخدام مترجم compiler من MSVC أو تحميل ملف DLL أثناء التنفيذ run time وأخيراً تضمين ملف libusb.h في الكود.
خلال محاولة استخدام النسخة المبنية مسبقاً مع Visual Studio Community 2019 واجهت أخطاء من المكتبة نفسها (أمثلة unresolved external symbol __imp__iob و Unresolved external symbol _sprintf) واكتشفت لاحقاً أنها بسبب عدم توافقية بين نسخة المترجم التي أستخدمها ونسخة المترجم المستخدم في المكتبة المبنيّة المتوفرة رسمياً وهذا أحد التبريرات “Microsoft sometimes makes changes to their C runtime, creating incompatibilities between libraries compiled with different versions” ويبدو ذلك منطقياً. الحل البديل كان بناء المكتبة من المصدر باستخدام نفس النسخة التي أستخدمها والمريح في الأمر أن يتوفر في الملفات المصدرية VS Project يمكن إدراجه مباشرة وبناؤه داخل Visual Studio.
الكود للحصول على موصّف الماوس string descriptor مباشر: بداية نقوم بتهيئة المكتبة باستدعاء libusb_init وثم إجراء مسح للأجهزة المتصلة عبر libusb_get_device_list ونهاية طلب الموصّف عبر libusb_get_device_descriptor.
لتوضيح مثال عن كيفية تفاعل المضيف Host مع device descriptor سنقوم باستدعاء string descriptor مشار له في حقل iProduct من الـdevice descriptor والذي يصف المنتج بجملة محرفيّة. قبل طلب الموصّف المحرفي string descriptor لابد من إنشاء اتصال معه بداية والحصول على رقم مرجعي يسمى handle والذي سيستخدم لاحقاً مع تابع libusb_get_string_descriptor_ascii.
#include <libusb.h> #include <stdio.h> libusb_context* context = NULL; static void find_dev(libusb_device **devs) { libusb_device *dev; int i = 0, j = 0; while ((dev = devs[i++]) != NULL) { struct libusb_device_descriptor desc; libusb_device_handle* handle = NULL; int ret; char string[256]; int r = libusb_get_device_descriptor(dev, &desc); if (r < 0) { fprintf(stderr, "failed to get device descriptor"); return; } ret = libusb_open(dev, &handle); if (LIBUSB_SUCCESS == ret) { printf("get %04x:%04x device string descriptor \n", desc.idVendor, desc.idProduct); printf("iProduct[%d]:\n", desc.iProduct); ret = libusb_get_string_descriptor_ascii(handle, desc.iProduct, string, sizeof(string)); if (ret > 0) { printf(string); } } } } int main(void) { libusb_device **devs; int r; ssize_t cnt; r = libusb_init(&context); if (r < 0) return r; cnt = libusb_get_device_list(NULL, &devs); if (cnt < 0){ libusb_exit(NULL); return (int) cnt; } find_dev(devs); libusb_free_device_list(devs, 1); libusb_exit(NULL); return 0; }
يمكن القراءة بتوسع أكبر عن المكتبة في التوثيق الرسمي لطريقة ومراحل الاتصال مع الأجهزة والاطلاع على التوابع Functions المتوفرة.
إلى هنا يمكننا التوقف مع هذا القدر من المصطلحات والمحتوى ولنكمل في جزء قادم مع توضيح وتلخيص بمثال عملي لكود لجهاز يو إس بي. أنصح القارئ حتى ذلك الوقت بالاطلاع على التوثيق الرسمي في Universal Serial Bus Specification Revision 2.0 وأيضاً حضور محاضرة من شركة مايكروتشيب بعنوان USB 2.0 Embedded Host and Device Concepts, Solutions and Traffic Capture.