قم بتنزيل التكوين على FPGA عبر USB أو قم بتفكيك FTDI MPSSE



في حياة كل درج يأتي وقت تريد فيه كتابة المحمل الخاص بك لملف التكوين في FPGA. كان علي المشاركة في تطوير منصة تدريب لقسم الجامعة التقنية. تم تصميم الحامل لدراسة معالجة الإشارات الرقمية ، على الرغم من أن هذا ليس له أهمية خاصة في إطار هذه المقالة. والأهمية هي أن FPGA (Altera Cyclone IV) في قلب المدرج ، حيث يقوم الطلاب بتجميع جميع أنواع مخططات DSP ، كما تصورها مؤلف الجناح. الحامل متصل بالكمبيوتر عبر USB. تحتاج إلى تنزيل FPGA من الكمبيوتر عبر USB.

تم اتخاذ قرار بالاتصال بجهاز كمبيوتر باستخدام FTDI في تجسيد القناة المزدوجة - FT2232H. سيتم استخدام قناة واحدة لتكوين FPGA ، ويمكن استخدام القناة الأخرى لتبادل FIFO عالي السرعة.


يحتوي FTDI على لوحة تصحيح MORPH-IC-II ، حيث تومض Cyclone II FPGA عبر USB. مفاهيم في المجال العام. رمز مصدر برنامج bootloader مفتوح جزئيًا: يتوفر برنامج bootloader نفسه ، ومع ذلك ، يتم نقل كل منطق العمل مع FTDI إلى مكتبة خاصة ولا يمكن تعديله. في الحقيقة ، خططت في الأصل لاستخدام محمل الإقلاع هذا في مشروعي ، أو ، في الحالات القصوى ، جعل صدفي قائمًا على dll. يتم تحميل البرنامج الثابت في FPGA في الوضع التسلسلي السلبي (التسلسلي السلبي - PS) ، يعمل FTDI في وضع MPSSE. على اللوح ، تم تأكيد أداء حل MORPH-IC-II بشكل كامل ، لكن المشكلة ، كما تحدث غالبًا ، لم تأت من أين. اتضح أنه أثناء تشغيل dll MORPH-IC-II ، يتم حظر جميع أجهزة FTDI المتصلة ، وكجزء من مجمع التدريب هناك جهازان آخران مع محولات مماثلة: مولد ومحلل إشارة. العمل في وقت واحد معهم غير ممكن. لعنة غريبة ومزعجة.


تم تنفيذ حالة مماثلة من قبل الرجال من المريخ روفر: USB JTAG programmer MBFTDI . يتم استخدام FTDI أيضًا في وضع MPSSE ، ولكن بخلاف MORPH-IC-II ، يتم تنفيذ عمليات FPGA في وضع JTAG. المصادر متاحة مجانًا ، لكني لم أجد إشارة واضحة لحالتهم (الترخيص). لذلك ، لاستخدامها في مشروع تجاري ، لم ترتفع يدي.


سوف أصحح مثل هذا الخطأ ، كل شيء سيتم تقديمه في إطار هذه المقالة يتم نشره في مستودع مفتوح بموجب ترخيص BSD.


قم بتنزيل ملف التكوين على شريحة FPGA


بادئ ذي بدء ، يجب أن تتعامل مع وضع التمهيد FPGA. بالنسبة لأولئك الذين بدأوا للتو في التعرف على الموضوع ، سأقدم رحلة صغيرة. على الرغم من تثبيت Altera (Intel) FPGAs من عائلة Cyclone IV E على لوحتي ، فإن طرق التحميل متشابهة لمجموعة Cyclone FPGA بأكملها ، وهناك شك في أنها مناسبة بشكل أو بآخر مناسبة للعديد من العائلات الأخرى.


يستخدم هذا النوع من FPGA SRAM المتغير لتخزين بيانات التكوين. تحدد بيانات التكوين هذه وظائف الجهاز الناتج. في المصطلحات المهنية ، غالبًا ما تسمى هذه البيانات "البرامج الثابتة". وبالتالي ، يتم تخزين البرامج الثابتة في ذاكرة وصول عشوائي خاصة وفي كل مرة يتم فيها تشغيل الجهاز ، يجب تحميله في شريحة FPGA. هناك عدة طرق (مخططات التكوين) يمكن من خلالها تحميل البرنامج الثابت في SRAM (القائمة ذات صلة بـ Cyclone IV E):


  1. المسلسل النشط (AS).
  2. مواز نشط (ا ف ب)
  3. المسلسل السلبي (PS)
  4. التوازي السلبي السريع (FPP).
  5. JTAG.

يتم اختيار وضع التمهيد المحدد باستخدام المطاريف الخارجية لمجموعة FPGA (مجموعة MSEL). وضع JTAG متاح دائمًا. يشير الوضع النشط إلى أنه عند تطبيق الطاقة ، يقرأ FPGA بشكل مستقل البيانات من الذاكرة الخارجية (التسلسلية أو المتوازية). في الوضع السلبي ، ينتظر FPGA وسيطًا خارجيًا لنقل بيانات التكوين إليه بشكل استباقي. تتناسب هذه المخططات جيدًا مع مفهوم السيد (السيد) - الرقيق (الرقيق). في الوضع النشط ، يعمل FPGA باعتباره سيدًا ، وفي الأوضاع السلبية كسبيدة.


