الصنف I2C -- بروتوكول تسلسلي ثنائي الأسلاك

I2C هو بروتوكول ثنائي الأسلاك للاتصال بين الأجهزة. على المستوى الفيزيائي يتألف من سلكين: SCL و SDA، وهما خط الساعة وخط البيانات على التوالي.

تُنشأ كائنات I2C وهي مرتبطة بناقل محدد. يمكن تهيئتها عند الإنشاء، أو تهيئتها لاحقاً.

طباعة كائن I2C تمنحك معلومات حول تكوينه.

توجد تطبيقات I2C على مستوى العتاد والبرمجيات عبر الصنفين I2C و SoftI2C. يستخدم I2C العتادي الدعم العتادي الأساسي للنظام لإجراء عمليات القراءة/الكتابة وهو عادةً فعّال وسريع لكنه قد يفرض قيوداً على الدبابيس التي يمكن استخدامها. أما I2C البرمجي فيُطبّق عن طريق التبديل المباشر للبتات ويمكن استخدامه على أي دبوس لكنه ليس بالفعالية ذاتها. يمتلك هذان الصنفان الطرق نفسها المتاحة ويختلفان بشكل أساسي في طريقة إنشائهما.

ملاحظة

يتطلب ناقل I2C دائرة سحب لأعلى (pull-up) على كل من SDA و SCL لكي يعمل. عادةً ما تكون هذه مقاومات في النطاق 1 - 10 كيلو أوم، موصولة من كل من SDA/SCL إلى Vcc. بدونها يكون السلوك غير محدد وقد يتراوح من التعليق، أو إعادة ضبط غير متوقعة من المؤقت الرقيب (watchdog)، إلى مجرد قيم خاطئة. غالباً ما تكون دائرة السحب لأعلى هذه مدمجة بالفعل في لوحة MCU أو لوحات تفريع المستشعرات، لكن لا توجد قاعدة لذلك. لذا يرجى التحقق في حال حدوث مشكلة. راجع أيضاً هذا الدليل التعليمي الممتاز من Adafruit حول توصيل أسلاك I2C.

مثال على الاستخدام:

from machine import I2C

i2c = I2C(freq=400000)          # create I2C peripheral at frequency of 400kHz
                                # depending on the port, extra parameters may be required
                                # to select the peripheral and/or pins to use

i2c.scan()                      # scan for peripherals, returning a list of 7-bit addresses

i2c.writeto(42, b'123')         # write 3 bytes to peripheral with 7-bit address 42
i2c.readfrom(42, 4)             # read 4 bytes from peripheral with 7-bit address 42

i2c.readfrom_mem(42, 8, 3)      # read 3 bytes from memory of peripheral 42,
                                #   starting at memory-address 8 in the peripheral
i2c.writeto_mem(42, 2, b'\x10') # write 1 byte to memory of peripheral 42
                                #   starting at address 2 in the peripheral

المُنشئات

class machine.I2C(id: int, *, scl: Pin | None = None, sda: Pin | None = None, freq: int = 400000, timeout: int = 50000)

أنشئ وأعد كائن I2C جديداً باستخدام المعاملات التالية:

  • id يحدد طرفية I2C معينة. تعتمد القيم المسموح بها على المنفذ/اللوحة المحددة

  • scl ينبغي أن يكون كائن دبوس يحدد الدبوس المستخدم لـ SCL.

  • sda ينبغي أن يكون كائن دبوس يحدد الدبوس المستخدم لـ SDA.

  • freq ينبغي أن يكون عدداً صحيحاً يحدد التردد الأقصى لـ SCL.

  • timeout هو الزمن الأقصى بالميكروثانية المسموح به لمعاملات I2C. هذا المعامل غير مسموح به على بعض المنافذ.

لاحظ أن بعض المنافذ/اللوحات سيكون لها قيم افتراضية لـ scl و sda يمكن تغييرها في هذا المُنشئ. وسيكون لأخرى قيم ثابتة لـ scl و sda لا يمكن تغييرها.

الطرق العامة

init(scl: Pin, sda: Pin, *, freq: int = 400000) None

