الجزء 2: استخدام وحدات تحكم Cypress UDB PSoC لتقليل عدد المقاطعات في طابعة ثلاثية الأبعاد



في المرة الأخيرة ، درسنا خيار توليد نبضات لمحركات السائر ، تمت إزالته جزئيًا من البرنامج إلى مستوى البرامج الثابتة. في حالة النجاح الكامل ، يعد هذا بعدم الحاجة إلى معالجة المقاطعات التي تصل إلى تردد يصل إلى 40 كيلو هرتز. لكن هذا الخيار لديه عدد من العيوب الواضحة. أولاً ، التسارع غير معتمد هناك. ثانياً ، إن دقة ترددات الخطوة المسموح بها في هذا الحل هي مئات هرتز (على سبيل المثال ، من الممكن توليد ترددات تبلغ 40.000 هرتز و 39966 هرتز ، لكن من المستحيل توليد ترددات ذات حجم بين هاتين القيمتين).

تسريع التنفيذ


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



لن أحاول حتى معرفة ما إذا كان من الممكن تنفيذ ذلك باستخدام UDB. هذا يرجع إلى حقيقة أن هناك نوعًا آخر من التسارع في الموضة الآن: ليس شبه منحرف ، ولكن منحنى S-Curve. جدولهم يشبه هذا:



هذا بالتأكيد ليس من أجل UDB. استسلم؟ لا على الاطلاق! لقد لاحظت بالفعل أن UDB لا يقوم بتطبيق واجهة أجهزة ، لكن يسمح لك ببساطة بنقل جزء من الكود من البرنامج إلى مستوى البرنامج الثابت. دع ملف التعريف يحسب المعالج المركزي ، ولا يزال تشكيل نبضات الخطوة يؤدي UDB. المعالج المركزي لديه الكثير من الوقت لإجراء العمليات الحسابية. ستستمر مهمة إزالة الانقطاعات المتكررة بأناقة فائقة ، ولم يخطط أحد لنقل العملية تمامًا إلى مستوى البرامج الثابتة.

بطبيعة الحال ، سوف تحتاج إلى إعداد ملف التعريف في الذاكرة ، وسيقوم UDB بالتقاط البيانات من هناك باستخدام DMA. ولكن ما مقدار الذاكرة المطلوبة؟ يحتاج ملليمتر واحد إلى 200 خطوة. الآن مع ترميز 24 بت ، هذا 600 بايت لكل 1 ملم من حركة الرأس! مرة أخرى ، تذكر عدم انقطاع متكرر للغاية ، ولكن لا يزال انقطاع مستمر لنقل كل شيء في شظايا؟ ليس حقا! الحقيقة هي أن آلية DMA الخاصة بـ PSoC تعتمد على الواصفات. بعد تنفيذ المهمة من واصف واحد ، تنتقل وحدة التحكم DMA إلى التالي. وهكذا ، على طول السلسلة ، يمكنك استخدام الكثير من الواصفات. نوضح هذا مع بعض السحب من الوثائق الرسمية:



في الواقع ، يمكن أيضًا استخدام هذه الآلية عن طريق إنشاء سلسلة من ثلاثة واصفات:

رقمالتفسير
1من الذاكرة إلى FIFO مع زيادة العنوان. يشير إلى قسم بملف تعريف التسارع.
2من الذاكرة إلى FIFO دون زيادة العنوان. يرسل في كل وقت لنفس الكلمة في الذاكرة لسرعة ثابتة.
3من الذاكرة إلى FIFO مع زيادة العنوان. يشير إلى قسم بملف تعريف الكبح.

اتضح أن المسار الرئيسي موصوف في الخطوة 2 ، وهناك نفس الكلمة تستخدم فعليًا ، والتي تحدد السرعة الثابتة. استهلاك الذاكرة ليست كبيرة. في الواقع ، يمكن تمثيل الواصف الثاني فعليًا بواسطة اثنين أو ثلاثة من الواصفات. هذا يرجع إلى حقيقة أن الحد الأقصى لطول الضخ ، وفقًا لـ TRM ، يمكن أن يكون 64 كيلو بايت (سيكون التعديل أقل). هذا هو ، 32767 كلمة. هذا في 200 خطوة لكل ملليمتر سوف تتوافق مع مسار 163 ملليمتر. قد تضطر إلى إنشاء جزء من جزأين أو ثلاثة أجزاء ، اعتمادًا على المسافة القصوى التي يمكن أن يقطعها المحرك في وقت واحد.

