4.14. أساسيات CSI

الوحدة csi هي الطريقة التي يقود بها كود Python مستشعر الكاميرا. وكل برنامج نصي يلتقط إطاراً يتبع الشكل ثلاثي الأجزاء نفسه: عمليات الاستيراد في الأعلى، والإعداد لمرة واحدة في الوسط، وحلقة while True في الأسفل تسحب الإطارات من الكاميرا واحداً تلو الآخر.

4.14.1. الحلقة النموذجية

import csi, image, time

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

clock = time.clock()
while True:
    clock.tick()
    img = csi0.snapshot()
    # process img here
    print(clock.fps())

4.14.2. ماذا يفعل كل استدعاء

import csi, image, time

تستورد ثلاث وحدات. تتحكم csi في المستشعر، وتعرّف image الصنف Image الذي تُرجعه snapshot()، وتوفر time الأداة المساعدة time.clock() المستخدمة لقياس عدد الإطارات في الثانية.

csi.CSI()

تبني نسخة CSI تغلّف مستشعر كاميرا فيزيائياً واحداً. ويطالب الباني بالطرفية الخاصة بالكاميرا ويسجّل الإعداد الخاص بكل مستشعر. الكاميرات ذات المستشعر الواحد لها نسخة CSI واحدة؛ والكاميرات ذات المستشعرين (ملون مع حراري، ملون مع حدثي) لها نسختان، تُختار كل منهما بوسيطة cid المُمرَّرة إلى الباني.

csi0.reset()

تشغّل المستشعر وتعدّه. تنبض افتراضياً بدبوس إعادة الضبط للمستشعر، ثم تكتب سجلات I2C للمستشعر إلى حالة بدء معروفة. وتدفع استدعاءات الإعداد اللاحقة -- pixformat وframesize ومقابض التحكم التلقائي -- مزيداً من عمليات كتابة السجلات عبر ناقل تحكم I2C نفسه.

csi0.pixformat(csi.RGB565)

تكتب سجلات المستشعر التي تختار تنسيق بكسل الخرج. والخيارات المتاحة هي التنسيقات التي قدّمتها صفحة تنسيقات البكسل: RGB565 وGRAYSCALE وBAYER وYUV422 وJPEG على المستشعرات التي تدعمه.

csi0.framesize(csi.QVGA)

تكتب السجلات التي تختار دقة الخرج. QVGA هي 320 × 240؛ والأحجام المسماة تصل إلى WQXGA2 (2592 × 1944، نحو 5 ميغابكسل) على المستشعرات التي تدعمها. وتعمل أيضاً مجموعة (width, height) مخصصة، طالما أنها تتوافق مع قدرات خرج المستشعر.

clock = time.clock()

تنشئ أداة ساعة مساعدة. ويسجّل كل استدعاء لـ clock.tick() داخل الحلقة وقت بدء التكرار؛ وتُبلّغ time.clock.fps() عن معدل الحلقة الأخير بعدد الإطارات في الثانية.

img = csi0.snapshot()

تلتقط إطاراً واحداً من المستشعر وتُرجعه على هيئة Image. وتستحق آلية وصول ذلك الإطار إلى الذاكرة نظرة أقرب.

4.14.3. كيف تملأ اللقطة الذاكرة

يسلّم المستشعر البكسلات على ناقل بيانات البكسل الموصوف في نواقل المستشعر بمعدلات تبلغ مئات الميغابايتات في الثانية -- أسرع بكثير من أن يتمكن المعالج المركزي من نسخها بكسلاً بكسلاً برمجياً.

بدلاً من ذلك، يفرّغ الـ MCU عملية النقل إلى الوصول المباشر للذاكرة (DMA) -- وهو محرك عتادي منفصل عن المعالج المركزي ينسخ البايتات من مكان إلى آخر داخل الـ MCU دون إشراك المعالج المركزي إطلاقاً. تلتقط طرفية دخل الكاميرا كل بايت بكسل وارد في FIFO صغير على الشريحة؛ وتعالج البيانات أثناء مرورها أيُّ مراحل ISP تعمل على جانب الـ MCU؛ ويكتب محرك DMA البكسلات النهائية في مخزن الإطارات في RAM عند إزاحة البكسل المقابلة. ولا يحتاج أي شيء في تلك السلسلة إلى المعالج المركزي بمجرد برمجة قناة DMA.

عند استدعاء snapshot():

  1. يبرمج مشغّل CSI محرك DMA بعنوان مخزن الإطارات، وطول النقل (ما يعادل بكسلات إطار واحد)، ودالة رد نداء لمقاطعة اكتمال DMA.

  2. يُمكّن المشغّل طرفية دخل الكاميرا وينتظر أن يشير المستشعر إلى بدء الإطار التالي.

  3. أثناء بثّ المستشعر للإطار، تمرّر الطرفية كل بايت بكسل عبر الـ ISP ثم إلى محرك DMA، الذي يكتب النتيجة في RAM عند إزاحة مخزن الإطارات التالية. ويكون المعالج المركزي حراً لتشغيل كود آخر أثناء النقل.

  4. عند وصول آخر بكسل في الإطار، يطلق DMA مقاطعة اكتماله، فيغلّف المشغّل مخزن الإطارات في Image، وتُرجعه snapshot() إلى كود المستخدم.

لا تمتلك Image المُرجَعة نسخة من بيانات البكسل -- بل تشير إلى أحد مخازن إطارات الكاميرا في RAM. أما عدد مخازن الإطارات التي تحتفظ بها الكاميرا، وكيفية تبادلها بين DMA وكود المستخدم في كل استدعاء لـ snapshot()، فيعتمد على وضع التخزين المؤقت الذي اختاره التطبيق عبر framebuffers().