في هذه المشكلة ، ليست FPGA ، ولكن يجب على المستخدم أن يقرر متى يجب تحديث البرنامج الثابت ، لذلك يجب أن يكون وضع التمهيد سلبيًا. ولحفظ أرجل الشريحة ، نختار واجهة تسلسلية. وضع التسلسل السلبي (PS) و JTAG مناسبان هنا. منطق JTAG أكثر تعقيدًا إلى حد ما ، لذلك دعونا نركز على الخيار الأول.
يوضح الشكل أدناه مخطط اتصال FPGA بوحدة تحكم خارجية للتنزيل في وضع PS.



لبدء التكوين ، يجب أن يقوم الرئيسي الخارجي بتوليد انتقال منخفض إلى مرتفع على خط nCONFIG . بمجرد أن تصبح FPGA جاهزة لاستقبال البيانات ، ستشكل مستوى عالٍ على خط nSTATUS . بعد ذلك ، يمكن للسيد البدء في إرسال البيانات على خط البيانات [0] ، وتنبض الساعة المقابلة على خط DCLK . يجب إرسال البيانات إلى الجهاز المستهدف حتى يتم إنشاء مستوى عالٍ على خط CONF_DONE (أو لا تنتهي البيانات) ، ويتحول FPGA إلى حالة التهيئة. وتجدر الإشارة إلى أنه بعد ضبط CONF_DONE على واحد ، يجب تطبيق نبضتي ساعة أخريين حتى تبدأ تهيئة FPGA.


يتم إرسال البيانات عن طريق البت الأقل أهمية ( LSB ) إلى الأمام ، أي إذا كان ملف التكوين يحتوي على التسلسل 02 1B EE 01 FA (خذ المثال كما هو من الكتيب) ، فيجب تشكيل التسلسل على خط البيانات:


0100-0000 1101-1000 0111-0111 1000-0000 0101-1111 

وبالتالي ، يتم استخدام خمسة خطوط فقط: خطوط DATA [0] و DCLK للإرسال التسلسلي ، خطوط nCONFIG ، nSTATUS ، CONF_DONE للتحكم.
في جوهرها ، لا يعد وضع PS أكثر من SPI مع معالجة إضافية للعلم.
يجب أن يكون معدل نقل البيانات أقل من الحد الأقصى للتردد المشار إليه في الوثائق ؛ بالنسبة لسلسلة Cyclone IV E المستخدمة في المشروع ، يكون 66 ميجاهرتز.


لا يوجد الحد الأدنى لتردد الإرسال ، من الناحية النظرية من الممكن تعليق التكوين لفترة غير محددة. وهذا يوفر فرصًا ممتازة لتصحيح الأخطاء خطوة بخطوة بمشاركة راسم الذبذبات ، والذي سنستخدمه بالتأكيد.


يوضح الشكل أدناه مخطط توقيت الواجهة مع أهم التوقيتات.



خبيث الوحش مبس


خذ بعين الاعتبار تشغيل FTDI في وضع MPSSE. في رأيي ، يعد وضع MPSSE (محرك تسلسلي متزامن متعدد البروتوكولات) محاولة ناجحة إلى حد ما لإنشاء مصمم واجهة تسلسلية معينة ، لإعطاء المطور الفرصة لتنفيذ بروتوكولات نقل البيانات واسعة النطاق ، مثل SPI و I2C و JTAG وسلك واحد والعديد من الآخرون على أساسهم.


يتوفر الوضع حاليًا للدوائر المصغرة: FT232H و FT2232D و FT2232H و FT4232H. في مشروعي ، أستخدم FT2232H ، لذلك نتحدث عنه إلى حد كبير. بالنسبة لوضع MPSSE ، يتم تخصيص 16 أرجل ، مقسمة إلى وحدتي بايت: يمكن قراءة أو خفض كل حرف L العلوي و H. تتميز الأرجل السفلية الأربعة للبايت L بوظائف خاصة - يمكن أن يحدث نقل البيانات التسلسلي من خلالها. يمكن تكوين كل رجل كمدخل أو إخراج ، يمكن تعيين قيمة افتراضية للإخراج. بالنسبة للإرسال التسلسلي ، وترتيب البتات ( MSB / LSB ) ، وطول الكلمة المرسلة ، وتكرار نبضات الساعة ، والتزامن الأمامي - الأمامي (الصاعد) أو الخلفي (السقوط) ، يمكنك اختيار إرسال نبضات الساعة فقط بدون بيانات ، أو اختيار تسجيل الوقت ثلاثي المراحل (ذات الصلة بـ I2C) وأكثر من ذلك بكثير.


انتقل بسلاسة إلى البرمجة. هناك طريقتان بديلتان لتفاعل البرنامج مع شرائح FTDI: الأولى ، دعنا نسميها كلاسيكية ، في هذه الحالة ، عند توصيلها بمنفذ USB ، يتم تعريف الشريحة في النظام كمنفذ تسلسلي افتراضي (COM) ، ويستخدم نظام التشغيل برنامج تشغيل VCP (منفذ COM الظاهري). لا تختلف جميع البرمجة الإضافية عن برمجة منفذ COM الكلاسيكي: مفتوح - مرسل / محسوب - مغلق. وينطبق هذا على أنظمة التشغيل المختلفة ، بما في ذلك Linux و Mac OS. ومع ذلك ، مع هذا النهج ، لن يكون من الممكن تحقيق جميع ميزات وحدة تحكم FTDI - ستعمل الشريحة كمحول USB-UART. يتم توفير الطريقة الثانية من قبل مكتبة الملكية FTD2XX ، توفر هذه الواجهة وظائف خاصة غير متوفرة في واجهة برمجة التطبيقات لمنفذ COM القياسي ، على وجه الخصوص ، من الممكن تكوين واستخدام أوضاع تشغيل خاصة ، مثل MPSSE و 245 FIFO و Bit-bang. مكتبة FTD2XX API موثقة جيدًا بواسطة دليل مبرمج تطوير تطبيقات البرامج D2XX ، والمعروف على نطاق واسع لفترة طويلة في الدوائر الضيقة. ونعم ، FTD2XX متاح أيضًا لأنظمة التشغيل المختلفة.