ومع ذلك ، لحفظ الذاكرة (وحساب كتل UDB) ، أقترح التخلي عن كتل DatapPath ذات 24 بت ، والتحول إلى أكثر اقتصادا من 16 بت.

هكذا الاقتراح الأول للمراجعة.

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

غرامة لحن المدى المتوسط


الآن سوف ننظر في كيفية التغلب على مشكلة حبيبات التردد. بالطبع ، لن يكون من الممكن ضبطه بالضبط. ولكن ، في الواقع ، فإن "البرامج الثابتة" الأصلي أيضا لا يمكن أن تفعل هذا. بدلاً من ذلك ، يستخدمون خوارزمية Bresenham. يتم إضافة تأخير قياس واحد إلى بعض الخطوات. نتيجة لذلك ، يصبح متوسط ​​التردد متوسط ​​، بين قيمة أصغر وأكبر. عن طريق ضبط نسبة الفترات العادية والممتدة ، يمكنك تغيير متوسط ​​التردد بسلاسة. إذا لم يتم ضبط سرعتنا الآن من خلال سجل البيانات ، ولكن يتم نقلها عبر FIFO ، ويتم تعيين عدد النبضات عمومًا من خلال عدد الكلمات المرسلة عبر DMA ، يتم تحرير كلا سجلات البيانات في UDB. بالإضافة إلى ذلك ، يتم أيضًا إصدار إحدى البطاريات ، التي تعد عدد النبضات. هنا سوف نبني بعض PWM عليها.

عادة ، ALUs مقارنة وتعيين السجلات مع نفس الفهرس. عندما يحتوي أحد السجلات على فهرس 0 والآخر يحتوي على 1 ، لا يمكن تنفيذ كل إصدار من العملية. لكنني تمكنت من تجميع مآس من السجلات التي يمكن من خلالها القيام PWM. اتضح كما هو موضح في الشكل.



عند استيفاء الشرط A0 <D1 ، سنضيف إيقاعًا إضافيًا لطول النبض المحدد. عندما لا يتم الوفاء الشرط ، ونحن لن.

حصان كروي في ظل ظروف طبيعية


لذلك ، نبدأ في تعديل الكتلة المطورة لـ UDB ، مع مراعاة الهيكل الجديد. استبدال عمق بت Datapath:



سنحتاج إلى الكثير من عمليات الخروج من Datapath أكثر من المرة الأخيرة.



النقر المزدوج عليها ، ونحن نرى التفاصيل:



هناك المزيد من الأرقام للمتغير الدولة ، لا تنسى أن تتصل القديم! في الإصدار القديم ، كان هناك 0 ثابت.



الرسم البياني انتقال الأوتوماتون حصلت على هذا النحو:



نحن في حالة الخمول بينما FIFO1 فارغ. بالمناسبة ، العمل مع FIFO1 وليس FIFO0 هو نتيجة لتشكيل سوليتير. يستخدم السجل A0 لتنفيذ PWM ، لذلك يتم تحديد عرض النبضة بواسطة السجل A1. ولا يمكنني تنزيله إلا من FIFO1 (ربما هناك طرق سرية أخرى ، ولكنها غير معروفة لي). لذلك ، تقوم DMA بتحميل البيانات إلى FIFO1 تمامًا ، وهي بالتحديد الحالة "غير الفارغة" لـ FIFO1 التي تخرج من حالة الخمول .

ALU في حالة IDLE يبطل التسجيل A0:



يعد ذلك ضروريًا حتى يبدأ تشغيل العمل دائمًا من البداية عند بدء تشغيل PWM.
لكن البيانات دخلت في FIFO. ينتقل الجهاز إلى حالة LoadData :