تهيئة ناقل I2C بالوسائط المعطاة:

  • scl هو كائن دبوس لخط SCL

  • sda هو كائن دبوس لخط SDA

  • freq هو معدل ساعة SCL

في حالة I2C العتادي قد يكون تردد الساعة الفعلي أقل من التردد المطلوب. ويعتمد ذلك على عتاد المنصة. يمكن تحديد المعدل الفعلي عن طريق طباعة كائن I2C.

scan() List[int]

افحص جميع عناوين I2C بين 0x08 و 0x77 شاملةً وأعد قائمة بتلك التي تستجيب. يستجيب الجهاز إذا سحب خط SDA إلى المستوى المنخفض بعد إرسال عنوانه (بما في ذلك بت الكتابة) على الناقل.

عمليات I2C الأولية

تطبّق الطرق التالية عمليات ناقل المتحكم I2C الأولية ويمكن دمجها لتكوين أي معاملة I2C. وهي متوفرة إذا كنت بحاجة إلى تحكم أكبر في الناقل، وإلا فيمكن استخدام الطرق القياسية (انظر أدناه).

هذه الطرق متاحة فقط على الصنف SoftI2C.

start() None

توليد حالة بدء (START) على الناقل (ينتقل SDA إلى المستوى المنخفض بينما يكون SCL مرتفعاً).

stop() None

توليد حالة إيقاف (STOP) على الناقل (ينتقل SDA إلى المستوى المرتفع بينما يكون SCL مرتفعاً).

readinto(buf: bytearray, nack: bool = True, /) None

يقرأ بايتات من الناقل ويخزنها في buf. عدد البايتات المقروءة هو طول buf. سيُرسل إقرار (ACK) على الناقل بعد استقبال جميع البايتات عدا الأخيرة. بعد استقبال البايت الأخير، إذا كان nack صحيحاً فسيُرسل عدم إقرار (NACK)، وإلا فسيُرسل إقرار (ACK) (وفي هذه الحالة تفترض الطرفية أنه سيتم قراءة المزيد من البايتات في استدعاء لاحق).

write(buf: bytes) int

اكتب البايتات من buf إلى الناقل. تتحقق من استقبال إقرار (ACK) بعد كل بايت وتتوقف عن إرسال البايتات المتبقية إذا استُقبل عدم إقرار (NACK). تُعيد الدالة عدد الإقرارات (ACK) التي استُقبلت.

عمليات الناقل القياسية

تطبّق الطرق التالية عمليات القراءة والكتابة القياسية لمتحكم I2C التي تستهدف جهازاً طرفياً معطى.

readfrom(addr: int, nbytes: int, stop: bool = True, /) bytes

اقرأ nbytes من الطرفية المحددة بـ addr. إذا كان stop صحيحاً فسيتم توليد حالة إيقاف (STOP) في نهاية النقل. تُعيد كائن bytes يحتوي على البيانات المقروءة.

readfrom_into(addr: int, buf: bytearray, stop: bool = True, /) None

اقرأ إلى buf من الطرفية المحددة بـ addr. سيكون عدد البايتات المقروءة هو طول buf. إذا كان stop صحيحاً فسيتم توليد حالة إيقاف (STOP) في نهاية النقل.

تُعيد الطريقة None.

writeto(addr: int, buf: bytes, stop: bool = True, /) int

اكتب البايتات من buf إلى الطرفية المحددة بـ addr. إذا استُقبل عدم إقرار (NACK) بعد كتابة بايت من buf فلن تُرسل البايتات المتبقية. إذا كان stop صحيحاً فسيتم توليد حالة إيقاف (STOP) في نهاية النقل، حتى إذا استُقبل عدم إقرار (NACK). تُعيد الدالة عدد الإقرارات (ACK) التي استُقبلت.

writevto(addr: int, vector: tuple | list, stop: bool = True, /) int

