في هذه المقالة ، أود أن أتحدث عن ميزات تنفيذ واجهة مستخدم رسومية مع عناصر واجهة مستخدم على متحكم دقيق وكيفية الحصول على واجهة مستخدم مألوفة و FPS لائق. لا أرغب في التركيز على أي مكتبة رسومات محددة ، ولكن على أشياء عامة - الذاكرة ، وذاكرة التخزين المؤقت للمعالج ، و dma ، وما إلى ذلك. نظرًا لأنني مطور لفريق Embox ، فستكون الأمثلة والتجارب على نظام التشغيل RT OS هذا.
تحدثنا سابقًا عن تشغيل مكتبة Qt على متحكم دقيق . تبين أن الرسوم المتحركة كانت سلسة للغاية ، لكن تكاليف الذاكرة حتى لتخزين البرامج الثابتة كانت كبيرة - تم تنفيذ الكود من ذاكرة فلاش QSPI الخارجية. بالطبع ، عندما تكون هناك حاجة إلى واجهة معقدة ومتعددة الوظائف ، والتي تعرف أيضًا كيفية القيام بنوع من الرسوم المتحركة ، فإن تكلفة موارد الأجهزة يمكن أن تكون مبررة تمامًا (خاصة إذا كان لديك بالفعل هذا الرمز تم تطويره لـ Qt).
ولكن ماذا لو لم تكن بحاجة إلى جميع وظائف Qt؟ ماذا لو كان لديك أربعة أزرار والتحكم في مستوى الصوت واثنين من القوائم المنبثقة؟ في نفس الوقت ، أريد أن "تبدو جميلة وتعمل بسرعة" :) ثم سيكون من المستحسن استخدام المزيد من الأدوات الخفيفة ، على سبيل المثال ، مكتبة lvgl أو ما شابه.
في مشروع Embox الخاص بنا ، تم نقل Nuklear منذ بعض الوقت - وهو مشروع لإنشاء مكتبة خفيفة الوزن للغاية تتكون من رأس واحد ويسمح لك بإنشاء واجهة مستخدم رسومية بسيطة بسهولة. قررنا استخدامه لإنشاء تطبيق صغير حيث سيكون هناك عنصر واجهة مستخدم مع مجموعة من العناصر الرسومية والتي يمكن التحكم فيها عبر شاشة تعمل باللمس.
تم اختيار STM32F7-Discovery مع Cortex-M7 وشاشة تعمل باللمس كمنصة.
التحسينات الأولى. احفظ الذاكرة
لذلك ، تم تحديد مكتبة الرسومات ، وكذلك النظام الأساسي. الآن دعونا نفهم ما هي الموارد. ومن الجدير بالذكر هنا أن الذاكرة الرئيسية SRAM أسرع بعدة مرات من الذاكرة الخارجية SDRAM ، لذلك إذا كانت أحجام الشاشة تسمح لك ، فمن الأفضل بالطبع وضع حاجز الإطارات في SRAM. تبلغ دقة شاشتنا 480x272. إذا أردنا لونًا يبلغ 4 بايت لكل بكسل ، فسنحصل على حوالي 512 كيلوبايت. في نفس الوقت حجم ذاكرة الوصول العشوائي الداخلية 320 فقط ويتضح على الفور أن ذاكرة الفيديو ستكون خارجية. خيار آخر هو تقليل عمق بت اللون إلى 16 (أي 2 بايت) ، وبالتالي تقليل استهلاك الذاكرة إلى 256 كيلوبايت ، والتي يمكن أن تتناسب بالفعل مع ذاكرة الوصول العشوائي الرئيسية.
أول شيء يمكنك تجربته هو التوفير في كل شيء. لنصنع مخزنًا مؤقتًا للفيديو بسعة 256 كيلو بايت ، ونضعه في ذاكرة الوصول العشوائي ونرسمه. كانت المشكلة التي واجهناها على الفور هي "وميض" المشهد الذي يحدث عند الرسم مباشرة في ذاكرة الفيديو. تعيد Nuklear رسم المشهد بالكامل من البداية ، لذلك في كل مرة يتم فيها ملء الشاشة بالكامل أولاً ، يتم رسم الأداة ، ثم يتم وضع زر فيها ، حيث يتم وضع النص ، وهكذا. نتيجة لذلك ، يمكن للعين المجردة أن ترى كيف يتم إعادة رسم المشهد بأكمله و "يومض" الصورة. أي أن وضعًا بسيطًا في الذاكرة الداخلية لا يحفظ.
وسيطة عازلة. تحسينات المترجم. FPU
بعد أن عبثنا بالطريقة السابقة (التنسيب في الذاكرة الداخلية) قليلاً ، بدأت ذكريات X Server و Wayland في الظهور على الفور. نعم ، في الواقع ، يشارك مديرو النوافذ في معالجة الطلبات الواردة من العملاء (فقط تطبيقنا المخصص) ، ثم تجميع العناصر في المشهد النهائي. على سبيل المثال ، يرسل Linux kernel الأحداث من أجهزة الإدخال إلى الخادم من خلال برنامج تشغيل evdev. الخادم ، بدوره ، يحدد العميل الذي سيتعامل مع الحدث. العملاء ، بعد تلقي حدث (على سبيل المثال ، الضغط على شاشة تعمل باللمس) ، ينفذون منطقهم الداخلي - يقومون بتمييز الزر ، وعرض قائمة جديدة. علاوة على ذلك (يختلف قليلاً بالنسبة لـ X و Wayland) إما أن العميل نفسه أو الخادم يرسم التغييرات إلى المخزن المؤقت. ثم يقوم المؤلف بتجميع كل القطع معًا للرسم على الشاشة.شرح بسيط وتخطيطي هناهنا .
أصبح من الواضح أننا بحاجة إلى منطق مماثل ، لكننا لا نريد حقًا دفع X Server إلى stm32 من أجل تطبيق صغير. لذلك ، دعونا نحاول فقط الرسم ليس في ذاكرة الفيديو ، ولكن في الذاكرة العادية. بعد عرض المشهد بالكامل ، سيقوم بنسخ المخزن المؤقت إلى ذاكرة الفيديو.
رمز القطعة
if (nk_begin(&rawfb->ctx, "Demo", nk_rect(50, 50, 200, 200),
NK_WINDOW_BORDER|NK_WINDOW_MOVABLE|
NK_WINDOW_CLOSABLE|NK_WINDOW_MINIMIZABLE|NK_WINDOW_TITLE)) {
enum {EASY, HARD};
static int op = EASY;
static int property = 20;
static float value = 0.6f;
if (mouse->type == INPUT_DEV_TOUCHSCREEN) {
/* Do not show cursor when using touchscreen */
nk_style_hide_cursor(&rawfb->ctx);
}
nk_layout_row_static(&rawfb->ctx, 30, 80, 1);
if (nk_button_label(&rawfb->ctx, "button"))
fprintf(stdout, "button pressed\n");
nk_layout_row_dynamic(&rawfb->ctx, 30, 2);
if (nk_option_label(&rawfb->ctx, "easy", op == EASY)) op = EASY;
if (nk_option_label(&rawfb->ctx, "hard", op == HARD)) op = HARD;
nk_layout_row_dynamic(&rawfb->ctx, 25, 1);
nk_property_int(&rawfb->ctx, "Compression:", 0, &property, 100, 10, 1);
nk_layout_row_begin(&rawfb->ctx, NK_STATIC, 30, 2);
{
nk_layout_row_push(&rawfb->ctx, 50);
nk_label(&rawfb->ctx, "Volume:", NK_TEXT_LEFT);
nk_layout_row_push(&rawfb->ctx, 110);
nk_slider_float(&rawfb->ctx, 0, &value, 1.0f, 0.1f);
}
nk_layout_row_end(&rawfb->ctx);
}
nk_end(&rawfb->ctx);
if (nk_window_is_closed(&rawfb->ctx, "Demo")) break;
/* Draw framebuffer */
nk_rawfb_render(rawfb, nk_rgb(30,30,30), 1);
memcpy(fb_info->screen_base, fb_buf, width * height * bpp);
ينشئ هذا المثال نافذة بحجم 200 × 200 بكسل ويرسم الرسومات فيها. يتم رسم المشهد الأخير نفسه في المخزن المؤقت fb_buf ، والذي خصصناه لـ SDRAM. ثم في السطر الأخير ، يتم استدعاء memcpy ببساطة. وكل شيء يعيد نفسه في دورة لا نهاية لها.
إذا قمنا ببناء هذا المثال وتشغيله ، فسنحصل على حوالي 10-15 إطارًا في الثانية. وهو بالتأكيد ليس جيدًا جدًا ، لأنه يمكن ملاحظته حتى بالعين. علاوة على ذلك ، نظرًا لوجود الكثير من حسابات الفاصلة العائمة في كود تقديم Nuklear ، فقد قمنا بتمكين دعمها في البداية ، وبدونها ستكون FPS أقل. التحسين الأول والأبسط (المجاني) هو بالطبع علامة المترجم -O2.
لنقم ببناء وتشغيل نفس المثال - نحصل على 20 إطارًا في الثانية. أفضل ، لكن لا يزال غير كافٍ للحصول على وظيفة جيدة.
تمكين مخابئ المعالج. وضع الكتابة
قبل الانتقال إلى مزيد من التحسينات ، سأقول إننا نستخدم المكون الإضافي rawfb كجزء من Nuklear ، والذي يرسم مباشرةً في الذاكرة. وفقًا لذلك ، يبدو تحسين الذاكرة واعدًا جدًا. أول ما يتبادر إلى الذهن هو ذاكرة التخزين المؤقت.
في الإصدارات القديمة من Cortex-M ، مثل Cortex-M7 (حالتنا) ، تم تضمين ذاكرة تخزين مؤقت إضافية للمعالج (ذاكرة التخزين المؤقت للتعليمات وذاكرة التخزين المؤقت للبيانات). يتم تمكينه من خلال سجل CCR الخاص بلوك التحكم في النظام. ولكن مع إدراج ذاكرة التخزين المؤقت تأتي مشاكل جديدة - عدم تناسق البيانات في ذاكرة التخزين المؤقت والذاكرة. هناك عدة طرق لإدارة ذاكرة التخزين المؤقت ، لكن في هذه المقالة لن أتطرق إليها ، لذلك سأنتقل إلى واحدة من أبسطها ، في رأيي. لحل مشكلة عدم تناسق ذاكرة التخزين المؤقت / الذاكرة ، يمكننا ببساطة تمييز كل الذاكرة المتاحة على أنها "غير قابلة للتخزين المؤقت". هذا يعني أن جميع عمليات الكتابة في هذه الذاكرة ستذهب دائمًا إلى الذاكرة وليس إلى ذاكرة التخزين المؤقت. ولكن إذا حددنا كل الذكريات بهذه الطريقة ، فلن يكون هناك نقطة في ذاكرة التخزين المؤقت أيضًا. هناك خيار آخر. هذا وضع "تمريري" ، حيث يتم نقل جميع عمليات الكتابة إلى الذاكرة التي تم وضع علامة "كتابة من خلالها" في نفس الوقت على ذاكرة التخزين المؤقت ،وفي الذاكرة. يؤدي هذا إلى إنشاء عبء للكتابة ، ولكن من ناحية أخرى ، يسرع القراءة بشكل كبير ، وبالتالي ستعتمد النتيجة على التطبيق المحدد.
بالنسبة لـ Nuklear ، تبين أن وضع الكتابة كان جيدًا جدًا - فقد ارتفع الأداء من 20 إطارًا في الثانية إلى 45 إطارًا في الثانية ، وهو بحد ذاته جيد وسلس بالفعل. التأثير مثير للاهتمام بالتأكيد ، حتى أننا حاولنا تعطيل وضع الكتابة من خلال عدم الانتباه إلى عدم تناسق البيانات ، لكن FPS ارتفع فقط إلى 50 إطارًا في الثانية ، أي لم تكن هناك زيادة كبيرة مقارنة بالكتابة. من هذا خلصنا إلى أن تطبيقنا يتطلب الكثير من عمليات القراءة ، وليس الكتابة. السؤال بالطبع أين؟ ربما بسبب عدد التحولات في كود rawfb الذي غالباً ما يصل إلى الذاكرة لقراءة المعامل التالي أو شيء من هذا القبيل.
تخزين مؤقت مزدوج (حتى الآن مع مخزن مؤقت وسيط). تمكين DMA
لم أرغب في التوقف عند 45 إطارًا في الثانية ، لذلك قررنا إجراء المزيد من التجارب. كانت الفكرة التالية هي التخزين المؤقت المزدوج. الفكرة معروفة على نطاق واسع وبسيطة بشكل عام. نقوم برسم المشهد باستخدام جهاز واحد إلى مخزن مؤقت واحد ، بينما يتم عرض الجهاز الآخر من مخزن مؤقت آخر. إذا نظرت إلى الكود السابق ، يمكنك أن ترى بوضوح حلقة يتم فيها رسم المشهد لأول مرة في المخزن المؤقت ، ثم يتم نسخ المحتويات في ذاكرة الفيديو باستخدام memcpy. من الواضح أن memcpy تستخدم وحدة المعالجة المركزية ، أي أن التقديم والنسخ يحدثان بالتتابع. كانت فكرتنا أن النسخ يمكن أن يتم بالتوازي باستخدام التحليل الميكانيكي الديناميكي (DMA). بمعنى آخر ، أثناء قيام المعالج برسم مشهد جديد ، يقوم DMA بنسخ المشهد السابق في ذاكرة الفيديو.
تم استبدال Memcpy بالرمز التالي:
while (dma_in_progress()) {
}
ret = dma_transfer((uint32_t) fb_info->screen_base,
(uint32_t) fb_buf[fb_buf_idx], (width * height * bpp) / 4);
if (ret < 0) {
printf("DMA transfer failed\n");
}
fb_buf_idx = (fb_buf_idx + 1) % 2;
هنا يتم إدخال fb_buf_idx - فهرس المخزن المؤقت. fb_buf_idx = 0 هو المخزن المؤقت الأمامي ، fb_buf_idx = 1 هو المخزن المؤقت الخلفي. تأخذ الدالة dma_transfer () الوجهة والمصدر وعددًا من كلمات 32 بت. ثم يتم تحميل DMA بالبيانات المطلوبة ، ويستمر العمل مع المخزن المؤقت التالي.
بعد تجربة هذه الآلية ، ارتفع الأداء إلى حوالي 48 إطارًا في الثانية. أفضل قليلاً من memcpy () ، لكن قليلاً فقط. لا أقصد أن أقول إن التحليل الميكانيكي الديناميكي (DMA) تبين أنه عديم الفائدة ، ولكن في هذا المثال المحدد ، كان تأثير ذاكرة التخزين المؤقت على الصورة الكبيرة أفضل.
بعد مفاجأة صغيرة أن أداء DMA كان أسوأ من المتوقع ، توصلنا إلى فكرة "ممتازة" ، كما بدا لنا في ذلك الوقت ، لاستخدام عدة قنوات DMA. ما هي النقطة؟ عدد البيانات التي يمكن تحميلها في DMA في وقت واحد على stm32f7xx هو 256 كيلوبايت. في الوقت نفسه ، تذكر أن الشاشة التي لدينا هي 480 × 272 وأن ذاكرة الفيديو تبلغ حوالي 512 كيلوبايت ، مما يعني أنه يبدو أنه يمكنك وضع النصف الأول من البيانات في قناة DMA واحدة ، والنصف الثاني في الثانية. ويبدو أن كل شيء على ما يرام ... لكن الأداء ينخفض من 48 إطارًا في الثانية إلى 25-30 إطارًا في الثانية. أي أننا نعود إلى الموقف عندما لم يتم تمكين ذاكرة التخزين المؤقت بعد. بما يمكن توصيله؟ في الواقع ، نظرًا لحقيقة أن الوصول إلى ذاكرة SDRAM متزامن ، حتى أن الذاكرة تسمى ذاكرة الوصول العشوائي الديناميكي المتزامن (SDRAM) ، لذلك لا يضيف هذا الخيار سوى مزامنة إضافية ،دون جعل الكتابة على الذاكرة موازية ، حسب الرغبة. بعد قليل من التفكير ، أدركنا أنه لا يوجد شيء يثير الدهشة هنا ، لأن الذاكرة واحدة ، ودورات الكتابة والقراءة يتم إنشاؤها لدائرة صغيرة واحدة (في ناقل واحد) ، وبما أنه تمت إضافة مصدر / مستقبل آخر ، فإن الحكم ، الذي يحل المكالمات على الحافلة ، تحتاج إلى خلط دورات الأوامر من قنوات DMA المختلفة.
تخزين مؤقت مزدوج. العمل مع LTDC
من المؤكد أن النسخ من مخزن مؤقت وسيط جيد ، ولكن كما اكتشفنا ، هذا لا يكفي. دعونا نلقي نظرة على تحسن واضح آخر - التخزين المؤقت المزدوج. في الغالبية العظمى من وحدات تحكم العرض الحديثة ، يمكنك تعيين العنوان لذاكرة الفيديو المستخدمة. وبالتالي ، يمكنك تجنب النسخ تمامًا ، وإعادة ترتيب عنوان ذاكرة الفيديو ببساطة إلى المخزن المؤقت المُجهز ، وستأخذ وحدة التحكم في الشاشة البيانات بالطريقة المثلى لها من تلقاء نفسها عبر DMA. هذا هو تخزين مؤقت مزدوج حقيقي ، بدون مخزن مؤقت وسيط كما كان من قبل. هناك أيضًا خيار عندما يمكن أن تحتوي وحدة التحكم في العرض على اثنين أو أكثر من المخازن المؤقتة ، وهو في الأساس نفس الشيء - نكتب إلى مخزن مؤقت واحد ، والآخر تستخدمه وحدة التحكم ، بينما النسخ غير مطلوب.
يحتوي LTDC (جهاز التحكم في شاشة LCD-TFT) في stm32f74xx على طبقتين من تراكب الأجهزة - الطبقة 1 والطبقة 2 ، حيث يتم تثبيت الطبقة الثانية على الطبقة 1. كل طبقة قابلة للتكوين بشكل مستقل ويمكن تمكينها أو تعطيلها بشكل منفصل. لقد حاولنا تمكين الطبقة الأولى فقط وإعادة ترتيب عنوان ذاكرة الفيديو على المخزن المؤقت الأمامي أو المخزن المؤقت الخلفي. أي ، نعطي أحدهما للعرض ، وفي الآخر نرسم في هذا الوقت. لكننا حصلنا على ارتعاش ملحوظ عند تبديل التراكبات.
لقد جربنا الخيار عندما نستخدم كلا الطبقتين مع تشغيل / إيقاف تشغيل أحدهما ، أي عندما يكون لكل طبقة عنوان ذاكرة فيديو خاص بها ، والذي لا يتغير ، ويتم تغيير المخزن المؤقت عن طريق تشغيل إحدى الطبقات أثناء إيقاف تشغيل الأخرى. أدى الاختلاف أيضًا إلى عدم الاستقرار. وأخيرًا ، جربنا الخيار عندما لم يتم إيقاف تشغيل الطبقة ، ولكن تم ضبط قناة ألفا على صفر 0 أو الحد الأقصى (255) ، أي أننا قمنا بالتحكم في الشفافية ، مما يجعل إحدى الطبقات غير مرئية. لكن هذا الخيار لم يرق إلى مستوى التوقعات ، فكان الارتعاش قائما.
لم يكن السبب واضحًا - تشير الوثائق إلى أنه يمكن إجراء تحديثات حالة الطبقة على الفور. لقد أجرينا اختبارًا بسيطًا - قمنا بإيقاف تشغيل ذاكرات التخزين المؤقت ، ونقطة عائمة ، ورسمنا صورة ثابتة مع مربع أخضر في وسط الشاشة ، ونفس الشيء لكل من الطبقة 1 والطبقة 2 ، وبدأنا في تبديل المستويات في حلقة ، على أمل الحصول على صورة ثابتة. لكننا حصلنا على نفس الهزة مرة أخرى.
أصبح من الواضح أنه شيء آخر. ثم تذكرنا محاذاة عنوان الإطارات الاحتياطية في الذاكرة. نظرًا لأنه تم تخصيص المخازن المؤقتة من الكومة ولم يتم محاذاة عناوينها ، قمنا بمحاذاة عناوينها بمقدار 1 كيلوبايت - حصلنا على الصورة المتوقعة بدون توتر. ثم وجدوا في الوثائق أن LTDC تطرح البيانات على دفعات 64 بايت ، وأن تفاوت البيانات يؤدي إلى خسارة كبيرة في الأداء. في هذه الحالة ، يجب محاذاة عنوان بداية المخزن المؤقت وعرضه. للاختبار ، قمنا بتغيير عرض 480 × 4 إلى 470 × 4 ، وهو غير قابل للقسمة على 64 بايت ، وحصلنا على نفس الارتعاش.
نتيجة لذلك ، قمنا بمحاذاة كلا المخازن المؤقتة بمقدار 64 بايت ، وتأكدنا من محاذاة العرض أيضًا بمقدار 64 بايت وتشغيل nuklear - اختفى الارتعاش. الحل الذي نجح يبدو هكذا. بدلاً من التبديل بين الطبقات عن طريق تعطيل الطبقة الأولى أو الطبقة تمامًا ، استخدم الشفافية. أي لتعطيل المستوى ، اضبط شفافيته على 0 ، ولتمكينه - على 255.
BSP_LCD_SetTransparency_NoReload(fb_buf_idx, 0xff);
fb_buf_idx = (fb_buf_idx + 1) % 2;
BSP_LCD_SetTransparency(fb_buf_idx, 0x00);
لقد حصلنا على 70-75 إطارًا في الثانية! أفضل بكثير من النسخة الأصلية 15.
وتجدر الإشارة إلى أن الحل يعمل من خلال التحكم في الشفافية ، والخيارات مع تعطيل أحد المستويات وخيار إعادة ترتيب عنوان المستوى تعطي تشويشًا للصورة عند FPS كبير 40-50 ، والسبب غير معروف حاليًا بالنسبة لنا. أيضًا ، للمضي قدمًا ، سأقول أن هذا حل لهذا المنتدى.
ملء مشهد الأجهزة عبر DMA2D
ولكن هذا ليس الحد الأقصى ، وآخر تحسين لدينا لزيادة FPS هو ملء مشهد الأجهزة. قبل ذلك ، قمنا بالتعبئة برمجيًا:
nk_rawfb_render(rawfb, nk_rgb(30,30,30), 1);
دعنا الآن نقول للمكوِّن الإضافي rawfb أنه ليست هناك حاجة لملء المشهد ، ولكن فقط قم بالطلاء:
nk_rawfb_render(rawfb, nk_rgb(30,30,30), 0);
سنملأ المشهد بنفس اللون 0xff303030 ، فقط في الأجهزة عبر وحدة التحكم DMA2D. تتمثل إحدى الوظائف الرئيسية لـ DMA2D في نسخ أو تعبئة مستطيل في ذاكرة الوصول العشوائي. تتمثل الراحة الرئيسية هنا في أن هذه ليست جزءًا مستمرًا من الذاكرة ، ولكنها منطقة مستطيلة توجد في الذاكرة مع وجود فواصل ، مما يعني أن DMA العادي لا يمكن القيام به على الفور. في Embox ، لم نعمل مع هذا الجهاز حتى الآن ، لذلك دعونا فقط نستخدم أدوات STM32Cube - وظيفة BSP_LCD_Clear (uint32_t Color). يقوم ببرمجة لون التعبئة وحجم الشاشة بأكملها في DMA2D.
فترة الطمس العمودي (VBLANK)
ولكن حتى مع تحقيق 80 إطارًا في الثانية ، بقيت مشكلة ملحوظة - فقد تحركت أجزاء من الأداة مع "فواصل" صغيرة عند التحرك عبر الشاشة. بمعنى ، يبدو أن الأداة مقسمة إلى 3 (أو أكثر) أجزاء تتحرك جنبًا إلى جنب ، ولكن مع تأخير طفيف. اتضح أن السبب كان تحديث ذاكرة فيديو غير صحيح. بتعبير أدق ، التحديثات في فترات زمنية خاطئة.
تحتوي وحدة التحكم في العرض على خاصية مثل VBLANK ، وهي أيضًا VBI أو فترة الطمس العمودي . يشير إلى الفاصل الزمني بين إطارات الفيديو المجاورة. أو بتعبير أدق ، الوقت بين السطر الأخير من إطار الفيديو السابق والسطر الأول من الإطار التالي. في هذا الفاصل الزمني ، لا يتم نقل أي بيانات جديدة إلى الشاشة ، والصورة ثابتة. لهذا السبب ، من الآمن تحديث ذاكرة الفيديو داخل VBLANK.
من الناحية العملية ، تحتوي وحدة التحكم LTDC على مقاطعة تم تكوينها ليتم تشغيلها بعد معالجة خط الإطار المؤقت التالي (سجل تكوين موضع مقاطعة خط LTDC (LTDC_LIPCR)). وبالتالي ، إذا قمت بتكوين هذه المقاطعة إلى رقم السطر الأخير ، فسنحصل فقط على بداية الفاصل الزمني لـ VBLANK. في هذه المرحلة ، نقوم بالتبديل اللازم للمخزن المؤقت.
نتيجة لمثل هذه الإجراءات ، عادت الصورة إلى طبيعتها ، وذهبت الفجوات. ولكن في نفس الوقت انخفض معدل الإطارات في الثانية من 80 إلى 60. دعونا نفهم ما يمكن أن يكون سبب هذا السلوك. يمكن العثور على الصيغة التالية
في الوثائق :
LCD_CLK (MHz) = total_screen_size * refresh_rate,
حيث total_screen_size = total_width x total_height. LCD_CLK هو التردد الذي ستحمل به وحدة التحكم في الشاشة وحدات البكسل من ذاكرة الفيديو إلى الشاشة (على سبيل المثال ، عبر واجهة العرض التسلسلية (DSI)). لكن Refresh_rate هو بالفعل معدل تحديث الشاشة نفسها ، خصائصها المادية. اتضح ، بمعرفة معدل تحديث الشاشة وأبعادها ، يمكنك تكوين تردد وحدة تحكم العرض. بعد التحقق من سجلات التكوين الذي ينشئه STM32Cube ، اكتشفنا أنه يضبط وحدة التحكم على شاشة 60 هرتز. لذلك اجتمع كل شيء معًا.
قليلا عن أجهزة الإدخال في مثالنا
دعنا نعود إلى تطبيقنا ونلقي نظرة على كيفية عمل شاشة اللمس ، لأنه كما تفهم ، فإن الواجهة الحديثة تعني التفاعل ، أي التفاعل مع المستخدم.
كل شيء مرتب بكل بساطة هنا. تتم معالجة الأحداث من أجهزة الإدخال في حلقة البرنامج الرئيسية مباشرة قبل عرض المشهد:
/* Input */
nk_input_begin(&rawfb->ctx);
{
switch (mouse->type) {
case INPUT_DEV_MOUSE:
handle_mouse(mouse, fb_info, rawfb);
break;
case INPUT_DEV_TOUCHSCREEN:
handle_touchscreen(mouse, fb_info, rawfb);
break;
default:
/* Unreachable */
break;
}
}
nk_input_end(&rawfb->ctx);
تحدث معالجة الأحداث من شاشة اللمس في وظيفة handle_touchscreen ():
handle_touchscreen
static void handle_touchscreen(struct input_dev *ts, struct fb_info *fb_info,
struct rawfb_context *rawfb) {
struct input_event ev;
int type;
static int x = 0, y = 0;
while (0 <= input_dev_event(ts, &ev)) {
type = ev.type & ~TS_EVENT_NEXT;
switch (type) {
case TS_TOUCH_1:
x = normalize_coord((ev.value >> 16) & 0xffff, 0, fb_info->var.xres);
y = normalize_coord(ev.value & 0xffff, 0, fb_info->var.yres);
nk_input_button(&rawfb->ctx, NK_BUTTON_LEFT, x, y, 1);
nk_input_motion(&rawfb->ctx, x, y);
break;
case TS_TOUCH_1_RELEASED:
nk_input_button(&rawfb->ctx, NK_BUTTON_LEFT, x, y, 0);
break;
default:
break;
}
}
}
في الواقع ، هذا هو المكان الذي يتم فيه تحويل أحداث جهاز الإدخال إلى تنسيق تفهمه نوكلير. في الواقع ، ربما هذا كل شيء.
إطلاق على لوحة أخرى
بعد حصولنا على نتائج جيدة ، قررنا إعادة إنتاجها على لوحة أخرى. كان لدينا لوحة أخرى مماثلة - STM32F769I-DISCO. هناك نفس وحدة التحكم LTDC ، ولكن شاشة مختلفة بدقة 800 × 480. بعد إطلاقه حصلت على 25 إطارًا في الثانية. هذا هو ، انخفاض ملحوظ في الأداء. يمكن تفسير ذلك بسهولة من خلال حجم الإطار المؤقت - فهو أكبر بثلاث مرات تقريبًا. ولكن تبين أن المشكلة الرئيسية مختلفة - كانت الصورة مشوهة للغاية ، ولم تكن هناك صورة ثابتة في الوقت الذي يجب أن تكون فيه الأداة في مكان واحد.
لم يكن السبب واضحًا ، لذلك ذهبنا لإلقاء نظرة على أمثلة قياسية من STM32Cube. كان هناك مثال على التخزين المؤقت المزدوج لهذه اللوحة المعينة. في هذا المثال ، يقوم المطورون ، على عكس الطريقة مع تغيير الشفافية ، بتحريك المؤشر ببساطة إلى حافظة الإطارات في مقاطعة VBLANK. لقد جربنا بالفعل هذه الطريقة مسبقًا للوحة الأولى ، لكنها لم تنجح معها. ولكن باستخدام هذه الطريقة لـ STM32F769I-DISCO ، حصلنا على تغيير سلس للصورة من 25 إطارًا في الثانية.
بسعادة غامرة ، اختبرنا هذه الطريقة مرة أخرى (مع إعادة ترتيب المؤشرات) على اللوحة الأولى ، لكنها ما زالت لا تعمل عند معدل إطارات مرتفع في الثانية. نتيجة لذلك ، تعمل الطريقة مع طبقة شفافة (60 إطارًا في الثانية) على لوحة واحدة ، والطريقة باستخدام مؤشرات إعادة الترتيب (25 إطارًا في الثانية) على الأخرى. بعد مناقشة الموقف ، قررنا تأجيل التوحيد حتى دراسة أعمق لمجموعة الرسومات.
النتيجة
لذا ، دعونا نلخص. يمثل المثال الموضح نمط واجهة المستخدم الرسومية البسيط والشائع لوحدات التحكم الدقيقة - بضعة أزرار أو عنصر تحكم في مستوى الصوت أو أي شيء آخر. المثال يفتقر إلى أي منطق مرتبط بالأحداث ، حيث تم التركيز على الرسومات. من حيث الأداء ، حصلنا على قيمة FPS مناسبة.
الفروق الدقيقة المتراكمة لتحسين الأداء تؤدي إلى استنتاج مفاده أن الرسومات أصبحت أكثر تعقيدًا في وحدات التحكم الدقيقة الحديثة. الآن ، تمامًا كما هو الحال في الأنظمة الأساسية الكبيرة ، تحتاج إلى مراقبة ذاكرة التخزين المؤقت للمعالج ، ووضع شيء ما في الذاكرة الخارجية ، وشيء ما في ذاكرة أسرع ، واستخدام DMA ، واستخدام DMA2D ، ومراقبة VBLANK ، وما إلى ذلك. بدأ كل شيء يبدو وكأنه منصات كبيرة ، وربما لهذا السبب أشرت بالفعل إلى X Server و Wayland عدة مرات.
ربما يكون أحد أكثر الأجزاء التي لم يتم تحسينها هو العرض نفسه ، فنحن نعيد رسم المشهد بالكامل من نقطة الصفر تمامًا. لا أستطيع أن أقول كيف يتم ذلك في مكتبات أخرى للميكروكونترولر ، ربما في مكان ما تكون هذه المرحلة مدمجة في المكتبة نفسها. ولكن بناءً على نتائج العمل مع Nuklear ، يبدو أنه في هذا المكان ، هناك حاجة إلى تناظرية لـ X Server أو Wayland ، بالطبع ، أخف ، مما يقودنا مرة أخرى إلى فكرة أن الأنظمة الصغيرة تتبع مسار الأنظمة الكبيرة.
UPD1
نتيجة لذلك ، لم تكن هناك حاجة إلى طريقة تغيير الشفافية. في كلتا اللوحتين ، عمل رمز مشترك - مع تبديل عنوان المخزن المؤقت بواسطة v-sync. علاوة على ذلك ، فإن الطريقة مع الورق الشفاف صحيحة أيضًا ، فهي ببساطة ليست ضرورية.
تحديث 2
أود أن أقول شكراً جزيلاً لجميع الأشخاص الذين اقترحوا التخزين المؤقت الثلاثي ، لم نصل إليه بعد. ولكن الآن يمكنك أن ترى أن هذه طريقة كلاسيكية (خاصة بالنسبة لمعدلات الإطارات المرتفعة FPS على الشاشة) ، والتي ، من بين أمور أخرى ، ستسمح لنا بالتخلص من التأخيرات بسبب انتظار v-sync (أي عندما يكون البرنامج متقدمًا بشكل ملحوظ على الصورة). لم نواجه هذا بعد ، لكنها مجرد مسألة وقت. وشكرًا خاصًا على المناقشة حول التخزين المؤقت الثلاثي أريد أن أقولبيسيتزروف و بيلاف!
جهات الاتصال لدينا:
Github: https://github.com/embox/embox
Newsletter: embox-ru [at] googlegroups.com
Telegram chat: t.me/embox_chat