في هذه الحالة ، تقوم ALU بتحميل الكلمة التالية من FIFO في تسجيل A1. على طول الطريق ، من أجل عدم إنشاء حالات غير ضرورية ، يتم زيادة قيمة العداد A0 ، والتي تستخدم للعمل مع PWM ،:



إذا لم تصل العداد A0 بعد إلى القيمة D0 (أي ، يتم تشغيل الشرط A0 <D0 ، مما يؤدي إلى ظهور علامة NoNeedReloadA0) ، فإننا ننتقل إلى الحالة الواحدة . خلاف ذلك ، فإن الدولة هي ClearA0 .

في حالة ClearA0 ، تقوم ALU ببساطة بتحديد قيمة A0 ، وبدء دورة PWM جديدة:



بعدها يذهب الجهاز أيضًا إلى حالة واحدة ، وفاز واحد فقط لاحقًا.

واحد مألوف بالنسبة لنا من الإصدار القديم من الجهاز. ALU فيه لا يؤدي أي وظائف.

وهكذا - في هذه الحالة ، يتم إنشاء وحدة عند إخراج Out_Step (هنا كان المحسن يعمل بشكل أفضل عندما يتم إنتاج الوحدة حسب الحالة ، وقد تم اكتشاف ذلك بشكل تجريبي).



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



سوف ندخل في حالة ExtraTick إذا تم تعيين علامة AddCycle ، والتي تم تعيينها للوفاء بالشرط A0 <D1. في هذه الحالة ، لا تقوم ALU بأي إجراءات مفيدة. إنها فقط تستغرق دورة واحدة للفوز لفترة أطول. علاوة على ذلك ، تتلاقى جميع المسارات في حالة التأخير .

هذا الشرط يقيس مدة النبض. يتم تقليل سجل A1 (يتم تحميله أثناء تحميله في حالة التحميل ) حتى يصل إلى الصفر.



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



يخرج الآن من UDB. لقد قمت بتحويل علامة الوجود في حالة الخمول إلى مقارنة غير متزامنة (في الإصدار السابق كان هناك مشغل تم ضبطه وإعادة تعيينه في حالات مختلفة) ، نظرًا لأن المحسن أظهر أفضل نتيجة. بالإضافة إلى ذلك ، تمت إضافة علم Hungry ، مما يشير إلى وحدة DMA بأنها مستعدة لاستقبال البيانات. انتهى الأمر على العلم "FIFO1 ليست مزدحمة" . نظرًا لأنه غير مزدحم ، يمكن لـ DMA تحميل كلمة بيانات أخرى هناك.



على الجزء التلقائي - هذا كل شيء.

إضافة كتل DMA إلى مخطط المشروع الرئيسي. في الوقت الحالي ، بدأت في مقاطعة إشارات إنهاء DMA ، ولكن ليس حقيقة أن هذا صحيح. عند اكتمال عملية الوصول المباشر إلى الذاكرة ، يمكنك بدء عملية جديدة تتعلق بالجزء نفسه ، ولكن لا يمكنك البدء في ملء معلومات حول القطاع الجديد. FIFO لا يزال لديه ثلاثة إلى أربعة عناصر. في هذا الوقت ، لا يزال من المستحيل إعادة برمجة السجلات D0 و D1 للكتلة القائمة على UDB ، لا تزال هناك حاجة للتشغيل. لذلك ، من الممكن أن تتم إضافة المقاطعات بناءً على مخرجات Out_Idle لاحقًا . لكن هذا المطبخ لم يعد متعلقًا ببرمجة UDB block ، لذلك سنذكره فقط.



تجارب البرمجيات


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