واجه مطورو FTDI مهمة دمج MPSSE الجديد نسبيًا في نموذج التفاعل البرمجي D2XX الحالي. ونجحوا ، للعمل في وضع MPSSE يتم استخدام نفس المجموعة من الوظائف كما هو الحال مع الأوضاع "الكلاسيكية" الأخرى ، يتم استخدام نفس مكتبة FTD2XX API.


باختصار ، يمكن وصف خوارزمية العمل في وضع MPSSE على النحو التالي:


  1. ابحث عن الجهاز في النظام وافتحه.
  2. قم بتهيئة الشريحة ووضعها في وضع MPSSE.
  3. اضبط وضع تشغيل MPSEE.
  4. العمل المباشر باستخدام البيانات: إرسال واستقبال وإدارة GPIO - ننفذ بروتوكول التبادل المستهدف.
  5. أغلق الجهاز.

كتابة محمل إقلاع


دعونا ننزل إلى الجزء العملي. في تجاربي ، سأستخدم إصدار Eclipse من Oxygen.3a Release (4.7.3a) مثل IDE ، و mingw32-gcc (6.3.0) كمترجم. نظام التشغيل Win7.


من موقع FTDI الإلكتروني نقوم بتنزيل أحدث إصدار حالي من برنامج التشغيل لنظام التشغيل الخاص بنا. في الأرشيف نجد ملف الرأس ftd2xx.h مع وصف لجميع وظائف API. يتم تنفيذ واجهة برمجة التطبيقات نفسها كملف ftd2xx.dll ، لكننا سنترك الاستيراد الديناميكي لوقت لاحق ، وسنستخدم الرابط الثابت: نحتاج إلى ملف المكتبة ftd2xx.lib. بالنسبة لحالتي ، ftd2xx.lib موجود في دليل i386.


في Eclipse ، قم بإنشاء مشروع C جديد. يمكن الوثوق بإنشاء ملف makefile باستخدام IDE. في إعدادات الرابط ، حدد مسار واسم مكتبة ftd2xx (قمت بنقل الملفات المطلوبة إلى دليل المشروع في مجلد ftdi). لن أركز على ميزات إعداد مشروع لـ Eclipse ، لأنني أشك في أن معظمهم يستخدمون بيئات ومجمعين آخرين لبرمجة Win.


النقطة الأولى. ابحث عن جهاز وافتحه


يتيح لك FTD2XX API فتح الشريحة باستخدام معلومات معروفة أو أخرى عنها. قد يكون هذا الرقم التسلسلي الخاص به في النظام: ستأخذ أول شريحة FTDI المتصلة الرقم 0 والرقم التالي وما إلى ذلك. يتم تحديد الرقم في النظام بالترتيب الذي يتم توصيل الدوائر المصغرة به ، وبصورة معتدلة ، فإن هذا ليس مناسبًا دائمًا. لفتح الشريحة بالرقم ، يتم FT_Open وظيفة FT_Open . يمكنك فتح الشريحة عن طريق FT_OPEN_BY_SERIAL_NUMBER التسلسلي ( FT_OPEN_BY_SERIAL_NUMBER ) أو الوصف ( FT_OPEN_BY_DESCRIPTION ) أو حسب الموقع ( FT_OPEN_BY_LOCATION ) ، لذلك يتم FT_OpenEx وظيفة FT_OpenEx . يتم تخزين الرقم التسلسلي والوصف في الذاكرة الداخلية للرقاقة ويمكن تسجيله هناك أثناء تصنيع الجهاز مع تثبيت FTDI. يصف الوصف ، كقاعدة عامة ، نوع الجهاز أو العائلة ، ويجب أن يكون الرقم التسلسلي فريدًا لكل منتج. لذلك ، فإن الطريقة الأكثر ملاءمة لتحديد الأجهزة التي يدعمها البرنامج الذي يتم تطويره هي وصفه. سنفتح شريحة FTDI حسب الوصف (الواصف). في الواقع ، إذا علمنا في البداية بسلسلة واصف الرقائق ، فلن نحتاج إلى البحث عن الجهاز في النظام ، ولكن كتجربة ، سنعرض جميع الأجهزة المتصلة بالكمبيوتر باستخدام FTDI. باستخدام وظيفة FT_CreateDeviceInfoList ، FT_CreateDeviceInfoList قائمة مفصلة بالرقائق المتصلة ، وباستخدام وظيفة FT_GetDeviceInfoList ، FT_GetDeviceInfoList فيها.


قائمة الأجهزة المتصلة. قائمة:
 ftStatus = FT_CreateDeviceInfoList(&numDevs); if (ftStatus == FT_OK) { printf("Number of devices is %d\n",numDevs); } if (numDevs == 0) return -1; // allocate storage for list based on numDevs devInfo = (FT_DEVICE_LIST_INFO_NODE*)malloc(sizeof(FT_DEVICE_LIST_INFO_NODE)*numDevs); ftStatus = FT_GetDeviceInfoList(devInfo,&numDevs); if (ftStatus == FT_OK) for (int i = 0; i < numDevs; i++) { printf("Dev %d:\n",i); printf(" Flags=0x%x\n",devInfo[i].Flags); printf(" Type=0x%x\n",devInfo[i].Type); printf(" ID=0x%x\n",devInfo[i].ID); printf(" LocId=0x%x\n",devInfo[i].LocId); printf(" SerialNumber=%s\n",devInfo[i].SerialNumber); printf(" Description=%s\n",devInfo[i].Description); } 

