لماذا اردوينو بطيء جدا وماذا يمكن فعله حيال ذلك

الشعار


ذات مرة صادفت مقالة ممتازة ( tyk ) - أظهر فيها المؤلف بوضوح الفرق بين استخدام وظائف Arduino والعمل مع السجلات. تمت كتابة الكثير من المقالات ، سواء تمدح أردوينو ويجادل بأن هذا كله تافه وعمومًا للأطفال ، لذلك لن نكرر ذلك ، ولكن حاول معرفة سبب النتائج التي حصل عليها مؤلف هذه المقالة. وبنفس القدر من الأهمية ، سنفكر فيما يمكننا القيام به. أي شخص مهتم ، أسأل تحت القط.


الجزء 1 "الأسئلة"


نقلاً عن مؤلف هذا المقال:


اتضح فقدان الأداء في هذه الحالة - 28 مرة. بالطبع ، هذا لا يعني أن اردوينو يعمل بشكل أبطأ 28 مرة ، لكن أعتقد أنه من أجل الوضوح ، هذا هو أفضل مثال على ما لا يعجبه أردوينو.

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


سنكتب برنامجًا بسيطًا لـ arduino (في الواقع ، مجرد نسخ وميض).


void setup() { pinMode(13, OUTPUT); } void loop() { digitalWrite(13, 1); // turn the LED on (HIGH is the voltage level) digitalWrite(13, 0); // turn the LED off by making the voltage LOW } 

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


Test_1


... لفترة طويلة. نحن نحاول أن نتذكر ما تعتمد عليه سرعة وحدة التحكم - بالضبط ، التردد. نحن ننظر في arduino.cc . سرعة الساعة هي 16 ميجاهرتز ، وهنا لدينا 145.5 كيلوهرتز. ماذا تفعل دعونا نحاول حلها في الجبين. على نفس موقع arduino.cc ننظر إلى باقي اللوحة:


  • ليوناردو - غير مناسب - يوجد أيضًا 16 ميجاهرتز
  • ميجا - أيضا - 16 ميجا هرتز
  • 101 - ستفعل - 32 ميجا هرتز
  • DUE - حتى أفضل - 84 ميجاهرتز

يمكن افتراض أنه إذا قمت بزيادة تردد وحدة التحكم مرتين ، فإن تردد وميض LED سيزداد أيضًا مرتين ، وإذا كان بمقدار 5 ، ثم 5 مرات.


Test_2


لم نحصل على النتائج المرجوة. والمولد أقل وأقل مثل المتعرج. نفكر أكثر - الآن ، على الأرجح ، اللغة سيئة. يبدو أنه مع c ، c ++ ، ولكنه صعب (وفقًا لتأثير Dunning-Krueger ، لا يمكننا أن ندرك أننا نكتب بالفعل في c ++) ، لذلك نحن نبحث عن بدائل. يقودنا بحث قصير إلى BASCOM-AVR (لم يتم إخبارنا عنها هنا ) ، قم بتعيين ، اكتب الرمز:


 $Regfile="m328pdef.dat" $Crystal=16000000 Config Portb.5 = Output Do Toggle Portb.5 Loop 

نحصل على:


Test_3


والنتيجة أفضل بكثير ، بالإضافة إلى أننا حصلنا على المتعرج المثالي ، ولكن ... BASIC في 2018 ، على محمل الجد؟ ربما سنترك هذا في الماضي.


الجزء 2 "الإجابات"


يبدو أن الوقت قد حان للتوقف عن لعب الأحمق والبدء في الفهم (وتذكر أيضًا si و المجمع). ما عليك سوى نسخ الرمز "المفيد" من المقالة المذكورة في البداية إلى الحلقة ().


هنا ، أعتقد ، هناك حاجة إلى تفسير: سيتم كتابة جميع التعليمات البرمجية في مشروع اردوينو ، ولكن في بيئة Atmel Studio 7.0 (يوجد أداة تفكيك ملائمة هناك) ، ستكون لقطات الشاشة منه.


 void setup() { DDRB |= (1 << 5); // PB5 } void loop() { PORTB &= ~(1 << 5); //OFF PORTB |= (1 << 5); //ON } 

النتيجة:


اختبار 4


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


Test_5


ويرجع ذلك إلى تشغيل المقاطعات من المؤقت المسؤول عن المللي (). لذا ما سنفعله هو ببساطة قطع الاتصال. نحن نبحث عن ISR (وظيفة معالج المقاطعة). نجد:


 ISR(TIMER0_OVF_vect) { // copy these to local variables so they can be stored in registers // (volatile variables must be read from memory on every access) unsigned long m = timer0_millis; nsigned char f = timer0_fract; m += MILLIS_INC; f += FRACT_INC; if (f >= FRACT_MAX) { f -= FRACT_MAX; m += 1; } timer0_fract = f; timer0_millis = m; timer0_overflow_count++; } 

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


 PORTB &= ~(1 << 5); //OFF PORTB |= (1 << 5); //ON 

عدد كبير جدًا من عوامل التشغيل ، يتم تقليلها إلى مهمة واحدة.


 PORTB = 0b00000000; //OFF PORTB = 0b11111111; //ON 

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


 int main(void) { init(); // ... setup(); for (;;) { loop(); if (serialEventRun) serialEventRun(); } return 0; } 

لذا قم بعمل حلقة لا نهائية في الإعداد (). نحصل على ما يلي:


 void setup() { cli(); DDRB |= (1 << 5); // PB5 while (1) { PORTB = 0b00000000; //OFF PORTB = 0b11111111; //ON } } 

Test_6


61 نانوثانية هو الحد الأقصى المطابق لتردد وحدة التحكم. هل ممكن أسرع؟ المفسد - لا. دعونا نحاول أن نفهم لماذا - لهذا نقوم بتفكيك الكود الخاص بنا:


Code_asm_1


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


 void setup() { cli(); DDRB |= (1 << 5); // PB5 while (1) { PORTB = 0b00000000; //OFF asm("nop"); asm("nop"); PORTB = 0b11111111; //ON } } 

Test_end


الجزء 3 "الاستنتاجات"


لسوء الحظ ، كل ما فعلناه كان عديم الفائدة على الإطلاق من الناحية العملية ، لأنه لم يعد بإمكاننا تنفيذ أي رمز. أيضًا ، في 99.9٪ من الحالات ، تكون ترددات تبديل المنفذ كافية لأي غرض. نعم ، وإذا كنا بحاجة حقًا إلى إنشاء متعرج سلس ، فيمكنك أن تأخذ stm32 مع dma أو رقاقة توقيت خارجية مثل NE555. هذه المقالة مفيدة لفهم أجهزة mega328p و arduino بشكل عام.


ومع ذلك ، الكتابة في سجلات قيم 8 بت PORTB = 0b11111111; أسرع بكثير من digitalWrite(13, 1); ولكن سيتعين عليك الدفع مقابل ذلك من خلال عدم القدرة على نقل الرمز إلى لوحات أخرى ، لأن أسماء السجلات قد تختلف.


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


استشهد المنشور بمقالات:



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


All Articles