اكتب البايتات الموجودة في vector إلى الطرفية المحددة بـ addr. ينبغي أن يكون vector صفاً (tuple) أو قائمة من الكائنات التي تدعم بروتوكول المخزن المؤقت. يُرسل addr مرة واحدة ثم تُكتب البايتات من كل كائن في vector بالتتابع. قد تكون الكائنات في vector بطول صفر بايت وفي هذه الحالة لا تساهم في المخرجات.

إذا استُقبل عدم إقرار (NACK) بعد كتابة بايت من أحد الكائنات في vector فلن تُرسل البايتات المتبقية، ولا أي كائنات متبقية. إذا كان stop صحيحاً فسيتم توليد حالة إيقاف (STOP) في نهاية النقل، حتى إذا استُقبل عدم إقرار (NACK). تُعيد الدالة عدد الإقرارات (ACK) التي استُقبلت.

عمليات الذاكرة

تعمل بعض أجهزة I2C كجهاز ذاكرة (أو مجموعة سجلات) يمكن القراءة منه والكتابة إليه. في هذه الحالة يرتبط عنوانان بمعاملة I2C: عنوان الطرفية وعنوان الذاكرة. الطرق التالية هي دوال مساعدة للتواصل مع مثل هذه الأجهزة.

readfrom_mem(addr: int, memaddr: int, nbytes: int, *, addrsize: int = 8) bytes

اقرأ nbytes من الطرفية المحددة بـ addr بدءاً من عنوان الذاكرة المحدد بـ memaddr. يحدد الوسيط addrsize حجم العنوان بالبتات. تُعيد كائن bytes يحتوي على البيانات المقروءة.

readfrom_mem_into(addr: int, memaddr: int, buf: bytearray, *, addrsize: int = 8) None

اقرأ إلى buf من الطرفية المحددة بـ addr بدءاً من عنوان الذاكرة المحدد بـ memaddr. عدد البايتات المقروءة هو طول buf. يحدد الوسيط addrsize حجم العنوان بالبتات.

تُعيد الطريقة None.

writeto_mem(addr: int, memaddr: int, buf: bytes, *, addrsize: int = 8) None

اكتب buf إلى الطرفية المحددة بـ addr بدءاً من عنوان الذاكرة المحدد بـ memaddr. يحدد الوسيط addrsize حجم العنوان بالبتات.

تُعيد الطريقة None.

الصنف SoftI2C -- ناقل I2C مُحاكى برمجياً

يطبّق الصنف SoftI2C بروتوكول I2C عن طريق التبديل المباشر للبتات على دبابيس GPIO اعتباطية. وهو يكشف سطح الطرق نفسه الذي يكشفه I2C بالإضافة إلى عمليات الناقل الأولية منخفضة المستوى (start()، stop()، readinto()، write()) للمستدعين الذين يحتاجون إلى تجميع معاملات غير قياسية. استخدمه عندما لا تكون الدبابيس التي تحتاجها موصولة بكتلة I2C عتادية، أو عندما تحتاج إلى عدد من النواقل أكبر مما يوفره العتاد، أو للتواصل مع أجهزة تتطلب تسلسلات غير معتادة (نبضات ساعة إضافية، بدايات متكررة بعد الكتابات، إلخ).

المُنشئات

class machine.SoftI2C(scl: Pin, sda: Pin, *, freq: int = 400000, timeout: int = 50000)

أنشئ ناقل I2C برمجياً مُشغّلاً بواسطة scl / sda.

freq هو معدل ساعة SCL المستهدف بالهرتز (المعدل الفعلي يكون عادةً أقل بسبب الحمل الإضافي لحلقة التبديل المباشر للبتات).

timeout هو الزمن الأقصى بالميكروثانية للانتظار من أجل تمديد الساعة (clock stretching) (احتفاظ جهاز آخر على الناقل بـ SCL منخفضاً)؛ وعند انتهائه يُطلق OSError(ETIMEDOUT).

الطرق العامة

init(scl: Pin, sda: Pin, *, freq: int = 400000) None

أعد تهيئة ناقل I2C البرمجي بالدبابيس والتردد المعطيين. مكافئ لإنشاء SoftI2C جديد على الكائن نفسه.

scan() List[int]

