3.22. SPI في الشيفرة¶
يغلّف machine.SPI متحكم SPI عتادياً؛ وتكون خطوط CS مخارج Pin اعتيادية يديرها البرنامج النصي. أنشئ نسخة SPI بمعرّف الناقل ومعدل الساعة المرغوب و(عند الحاجة) الوضع:
from machine import SPI, Pin
spi = SPI(0, baudrate=1_000_000, polarity=0, phase=0)
cs = Pin("P3", Pin.OUT, value=1) # CS idle high
يختار id أي كتلة SPI عتادية تُستخدم؛ وتعتمد الأرقام المتاحة ودبابيس SCK/MOSI/MISO التي تُربط بها على اللوحة (انظر لوحات OpenMV). و baudrate هو تردد SCK بالهرتز -- وقد يكون المعدل الفعلي الذي يحققه العتاد أدنى قليلاً بسبب تقسيم الساعة، وهو ما ستُظهره القيمة المطبوعة لكائن SPI.
يُنشأ دبوس CS بـ value=1 ليخمل غير مفعّل. تُفعّل كل معاملة CS (تقوده إلى المستوى المنخفض)، وتنقل البايتات، ثم تُلغي تفعيل CS (تقوده إلى المستوى المرتفع) مجدداً.
3.22.1. القراءة والكتابة والتبادل¶
تغطي ثلاث دوال الحالات الشائعة:
cs.value(0)
spi.write(b"\x10\x20\x30") # send 3 bytes, ignore what comes back
cs.value(1)
cs.value(0)
data = spi.read(4) # read 4 bytes; sends 0x00 while reading
cs.value(1)
rx = bytearray(2)
cs.value(0)
spi.write_readinto(b"\x9F\x00", rx) # send 0x9F, 0x00; receive 2 bytes
cs.value(1)
write() هو المسار السريع للكتابة فقط؛ يدفع المتحكم البايتات ويتجاهل أياً كان ما أرسله الجهاز الطرفي على MISO. و read() هو الصورة المعكوسة -- إذ يُصدر N من نبضات SCK بينما يرسل بايت حشو ثابتاً (0 افتراضياً) على MOSI ويخزّن بايتات MISO. أما write_readinto() فهو الصيغة ثنائية الاتجاه الكامل: يرسل البايتات من مخزن مؤقت ويخزّن بايتات MISO المتزامنة في مخزن آخر. وتستخدم كثير من الأجهزة الطرفية هذا النمط -- "أرسل بايت أمر، ثم اقرأ الاستجابة في النقل التالي" -- بحيث تتلاءم العمليتان بشكل طبيعي في استدعاء write_readinto واحد.
تتوقع معظم الأجهزة الطرفية أن يبقى خط CS مفعّلاً طوال المعاملة (من بايتات الأمر حتى بايتات الاستجابة)، لذا أبقِ قوسي cs.value(0) / cs.value(1) حول كامل التسلسل، لا حول كل استدعاء دالة.
3.22.2. قراءة مستشعر نموذجية¶
تنظّم كثير من مستشعرات SPI حالتها كمجموعة من السجلات الداخلية وتتبع شكل التبادل نفسه: أرسل عنوان السجل (مع راية قراءة/كتابة في البتة العليا)، ثم اقرأ أو اكتب بايتات السجل. قراءة السجل 0x0F على جهاز كهذا:
rx = bytearray(2)
cs.value(0)
spi.write_readinto(b"\x8F\x00", rx) # 0x80 = "read" flag, then reg 0x0F
cs.value(1)
register_value = rx[1]
بايت MISO الأول هو نفاية (كان الجهاز لا يزال يستقبل الأمر عند تلك النقطة)؛ ويحمل بايت MISO الثاني محتويات السجل. أما التنسيق الدقيق لبايت الأمر -- أي بتة هي راية القراءة/الكتابة، وما إذا كان العنوان يزداد تلقائياً في القراءات متعددة البايتات -- فموجود في ورقة بيانات الجهاز.
3.22.3. التبديل البرمجي للبتات (Bit-banging)¶
تستخدم نسخة SPI أعلاه كتلة SPI عتادية: طرفية مخصّصة داخل المتحكم لها سجل إزاحة ومولّد ساعة خاصان بها يُنتجان أمواج SCK / MOSI / MISO في السيليكون. تسلّم البرمجيات بايتاً فحسب؛ وتتحرك البتات على السلك دون مساعدة إضافية من المعالج، مما يترك المعالج حراً للقيام بأعمال أخرى بالتوازي.
البديل هو التبديل البرمجي للبتات: إذ تدور البرمجيات على كل بتة وتبدّل دبابيس GPIO مباشرةً لإنتاج الموجة نفسها. لا توجد طرفية عتادية مشاركة -- يقود المعالج SCK إلى المنخفض، ويضبط MOSI، ويقود SCK إلى المرتفع، ويأخذ عيّنة من MISO، وهكذا لكل بتة من كل بايت. وهذا يشغل المعالج طوال المعاملة ويعمل أبطأ مما تستطيعه الكتلة العتادية، لكنه يعمل على أي دبوس ولا يحتاج إلى كتلة عتادية حرة.
machine.SoftSPI هو تنفيذ التبديل البرمجي للبتات لواجهة SPI نفسها:
from machine import SoftSPI, Pin
spi = SoftSPI(baudrate=500_000, polarity=0, phase=0,
sck=Pin("P2"), mosi=Pin("P0"), miso=Pin("P1"))
استخدمه عندما يحتاج الجهاز إلى أن يكون على دبابيس غير موصّلة بكتلة SPI عتادية، أو عندما تكون الكتل العتادية كلها قيد الاستخدام. و 500 kHz سقف مريح على معظم الكاميرات؛ ويبقى المعالج مشغولاً طوال المعاملة.