مرحبا بحديقتي
 D:\workspace\ftdi-mpsse-ps\Debug>ftdi-mpsse-ps.exe Number of devices is 4 Dev 0: Flags = 0x0 Type = 0x5 ID = 0x4036001 LocId = 0x214 SerialNumber = AI043NNV Description = FT232R USB UART Dev 1: Flags = 0x2 Type = 0x6 ID = 0x4036010 LocId = 0x2121 SerialNumber = L731T70OA Description = LESO7 A Dev 2: Flags = 0x2 Type = 0x6 ID = 0x4036010 LocId = 0x2122 SerialNumber = L731T70OB Description = LESO7 B Dev 3: Flags = 0x2 Type = 0x8 ID = 0x4036014 LocId = 0x213 SerialNumber = FTYZ92L6 Description = LESO4.1_ER 

هناك ثلاثة أجهزة مزودة بشرائح FTDI متصلة بجهاز الكمبيوتر الخاص بي: FT232RL (النوع 0x5) ، FT2232H (النوع 0x6) و FT232H (المرحلة 0x8). تم عرض شريحة FT2232H في النظام كجهازين مستقلين (Dev 1 و Dev 2). واجهة FPGA PS متصلة بـ Dev 2 ، واصفها هو "LESO7 B". افتحه:


 //Open a device with device description "LESO7 B" ftStatus = FT_OpenEx("LESO7 B", FT_OPEN_BY_DESCRIPTION, &ftHandle); if (ftStatus != FT_OK) { printf ("pen failure\r\n"); return -1; } 

تعرض معظم وظائف واجهة برمجة التطبيقات حالة المكالمة من النوع FT_STATUS ، ويتم وصف جميع القيم الممكنة على أنها تعداد في ملف الرأس. هناك العديد منها ، ولكن يكفي أن تعرف أن قيمة FT_OK هي عدم وجود خطأ ، وجميع القيم الأخرى هي رموز خطأ. أسلوب البرمجة الجيد هو التحقق من قيمة الحالة بعد كل استدعاء لوظيفة API.


إذا تم فتح الجهاز بنجاح ، فعندئذٍ يظهر في متغير ftHandle قيمة أخرى غير الصفر ، بعض واصفات الملفات المكافئة ، والتي يتم استخدامها عند العمل مع الملفات. يقوم المقبض الناتج بإنشاء اتصال بواجهة الأجهزة ويجب استخدامه عند استدعاء جميع وظائف المكتبة التي تتطلب الوصول إلى الشريحة.
من أجل التأكيد عمليًا على تشغيل النظام للمرحلة الحالية ، يجب أن ننتقل فورًا إلى الخطوة الخامسة من الخوارزمية.


بعد الانتهاء من العمل مع الشريحة ، يجب إغلاقها. للقيام بذلك ، استخدم الدالة FT_Close :


 FT_Close(ftHandle); 

النقطة 2. تهيئة الشريحة وتشغيل MPSSE


يعد الإعداد نموذجيًا لمعظم الأوضاع وقد تم وصفه جيدًا في وثائق أساسيات AN_135 FTDI MPSSE .


  1. نقوم بإعادة ضبط الشريحة. دالة FT_ResetDevice .
  2. في حالة وجود أي قمامة في المخزن المؤقت للاستلام ، نقوم بمسحها. وظيفة FT_Purge .
  3. اضبط حجم المخازن المؤقتة للقراءة والكتابة. دالة FT_SetUSBParameters .
  4. قم بإيقاف التكافؤ. FT_SetChars .
  5. وضعنا مهلات للقراءة والكتابة. بشكل افتراضي ، يتم تعطيل المهلات ، وتمكين مهلة الإرسال. FT_SetTimeouts .
  6. نقوم بتكوين وقت الانتظار لإرسال حزمة من الشريحة إلى المضيف. افتراضيًا ، تسارع 16 مللي ثانية إلى 1 مللي ثانية. FT_SetLatencyTimer .
  7. قم بتشغيل التحكم في التدفق لمزامنة الطلبات الواردة. FT_SetFlowControl .
  8. كل شيء جاهز لتنشيط وضع MPSSE. إعادة تعيين وحدة تحكم MPSSE. نستخدم الدالة FT_SetBitMode ، اضبط الوضع على 0 (mode = 0 ، mask = 0).
  9. قم بتشغيل وضع MPSSE. الوظيفة FT_SetBitMode - الوضع = 2 ، القناع = 0.

نقوم بتوحيد وتهيئة الشريحة في وظيفة MPSSE_open ، كمعلمة نقوم بتمرير خط بمقبض الجهاز المراد فتحه:


قائمة MPSSE_open
 static FT_STATUS MPSSE_open (char *description) { FT_STATUS ftStatus; ftStatus = FT_OpenEx(description, FT_OPEN_BY_DESCRIPTION, &ftHandle); if (ftStatus != FT_OK) { printf ("open failure\r\n"); return FT_DEVICE_NOT_OPENED; } printf ("open OK, %d\r\n", ftHandle); printf("\nConfiguring port for MPSSE use...\n"); ftStatus |= FT_ResetDevice(ftHandle); //Purge USB receive buffer first by reading out all old data from FT2232H receive buff: ftStatus |= FT_Purge(ftHandle, FT_PURGE_RX); //Set USB request transfer sizes to 64K: ftStatus |= FT_SetUSBParameters(ftHandle, 65536, 65536); //Disable event and error characters: ftStatus |= FT_SetChars(ftHandle, 0, 0, 0, 0); //Sets the read and write timeouts in milliseconds: ftStatus |= FT_SetTimeouts(ftHandle, 0, 5000); //Set the latency timer to 1mS (default is 16mS): ftStatus |= FT_SetLatencyTimer(ftHandle, 1); //Turn on flow control to synchronize IN requests: ftStatus |= FT_SetFlowControl(ftHandle, FT_FLOW_RTS_CTS, 0x00, 0x00); //Reset controller: ftStatus |= FT_SetBitMode(ftHandle, 0x0, FT_BITMODE_RESET); //Enable MPSSE mode: ftStatus |= FT_SetBitMode(ftHandle, 0x0, FT_BITMODE_MPSSE); if (ftStatus != FT_OK) { printf("Error in initializing the MPSSE %d\n", ftStatus); return FT_OTHER_ERROR; } Sleep(50); // Wait for all the USB stuff to complete and work return FT_OK; } 

البند 3. تكوين وضع MPSEE العملية


في الواقع ، في هذه المرحلة يتم تنشيط معالج MPSSE وجاهز لتلقي الأوامر. الأوامر عبارة عن تتابعات بايت ، أولها بايت "op-code" ، متبوعة بمعلمات الأمر. قد لا يحتوي الأمر على معلمات ويتكون من "op-code" واحد. يتم إرسال الأوامر باستخدام وظيفة FT_Write ، ويمكن الحصول على استجابة من معالج MPSSE باستخدام وظيفة FT_Read .


بعد إرسال كل أمر ، من المفيد قراءة استجابة المعالج ، لأنه في حالة وجود أمر غير صحيح ، قد تحتوي الاستجابة على رسالة خطأ - الحرف 0xFA. يمكن استخدام آلية "الأمر السيئ - استجابة 0xFA" لمزامنة برنامج التطبيق مع معالج MPSSE. إذا كان كل شيء على ما يرام ، فستعيد الشريحة حرف 0xFA في أمر خاطئ عن عمد. يتم وصف رموز التشغيل في Command Processor (معالج المعالج) لـ MPSSE و MCU Host Emulation Mode .
يأتي تكوين MPSSE في تعيين معدل البيانات والاتجاه والحالات الأولية لخطوط الإدخال / الإخراج.
ضع في اعتبارك تعيين معدل بيانات معالج MPSSE. تختلف إعدادات الرقائق التي تدعم وضع السرعة الكاملة فقط (FT2232 D ) والرقائق ذات السرعة العالية (FT2232 H و FT232H و FT4232H) إلى حد ما. يستخدم الطراز FT2232D القديم ساعة 12 ميجاهرتز ، بينما تستخدم الأجهزة الحديثة 60 ميجاهرتز. ومن هنا صيغة حساب معدل نقل البيانات:


DataSpeed= fracfcore(1+Divisor) cdot2


حيث f core هو التردد الأساسي لـ FTDI ، فإن Divisor هو مقسم ثنائي البايت ، والذي ، في الواقع ، يضبط تردد ساعة البيانات.
ونتيجة لذلك ، إذا كان الفاصل يساوي صفرًا ، فسيكون الحد الأقصى لمعدل نقل البيانات 30 ميجابت في الثانية ، وسيكون الحد الأدنى لمعدل نقل البيانات عند فاصل 65535 - 458 بت / ثانية.
سوف نعهد بحساب المقسم إلى المعالج الأولي. يعيد الماكرو المقسوم:


 #define FCORE 60000000ul #define MPSSE_DATA_SPEED_DIV(data_speed) ((FCORE/(2*data_speed)) -1) 

ويعيد هذان الماكرو وحدات البايت العالية والمنخفضة للمقسّم ، على التوالي:


 #define MPSSE_DATA_SPEED_DIV_H(data_speed) ((MPSSE_DATA_SPEED_DIV(data_speed)) >> 8) #define MPSSE_DATA_SPEED_DIV_L(data_speed) \ (MPSSE_DATA_SPEED_DIV(data_speed) - (MPSSE_DATA_SPEED_DIV_H(data_speed)<< 8)) 

بالإضافة إلى ذلك ، تجدر الإشارة إلى أنه في الرقائق الحديثة للتوافق مع FT2232D القديم هناك 5 مقسم إضافي ، والذي يحول 60 ميجاهرتز إلى 12 ميجاهرتز. يتم تنشيط هذا الحاجز افتراضيًا ، وفي حالتنا يجب إيقاف تشغيله.
نجد كود التشفير المقابل (0x8A) وأمر الخوذة للمعالج:


قائمة تقديم الفريق
 BYTE byOutputBuffer[8], byInputBuffer[8]; DWORD dwNumBytesToRead, dwNumBytesSent = 0, dwNumBytesRead = 0; byOutputBuffer[0] = 0x8A; ftStatus = FT_Write(ftHandle, byOutputBuffer, 1, &dwNumBytesSent); Sleep(2); // Wait for data to be transmitted and status ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); ftStatus |= FT_Read(ftHandle, byInputBuffer, dwNumBytesToRead, &dwNumBytesRead); if (ftStatus != FT_OK) { printf("Error\r\n"); return FT_OTHER_ERROR; } else if (dwNumBytesToRead > 0) { printf("dwNumBytesToRead = %d:", dwNumBytesToRead); for ( int i = 0; i < dwNumBytesToRead; i++) printf (" %02Xh", byInputBuffer[i]); printf("\r\n"); return FT_INVALID_PARAMETER; } return FT_OK; 

كتجربة ، بدلاً من الأمر الفعلي 0x8A ، سنرسل القيمة 0xFE ، التي لا تتوافق مع أي كود تشغيلي ، ناتج وحدة التحكم:


 dwNumBytesToRead = 2: FAh FEh 

قام المعالج بإرجاع وحدتي بايت ، بايت بايت الأمر 0xFA وقيمة هذا الأمر غير صحيح. وبالتالي ، من خلال إرسال عدة أوامر في وقت واحد ، لا يمكننا تتبع حقيقة الخطأ فقط ، ولكن أيضًا فهم أي فريق حدث هذا الخطأ.
حتى لا يتم التعامل مع "الأرقام السحرية" في المستقبل ، سنقوم بتنسيق جميع رموز المرجع في شكل ثوابت ونضعها في ملف رأس منفصل.
لتكوين الوضع بالكامل ، تحتاج إلى تحديد اتجاه خطوط الإدخال / الإخراج وقيمتها الافتراضية. دعونا ننتقل إلى مخطط الاتصال. حتى لا تفسد مقالة منتفخة بالفعل ، لقد رسمت جزءًا مثيرًا من المخطط:



يجب تكوين خطوط DCLK و DATA [0] و nCONFIG كمخرجات وخطوط nSTATUS و CONF_DONE كمدخلات. باستخدام الرسم البياني ، نحدد الحالات الأولية التي يجب أن تحتوي عليها الخطوط. للتوضيح ، يتم تلخيص pinout للدائرة في الجدول:


دبوس FPGAاسم الدبوسدبوسMPSSEالاتجاهافتراضي
DCLKBDBUS038TCK / SKخارج0
البيانات [0]BDBUS139TDI / DOخارج1
nconfigBDBUS240TDO / DIخارج1
نستاتوسBDBUS341TMS / CSفي1
CONF_DONEBDBUS443GPIOL0في1

تقع جميع الخطوط المستخدمة في البايت المنخفض لمنفذ MPSSE. لتعيين القيمة ، استخدم كود المرجع 0x80. يفترض هذا الأمر وسيطتين: البايت الأول الذي يتبع شفرة المرجع هو قيمة بتة بت ، والثاني هو الاتجاه (الأول هو منفذ الإخراج ، والصفر هو منفذ الإدخال).
كجزء من مكافحة "الرقم السحري" ، سيتم تنسيق جميع أرقام السلاسل التسلسلية وقيمها الافتراضية على أنها ثوابت:


تحديد المنافذ
 #define PORT_DIRECTION (0x07) #define DCLK (0) #define DATA0 (1) #define N_CONFIG (2) #define N_STATUS (3) #define CONF_DONE (4) // initial states of the MPSSE interface #define DCLK_DEF (1) #define DATA0_DEF (0) #define N_CONFIG_DEF (1) #define N_STATUS_DEF (1) #define CONF_DONE_DEF (1) 

يبقى فقط للتأكد من تعطيل حلقة TDI - TDO (يمكن تنشيطها للاختبار) ووضعها في وظيفة منفصلة:


سرد وظيفة MPSSE_setup
 static FT_STATUS MPSSE_setup () { DWORD dwNumBytesToSend, dwNumBytesSent, dwNumBytesToRead, dwNumBytesRead; BYTE byOutputBuffer[8], byInputBuffer[8]; FT_STATUS ftStatus; // Multple commands can be sent to the MPSSE with one FT_Write dwNumBytesToSend = 0; // Start with a fresh index byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_DIVIDER_5; byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_ADAPTIVE_CLK; byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_3PHASE_CLOCKING; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); dwNumBytesToSend = 0; // Reset output buffer pointer // Set TCK frequency // Command to set clock divisor: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_TCK_DIVISION; // Set ValueL of clock divisor: byOutputBuffer[dwNumBytesToSend++] = MPSSE_DATA_SPEED_DIV_L(DATA_SPEED); // Set 0xValueH of clock divisor: byOutputBuffer[dwNumBytesToSend++] = MPSSE_DATA_SPEED_DIV_H(DATA_SPEED); ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); dwNumBytesToSend = 0; // Reset output buffer pointer // Set initial states of the MPSSE interface // - low byte, both pin directions and output values /* | FPGA pin | Pin Name | Pin | MPSSE | Dir | def | | --------- | -------- | --- | ------ | --- | --- | | DCLK | BDBUS0 | 38 | TCK/SK | Out | 0 | | DATA[0] | BDBUS1 | 39 | TDI/DO | Out | 1 | | nCONFIG | BDBUS2 | 40 | TDO/DI | Out | 1 | | nSTATUS | BDBUS3 | 41 | TMS/CS | In | 1 | | CONF_DONE | BDBUS4 | 43 | GPIOL0 | In | 1 | */ // Configure data bits low-byte of MPSSE port: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_DATA_BITS_LOWBYTE; // Initial state config above: byOutputBuffer[dwNumBytesToSend++] = (DCLK_DEF << DCLK) | (DATA0_DEF << DATA0) | (N_CONFIG_DEF << N_CONFIG) | (N_STATUS_DEF << N_STATUS) | (CONF_DONE_DEF << CONF_DONE); // Direction config above: byOutputBuffer[dwNumBytesToSend++] = PORT_DIRECTION; ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); // Send off the low GPIO config commands dwNumBytesToSend = 0; // Reset output buffer pointer // Set initial states of the MPSSE interface // - high byte, all input, Initial State -- 0. // Send off the high GPIO config commands: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_DATA_BITS_HIGHBYTE; byOutputBuffer[dwNumBytesToSend++] = 0x00; byOutputBuffer[dwNumBytesToSend++] = 0x00; ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); // Disable loopback: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_LOOP_TDI_TDO; ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); Sleep(2); // Wait for data to be transmitted and status ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); ftStatus |= FT_Read(ftHandle, byInputBuffer, dwNumBytesToRead, &dwNumBytesRead); if (ftStatus != FT_OK) { printf("Unknown error in initializing the MPSSE\r\n"); return FT_OTHER_ERROR; } else if (dwNumBytesToRead > 0) { printf("Error in initializing the MPSSE, bad code:\r\n"); for ( int i = 0; i < dwNumBytesToRead; i++) printf (" %02Xh", byInputBuffer[i]); printf("\r\n"); return FT_INVALID_PARAMETER; } return FT_OK; } 

النقطة 4. ننفذ بروتوكول التحميل


يبدو أن كل شيء جاهز للتجارب العملية. أولاً ، تحقق من تنفيذ التهيئة بشكل صحيح ، في الجزء الرئيسي من البرنامج ، اتصل MPSSE_open() و MPSSE_setup() ، وقبل إغلاق الجهاز ( FT_Close ) ، نضع getchar() فارغًا. قم بتشغيل البرنامج واستخدام الذبذبات للتأكد من تعيين جميع خطوط PS إلى المستويات الافتراضية. تغيير قيمة هذه المستويات في التهيئة (لن يحدث شيء سيئ مع FPGA) ، نتأكد من أن معالج MPSSE يعطي النتيجة المرجوة على أنها صالحة - كل شيء يعمل بشكل كاف ويمكنك المضي قدما في نقل البيانات.
يتم إرسال البيانات واستلامها بشكل متسلسل في وضع الأوامر باستخدام نفس كود التشغيل. البايت الأول من الأمر هو كود التشفير ، الذي يحدد نوع العملية ، متبوعًا بطول التسلسل المرسل أو المستلم ، وإذا كان الإرسال ، البيانات الفعلية. يمكن لمعالج MPSSE إرسال البيانات واستقبالها ، وكذلك القيام بذلك في نفس الوقت. يمكن أن يكون الإرسال إما أقل بتات إلى الأمام (LSB) أو الأكثر أهمية (MSB). يمكن أن يحدث نقل البيانات إما على الحواف الأمامية أو اللاحقة لنبضات الساعة. تحتوي كل مجموعة من الخيارات على شفرة تشغيل خاصة بها ، وتصف كل بتة شفرة تشغيل وضع التشغيل:


بتالوظيفة
0تزامن الكتابة الأمامية: 0 - موجب ، 1 - سلبي
11 - العمل بالبايت 0 - العمل بالبتات
2الحافة الأمامية للقراءة: 0 - إيجابي ، 1 - سلبي
3وضع الإرسال: 1 - LSB ، 0 - MSB أولاً
4نقل بيانات TDI
5قراءة البيانات من خط TDO
6نقل بيانات TMS
7يجب أن تكون 0 ، وإلا فهذه مجموعة أخرى من الأوامر

عند تكوين FPGAs وفقًا لمخطط PS ، يتم إرسال البيانات على الحافة الأمامية في وضع LSB.إنه أكثر ملاءمة بالنسبة لنا للعمل بالبايت بدلاً من البتات ، وفي هذه الحالة سيأخذ كود المرجع القيمة 0001_1000b أو 0x18 في تمثيل سداسي عشري. وسيطات الأمر ستكون طول التسلسل المرسل (وحدتان بايتتان ، تبدأ بأدنى دلالة) ، وتسلسل البيانات نفسه. يجب أن تؤخذ في الاعتبار ميزة صغيرة: يتم ترميز الطول ناقص واحد. أي إذا أردنا إرسال بايت واحد ، فسيكون الطول 0 ، إذا أردنا إرسال 65536 ، فإننا بحاجة إلى تحديد طول 65535. أعتقد أنه من الواضح سبب القيام بذلك. دعنا نرسل كتلة البيانات كدالة MPSSE_send.


سرد وظيفة MPSSE_send
 static BYTE byBuffer[65536 + 3]; static FT_STATUS MPSSE_send(BYTE * buff, DWORD dwBytesToWrite) { DWORD dwNumBytesToSend = 0, dwNumBytesSent, bytes; FT_STATUS ftStatus; // Output on rising clock, no input // MSB first, clock a number of bytes out byBuffer[dwNumBytesToSend++] = MPSSE_CMD_LSB_DATA_OUT_BYTES_POS_EDGE; // 0x18 bytes = dwBytesToWrite -1; byBuffer[dwNumBytesToSend++] = (bytes) & 0xFF; // Length L byBuffer[dwNumBytesToSend++] = (bytes >> 8) & 0xFF; // Length H memcpy(&byBuffer[dwNumBytesToSend], buff, dwBytesToWrite); dwNumBytesToSend += dwBytesToWrite; ftStatus = FT_Write(ftHandle, byBuffer, dwNumBytesToSend, &dwNumBytesSent); if (ftStatus != FT_OK ) { printf ("ERROR send data\r\n"); return ftStatus; } else if (dwNumBytesSent != dwNumBytesToSend) { printf ("ERROR send data, %d %d\r\n", dwNumBytesSent, dwNumBytesToSend); } return FT_OK; } 

— 65 , - , op-code . byBuffer , buff , , op-code . , , .
, "" , 25 , , , 1 ( , #define DATA_SPEED 1000000ul ). :


 BYTE byOutputBuffer[] = {0x02, 0x1B, 0xEE, 0x01, 0xFA}; MPSSE_send(byOutputBuffer, sizeof(byOutputBuffer)); 

( ):


DATA[0] , — DCLK . . , , .


, SPI ( ). , PS, . nCONFIG , nSTATUS , CONF_DONE . — , , — , .


MPSSE_get_lbyte , , .


MPSSE_get_lbyte
 static FT_STATUS MPSSE_get_lbyte(BYTE *lbyte) { DWORD dwNumBytesToSend, dwNumBytesSent, dwNumBytesToRead, dwNumBytesRead; BYTE byOutputBuffer[8]; FT_STATUS ftStatus; dwNumBytesToSend = 0; byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_GET_DATA_BITS_LOWBYTE; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); Sleep(2); // Wait for data to be transmitted and status ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); ftStatus |= FT_Read(ftHandle, lbyte, dwNumBytesToRead, &dwNumBytesRead); if ((ftStatus != FT_OK) & (dwNumBytesToRead != 1)) { printf("Error read Lbyte\r\n"); return FT_OTHER_ERROR; // Exit with error } return FT_OK; } 

, op-code , . , - , , . , . MPSSE_set_lbyte :


MPSSE_set_lbyte
 static FT_STATUS MPSSE_set_lbyte(BYTE lb, BYTE mask) { DWORD dwNumBytesToSend, dwNumBytesSent; BYTE byOutputBuffer[8], lbyte; FT_STATUS ftStatus; ftStatus = MPSSE_get_lbyte(&lbyte); if ( ftStatus != FT_OK) return ftStatus; // Set to zero the bits selected by the mask: lbyte &= ~mask; // Setting zero is not selected by the mask bits: lb &= mask; lbyte |= lb; dwNumBytesToSend = 0; // Set data bits low-byte of MPSSE port: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_DATA_BITS_LOWBYTE; byOutputBuffer[dwNumBytesToSend++] = lbyte; byOutputBuffer[dwNumBytesToSend++] = PORT_DIRECTION; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); if ((ftStatus != FT_OK) & (dwNumBytesSent != 1)) { printf("Error set Lbyte\r\n"); return FT_OTHER_ERROR; } return FT_OK; } 

, . : FTDI; MPSSE; rbf- , nCONFIG , N_STATUS ; rbf- ; , , CONF_DONE . , MPSSE FTDI . , nCONFIG "" , , , .


main
 int main(int argc, char *argv[]) { FT_STATUS ftStatus; BYTE lowByte; DWORD numDevs; // create the device information list if ( argv[1] == NULL) { printf ("NO file\r\n"); return -1; } frbf = fopen(argv[1],"rb"); if (frbf == NULL) { printf ("Error open rbf\r\n"); return -1; } ftStatus = FT_CreateDeviceInfoList(&numDevs); if ((numDevs == 0) || (ftStatus != FT_OK)) { printf("Error. FTDI devices not found in the system\r\n"); return -1; } ftStatus = MPSSE_open ("LESO7 B"); if (ftStatus != FT_OK) { printf("Error in MPSSE_open %d\n", ftStatus); EXIT(-1); } MPSSE_setup(); if (ftStatus != FT_OK) { printf("Error in MPSSE_setup %d\n", ftStatus); EXIT(-1); } printf ("nConfig -> 0\r\n"); MPSSE_set_lbyte(0, 1 << N_CONFIG); printf ("nConfig -> 1\r\n"); MPSSE_set_lbyte(1 << N_CONFIG, 1 << N_CONFIG); if (MPSSE_get_lbyte(&lowByte) != FT_OK) { EXIT(-1); } if (((lowByte >> N_STATUS) & 1) == 0) { printf("Error. FPGA is not responding\r\n"); EXIT(-1); } int i = 0; size_t readBytes = 0; // Send the configuration file: do { readBytes = fread(buff, 1, MPSSE_PCK_SEND_SIZE, frbf); if (MPSSE_send(buff, readBytes) != FT_OK) EXIT(-1); putchar('*'); if (!((++i)%16)) printf("\r\n"); } while (readBytes == MPSSE_PCK_SEND_SIZE); printf("\r\n"); memset(buff, 0x00, sizeof(buff)); MPSSE_send(buff, 1); //        ? printf("Load complete\r\n"); // wait CONF_DONE set // A low-to-high transition on the CONF_DONE pin indicates that the configuration is // complete and initialization of the device can begin. i = 0; do { if (MPSSE_get_lbyte(&lowByte) != FT_OK) { printf ("Error read CONF_DONE\r\n"); EXIT(-1); } if (i++ > TIMEOUT_CONF_DONE) { printf ("Error CONF_DONE\r\n"); EXIT(-1); } Sleep(2); } while (((lowByte >> CONF_DONE) & 1) == 0); printf("Configuration complete\r\n"); FT_Close(ftHandle); fclose(frbf); } 

مثال على بدء برنامج:


 pen "LESO7 B" OK nConfig -> 0 nConfig -> 1 ** Load complete Configuration complete 

rbf- . . 30 / .
, - JTAG.



  1. FTDI-MPSSE-Altera PS . .
  2. . . .
  3. Software Application Development D2XX Programmer's Guide . FTDI. API D2XX.
  4. FTDI MPSSE Basics. Application Note AN_135 . . FTDI MPSSE. .
  5. Command Processor for MPSSE and MCU Host Bus Emulation Modes. Application Note AN_108 . op-code. .
  6. D2XX Drivers . FTDI.

Source: https://habr.com/ru/post/ar426131/


All Articles