افحص جميع عناوين I2C بين 0x08 و 0x77 شاملةً وأعد قائمة بتلك التي استجابت.

عمليات I2C الأولية

تطبّق الطرق التالية عمليات ناقل المتحكم I2C الأولية ويمكن دمجها لتكوين أي معاملة I2C. وهي خاصة بـ SoftI2C فقط -- الصنف العتادي I2C لا يكشفها.

start() None

توليد حالة بدء (START) على الناقل (ينتقل SDA إلى المستوى المنخفض بينما يكون SCL مرتفعاً).

stop() None

توليد حالة إيقاف (STOP) على الناقل (ينتقل SDA إلى المستوى المرتفع بينما يكون SCL مرتفعاً).

readinto(buf: bytearray, nack: bool = True, /) None

اقرأ بايتات من الناقل إلى buf. تُقرأ len(buf) بايتات؛ ويُرسل إقرار (ACK) بعد كل بايت عدا الأخير. بعد البايت الأخير، يرسل nack=True (الافتراضي) عدم إقرار (NACK) لإنهاء النقل؛ ويرسل nack=False إقراراً (ACK) ليبقى الجهاز محدداً من أجل readinto() لاحقة.

write(buf: bytes) int

اكتب buf إلى الناقل، مع التحقق من الإقرار (ACK) بعد كل بايت. يتوقف الإرسال عند أول عدم إقرار (NACK). تُعيد عدد الإقرارات (ACK) المستقبلة.

عمليات الناقل القياسية

تطبّق الطرق التالية عمليات القراءة والكتابة القياسية لمتحكم I2C التي تستهدف جهازاً طرفياً معطى.

readfrom(addr: int, nbytes: int, stop: bool = True, /) bytes

اقرأ nbytes من الجهاز الموجود على العنوان addr ذي 7 بتات. إذا كان stop صحيحاً فسيتم توليد حالة إيقاف (STOP) في نهاية النقل.

readfrom_into(addr: int, buf: bytearray, stop: bool = True, /) None

اقرأ len(buf) بايتات من الجهاز الموجود على العنوان addr إلى buf. إذا كان stop صحيحاً فسيتم توليد حالة إيقاف (STOP) في نهاية النقل.

writeto(addr: int, buf: bytes, stop: bool = True, /) int

اكتب buf إلى الجهاز الموجود على العنوان addr. يتوقف الإرسال عند أول عدم إقرار (NACK). إذا كان stop صحيحاً فسيتم دائماً توليد حالة إيقاف (STOP) في نهاية النقل (حتى عند عدم إقرار مبكر). تُعيد عدد الإقرارات (ACK) المستقبلة.

writevto(addr: int, vector: tuple | list, stop: bool = True, /) int

اكتب تسلسل المخازن المؤقتة الموجودة في vector إلى الجهاز الموجود على العنوان addr كمعاملة واحدة. تُتجاهل المخازن المؤقتة الفارغة. تتصرف مثل writeto() فيما يخص دلالات stop والقيمة المُعادة.

عمليات الذاكرة

تعمل بعض أجهزة I2C كجهاز ذاكرة (أو مجموعة سجلات) يمكن القراءة منه والكتابة إليه. في هذه الحالة يرتبط عنوانان بمعاملة I2C: عنوان الطرفية وعنوان الذاكرة. الطرق التالية هي مساعدات ملائمة للتواصل مع مثل هذه الأجهزة.

readfrom_mem(addr: int, memaddr: int, nbytes: int, *, addrsize: int = 8) bytes

اقرأ nbytes من الجهاز الموجود على العنوان addr بدءاً من السجل memaddr. addrsize هو عرض عنوان السجل بالبتات (عادةً 8 أو 16).

readfrom_mem_into(addr: int, memaddr: int, buf: bytearray, *, addrsize: int = 8) None

اقرأ إلى buf من الجهاز الموجود على العنوان addr بدءاً من السجل memaddr.

writeto_mem(addr: int, memaddr: int, buf: bytes, *, addrsize: int = 8) None

اكتب buf إلى الجهاز الموجود على العنوان addr بدءاً من السجل memaddr.