int main(void) { CyGlobalIntEnable; /* Enable global interrupts. */ // isr_1_StartEx(StepperFinished); StepperController_X_Start(); StepperController_Y_Start(); StepperController_Z_Start(); StepperController_E0_Start(); StepperController_E1_Start(); // TestShortSteps(); TestWithPacking (); for(;;) { } 

دعونا نحاول إرسال حزمة من النبضات عن طريق استدعاء وظيفة ، والتحقق من حقيقة إدخال نبض إضافي. استدعاء الوظيفة بسيط:

 TestShortSteps(); 

لكن الجسم يحتاج الى تفسير.
سأقدم الوظيفة بأكملها أولاً
 void TestShortSteps() { //   ,   //      //   ,  DMA    !!! //    ,   !!! StepperController_X_SingleVibrator_WritePeriod (6); //     //    —   CY_SET_REG16(StepperController_X_Datapath_1_D0_PTR, 4); CY_SET_REG16(StepperController_X_Datapath_1_D1_PTR, 2); //         . //         static const uint16 steps[] = { 0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001, 0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001 }; //  DMA  ,      uint8 channel = DMA_X_DmaInitialize (sizeof(steps[0]),1,HI16(steps),HI16(StepperController_X_Datapath_1_F1_PTR)); CyDmaChRoundRobin (channel,true); //       ,       uint8 td = CyDmaTdAllocate(); //       .  ,    . CyDmaTdSetConfiguration(td, sizeof(steps), CY_DMA_DISABLE_TD, TD_INC_SRC_ADR | TD_AUTO_EXEC_NEXT); //       CyDmaTdSetAddress(td, LO16((uint32)steps), LO16((uint32)StepperController_X_Datapath_1_F1_PTR)); //      CyDmaChSetInitialTd(channel, td); //         CyDmaChEnable(channel, 1); } 


الآن النظر في الأجزاء المهمة.

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

  //   ,   //      //   ,  DMA    !!! //    ,   !!! StepperController_X_SingleVibrator_WritePeriod (6); 

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

استنشاق الكمون


فكرة الإصلاح الأخرى التي تكمن في رأسي هي تسريع الأجهزة لوظائف معينة من نواة RTOS MAX. ولكن للأسف ، كل ما عندي من الأفكار مفككة عن تلك الاختلافات نفسها.

كانت هناك حالة ، درست تطوير تطبيقات Bare Metal لـ Cyclone V SoC. لكن اتضح أن العمل مع سجلات FPGA واحدة (عند الكتابة بالتناوب عليها ، ثم القراءة منها) يقلل من العملية الأساسية مئات (!!!) مرات. سمعت صحيح. إنه بالمئات. علاوة على ذلك ، كل هذا غير موثق بشكل سيئ ، لكن في البداية شعرت بالداخل ، وبعد ذلك أثبتت من قصاصات من العبارات من الوثائق أن الكمون كانت مذنبة عند تمرير الطلبات عبر مجموعة من الجسور. إذا كنت بحاجة لطرد مجموعة كبيرة ، فسيكون هناك أيضًا زمن استتار ، لكن فيما يتعلق بكلمة واحدة تم ضخها ، فلن تكون مهمة. عندما تكون الطلبات مفردة (ويتضمن تسريع الأجهزة الخاص بـ kernel OS فقط هذه الطلبات) ، فإن التباطؤ يمر لمئات المرات بالضبط. سيكون من الأسرع بكثير القيام بكل شيء بطريقة برنامجية بحتة ، عندما يعمل البرنامج مع الذاكرة الرئيسية من خلال ذاكرة التخزين المؤقت بسرعة محمومة.

في PSoC ، كان لدي أيضًا خطط معينة. في المظهر ، يمكنك البحث عن بيانات رائعة في صفيف باستخدام DMA و UDB. ما هو حقا هناك! بسبب بنية واصف DMA ، يمكن لوحدات التحكم هذه إجراء بحث كامل عن الأجهزة في القوائم المرتبطة! ولكن بعد تلقي القابس الموصوف أعلاه ، أدركت أنه يرتبط أيضًا بالاختفاء. هنا ، يتم وصف هذا الكمون بشكل جميل في الوثائق. سواء في العائلة TRM أو في وثيقة منفصلة AN84810 - PSoC 3 و PSoC 5LP Advanced DMA Topics . هناك قسم 3.2 مكرس لهذا الغرض. لذلك يتم إلغاء تسريع الجهاز التالي. من المؤسف. ولكن ، كما قال سيميون سيميونوفيتش غوربونكوف: "سنبحث".

تجارب البرامج المستمرة


بعد ذلك ، قمت بتعيين معلمات خوارزمية Bresenham:

  //     //    —   CY_SET_REG16(StepperController_X_Datapath_1_D0_PTR, 4); CY_SET_REG16(StepperController_X_Datapath_1_D1_PTR, 2); 

حسنًا ، يأتي بعد ذلك الكود العادي الذي ينقل مجموعة من الكلمات من خلال DMA إلى FIFO1 من وحدة التحكم في المحرك X.

النتيجة تتطلب بعض التفسير. ومن هنا:



تظهر قيمة العداد A0 باللون الأحمر عندما يكون الجهاز في حالة واحدة . تُظهر العلامة النجمية الخضراء حالات عند إدراج التأخير نظرًا لوجود الجهاز في حالة ExtraTick . هناك أيضًا أشرطة حيث يكون التأخير بسبب حالة ClearA0 ، ويتم تمييزها بشبكة زرقاء.

كما ترون ، عند إدخال لأول مرة يتم فقد التأخير الأول. هذا يرجع إلى حقيقة أن A0 تتم إعادة ضبطه عندما يكون في وضع الخمول ، ولكنه يزيد عندما يدخل LoadData . لذلك ، إلى حد التحليل (الخروج من حالة واحد ) ، هو بالفعل مساوٍ للوحدة. الحساب يبدأ معها. ولكن بشكل عام ، هذا لن يؤثر على التردد المتوسط. انها تحتاج فقط أن تبقى في الاعتبار. كما يجب أن يؤخذ في الاعتبار أنه عند إعادة تعيين A0 ، سيتم أيضًا إدراج الساعة. يجب أن يؤخذ في الاعتبار عند حساب متوسط ​​التردد.

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

تتكون من مرحلة التسارع ، الحركة الخطية والكبح.
 void TestWithPacking(int countOnLinearStage) { //   ,   //     . //   ,  DMA    !!! //    ,   !!! StepperController_X_SingleVibrator_WritePeriod (6); //     //    —   CY_SET_REG16(StepperController_X_Datapath_1_D0_PTR, 4); CY_SET_REG16(StepperController_X_Datapath_1_D1_PTR, 2); //    static const uint16 accelerate[] = {0x0010,0x0008,0x0004}; //    static const uint16 deccelerate[] = {0x004,0x0008,0x0010}; //  .    . static const uint16 steps[] = {0x0001}; //  DMA  ,      uint8 channel = DMA_X_DmaInitialize (sizeof(steps[0]),1,HI16(steps),HI16(StepperController_X_Datapath_1_F1_PTR)); CyDmaChRoundRobin (channel,true); //   uint8 tdDeccelerate = CyDmaTdAllocate(); CyDmaTdSetConfiguration(tdDeccelerate, sizeof(deccelerate), CY_DMA_DISABLE_TD, TD_INC_SRC_ADR | TD_AUTO_EXEC_NEXT); CyDmaTdSetAddress(tdDeccelerate, LO16((uint32)deccelerate), LO16((uint32)StepperController_X_Datapath_1_F1_PTR)); //       uint8 tdSteps = CyDmaTdAllocate(); //   !!! //     !!! CyDmaTdSetConfiguration(tdSteps, countOnLinearStage, tdDeccelerate, /*TD_INC_SRC_ADR |*/ TD_AUTO_EXEC_NEXT); CyDmaTdSetAddress(tdSteps, LO16((uint32)steps), LO16((uint32)StepperController_X_Datapath_1_F1_PTR)); //   //     !!! uint8 tdAccelerate = CyDmaTdAllocate(); CyDmaTdSetConfiguration(tdAccelerate, sizeof(accelerate), tdSteps, TD_INC_SRC_ADR | TD_AUTO_EXEC_NEXT); CyDmaTdSetAddress(tdAccelerate, LO16((uint32)accelerate), LO16((uint32)StepperController_X_Datapath_1_F1_PTR)); //      CyDmaChSetInitialTd(channel, tdAccelerate); //         CyDmaChEnable(channel, 1); } 


أولاً ، اطلب الخطوات العشر نفسها (في DMA ، انتقل 20 بايت بالفعل):

 TestWithPacking (20); 

والنتيجة كما هو متوقع. في البداية ، تسارع مرئي. ويحدث المخرج إلى IDLE (بلو راي) بتأخير كبير من النبضة الأخيرة ، ثم تم الانتهاء من الخطوة الأخيرة تمامًا ، وقيمتها تساوي تقريبًا قيمة الأولى.



الحصان الحقيقي في ظل الظروف العادية


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

لدينا بطارية 16 بت. قررت إضافة الكيان القياسي المكون من سبع بت إلى وحدات البت العالية. ما هو هذا عداد سبعة بت؟ هذا هو التصميم المتوفر في كل فدرة UDB (تحتوي فدرة UDB الأساسية على عرض بت لجميع سجلات 8 بت ، ويتم تحديد الزيادة في عمق البتة من خلال مجموعة الكتل في المجموعات). من نفس الموارد ، يمكن تنفيذ سجلات التحكم / الحالة . الآن لدينا عداد واحد وليس زوج تحكم / حالة واحد لـ 16 بت من البيانات. لذلك ، بإضافة عداد آخر للنظام ، لن نقوم بتأخير الموارد الإضافية. نحن فقط نأخذ ما تم تخصيصه بالفعل لنا. هذا لطيف! نصنع البايت العالي لعداد عرض النبضة من خلال هذه الآلية ونحصل على العرض الكلي لعداد عرض النبضة يساوي 23 بت.



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

علاوة على ذلك ، فإن الوثائق الخاصة بهذا العداد تقول إنني على حق. حرفيا تقول:
الفترة
يحدد قيمة تسجيل الفترة الأولية. لفترة من الساعات N ، يجب ضبط قيمة الفترة على قيمة N-1. سيتم عد العداد من N-1 إلى 0 والذي ينتج عنه فترة دورة على مدار الساعة N. لا يتم دعم قيمة سجل الفترة البالغة 0 وستؤدي إلى إخراج عدد المحطات الطرفية في حالة عالية ثابتة.
لقد أظهرت الحياة أن كل شيء مختلف. استنتجت حالة خط العد الطرفي على راسم الذبذبات وشاهدت قيمته عند صفر تم تحميله مسبقًا في الفترة وأثناء تحميل البرنامج. للأسف وآه. لم يكن هناك حالة عالية ثابتة !

من خلال التجربة والخطأ ، تمكنت من جعل النظام يعمل بشكل صحيح ، ولكن لكي يحدث هذا ، يجب أن يحدث عملية طرح واحدة على الأقل من العداد! الحالة الجديدة من "الطرح" ليست على جانب. كان لا بد من ربطها في المسار المطلوب. يقع أمام ولاية التأخير ويسمى Next65536 .



لا تقوم ALU في هذه الحالة بأي إجراءات مفيدة. في الواقع ، لا يتفاعل سوى عداد جديد مع حقيقة كونك في هذه الحالة. ومن هنا في الرسم البياني:



فيما يلي خصائصه بمزيد من التفاصيل:



بشكل عام ، مع مراعاة المقالات السابقة ، فإن جوهر هذا العداد واضح. فقط خط التمكين هو المعاناة. مرة أخرى ، لا أفهم تمامًا سبب تشغيله عندما يكون الجهاز في حالة LoadData (ثم ​​يعيد العداد إعادة تحميل قيمة الفترة). لقد استعرت هذه الخدعة من خصائص العداد التي تتحكم في المصابيح ، مأخوذة من المؤلف الإنجليزي لوحدة التحكم لتلك المصابيح. بدونها ، لا تعمل قيمة الفترة صفر. هي تعمل معها.

في رمز واجهة برمجة التطبيقات ، نضيف تهيئة عداد جديد. تبدو وظيفة البدء الآن كما يلي:

 void `$INSTANCE_NAME`_Start() { `$INSTANCE_NAME`_SingleVibrator_Start(); //"One" Generator start `$INSTANCE_NAME`_Plus65536_Start(); } 

دعنا تحقق من النظام الجديد. هنا هو رمز الوظيفة للاختبار

(فيه ، السطر الأول فقط يختلف عن الخط المعروف بالفعل):
 void JustTest(int extra65536s) { //      65536  StepperController_X_Plus65536_WritePeriod((uint8) extra65536s); //     //    —   CY_SET_REG16(StepperController_X_Datapath_1_D0_PTR, 4); CY_SET_REG16(StepperController_X_Datapath_1_D1_PTR, 2); //         . //         static const uint16 steps[] = { 0x1000,0x1000,0x1000,0x1000 }; //  DMA  ,      uint8 channel = DMA_X_DmaInitialize (sizeof(steps[0]),1,HI16(steps),HI16(StepperController_X_Datapath_1_F1_PTR)); CyDmaChRoundRobin (channel,true); //       ,       uint8 td = CyDmaTdAllocate(); //       .  ,    . CyDmaTdSetConfiguration(td, sizeof(steps), CY_DMA_DISABLE_TD, TD_INC_SRC_ADR | TD_AUTO_EXEC_NEXT); //       CyDmaTdSetAddress(td, LO16((uint32)steps), LO16((uint32)StepperController_X_Datapath_1_F1_PTR)); //      CyDmaChSetInitialTd(channel, td); //         CyDmaChEnable(channel, 1); } 


نحن نسميها مثل هذا:

  JustTest(0); 

على الذبذبات نرى ما يلي (شعاع أصفر - خرج الخطوة ، أزرق - قيمة خرج العداد TC للتحكم في العملية). يتم تعيين مدة النبض بخطوات الصفيف. في كل خطوة ، تكون المدة هي مقاييس 0x1000.



قم بالتبديل إلى فحص آخر بحيث يكون هناك توافق بين النتائج المختلفة:



قم بتغيير استدعاء الوظيفة إلى هذا:

  JustTest(1); 

والنتيجة كما هو متوقع. أولاً ، يكون خرج TC صفرًا بالنسبة إلى دورات 0x1000 ، ثم - وحدة للدورات 0x10000 (65536d). التردد يساوي 700 هيرتز تقريبًا ، اكتشفنا في الجزء الأخير من المقالة ، لذلك كل شيء على ما يرام.



حسنًا ، دعنا نجرب الشيطان:

  JustTest(2); 

نحصل على:



هذا صحيح. يتم قلب الإخراج TC إلى واحد على دورات الساعة 65536 مشاركة. قبل ذلك ، كان عند مستوى الصفر لدورات 0x1000 + 0x10000.

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

يطير في مرهم


تنص وثيقة TRM لعائلة PSoC5LP على ما يلي:
يمكن أن تكون كل معاملة من 1 إلى 64 كيلو بايت
ولكن في AN84810 التي سبق ذكرها ، هناك مثل هذه العبارة:
1. كيف يمكنك تخزين أكثر من 4095 بايت باستخدام DMA؟
الحد الأقصى لعدد نقل TD يقتصر على 4095 بايت. إذا كنت بحاجة إلى نقل أكثر من 4095 بايت باستخدام قناة DMA واحدة ، فاستخدم عدة TDs وسلسلها كما هو موضح في المثال 5.
من هو الصحيح؟ إذا أجريت تجارب ، فستتميل النتائج لصالح أسوأ العبارات ، لكن السلوك سيكون غير مفهوم تمامًا. الخطأ كله هو هذا الاختيار في API:



نفس النص.
 cystatus CyDmaTdSetConfiguration(uint8 tdHandle, uint16 transferCount, uint8 nextTd, uint8 configuration) \ { cystatus status = CYRET_BAD_PARAM; if((tdHandle < CY_DMA_NUMBEROF_TDS) && (0u == (0xF000u & transferCount))) { /* Set 12 bits transfer count. */ reg16 *convert = (reg16 *) &CY_DMA_TDMEM_STRUCT_PTR[tdHandle].TD0[0u]; CY_SET_REG16(convert, transferCount); /* Set Next TD pointer. */ CY_DMA_TDMEM_STRUCT_PTR[tdHandle].TD0[2u] = nextTd; /* Configure the TD */ CY_DMA_TDMEM_STRUCT_PTR[tdHandle].TD0[3u] = configuration; status = CYRET_SUCCESS; } return(status); } 


إذا تم تحديد معاملة أطول من 4095 بايت ، فسيتم استخدام الإعداد السابق. نعم ، لم أفكر في التحقق من رموز الخطأ ...

أظهرت التجارب أنه إذا قمت بإزالة هذا الفحص ، فسيتم قطع الطول الفعلي باستخدام القناع 0xfff (4096 40 = 0x1000). للأسف وآه. انهارت جميع الآمال للحصول على وظيفة ممتعة. يمكنك ، بالطبع ، عمل سلاسل من الواصفات ذات الصلة في 4K. ولكن ، قل ، 64 كيلو بايت 16 سلاسل. ثلاثة محركات نشطة (البثق سيكون له خطوات أقل) - 48 سلسلة. بالضبط يجب ملء الكثير في أسوأ الحالات ، قبل كل قطعة. ربما هو مقبول في الوقت المناسب. يتوفر 127 واصفا كحد أدنى ، لذلك سيكون هناك بالتأكيد ذاكرة كافية.

يمكنك إرسال البيانات المفقودة حسب الحاجة. حدث انقطاع في أن قناة DMA قد أكملت العمل ، فنحن ننقل شريحة أخرى إليها. في هذه الحالة ، لا يلزم إجراء عمليات حسابية ، يتم تكوين القطاع بالفعل ، وسيكون كل شيء سريعًا. ولا توجد متطلبات للأداء: عند إصدار طلب مقاطعة ، سيكون هناك 4 عناصر إضافية في FIFO سيتم تقديم الخدمات لكل منها لعدة مئات أو حتى آلاف دورات الساعة. وهذا هو ، كل شيء حقيقي. ستكون الإستراتيجية المحددة أسهل في الاختيار أثناء العمل الحقيقي. لكن خطأ في الوثائق (TRM) أفسد المزاج كله. إذا كان هذا معروفًا مسبقًا ، فربما لم أكن لأتحقق من المنهجية.

الخاتمة


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

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

حدث خطأ في وثائق وحدة التحكم DMA في عدم وضوح النتيجة. ومع ذلك ، فإن الانقطاعات مطلوبة ، ولكن ليس بنفس التردد وليس مع الأهمية الزمنية التي كانت في الإصدار الأصلي. لذا فإن الحالة المزاجية تفسد ، لكن استخدام "المعالج الثانوي" على أساس UDB لا يزال يوفر مكسبًا كبيرًا مقارنةً بعمل البرامج المحض.

على طول الطريق ، تم الكشف عن أن DMA تعمل بسرعة منخفضة إلى حد ما. نتيجة لذلك ، أجريت بعض القياسات على كل من PSoC5LP وعلى STM32. النتائج سحب مقال آخر. ربما سأفعل ذلك يومًا ما إذا كان الموضوع مثيرًا للاهتمام.

نتيجة للتجارب ، تم الحصول على مشروعين للاختبار في وقت واحد. الأول أسهل للفهم. يمكنك أن تأخذ هنا . الموروثة الثانية من الأولى ، لكن في حيرة من أمرها عند إضافة عداد من 7 بت والمنطق المرتبط. يمكنك أن تأخذ هنا . بالطبع ، هذه الأمثلة ليست سوى أمثلة اختبار. لا يوجد وقت فراغ حتى الآن لتضمين "البرامج الثابتة" الحقيقية. ولكن في إطار هذه المقالات ، من المهم أكثر ممارسة العمل مع UDB.

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


All Articles