3.24. I2C في الشيفرة¶
يغلّف machine.I2C متحكم I2C عتاديًا. أنشئ واحدًا بمعرف الناقل وتردد ساعة اختياري:
from machine import I2C
i2c = I2C(2, freq=400_000)
يحدد id كتلة I2C العتادية التي ستُستخدم؛ وتعتمد الأرقام المتاحة ودبابيس SDA / SCL التي تُربط بها على اللوحة (انظر لوحات OpenMV). وfreq هو معدل ساعة SCL -- إذ 100_000 هو الوضع المعياري، و400_000 هي السرعة "السريعة" الشائعة التي تدعمها معظم المستشعرات.
3.24.1. مسح الناقل¶
تمر scan() عبر فضاء العناوين ذي 7 بتات وتعيد قائمة بكل جهاز أقر بعنوانه:
>>> i2c.scan()
[0x40, 0x68]
هذا أول ما ينبغي تجربته بعد توصيل ناقل جديد. فإن كان الجهاز على القائمة، فالتوصيل ومقاومات السحب لأعلى سليمة. وإن كانت القائمة فارغة (أو أقصر من المتوقع)، فاشتبه في مقاومات السحب لأعلى، أو التوصيل، أو دبابيس عنوان الجهاز قبل الاشتباه في الشيفرة.
3.24.2. نمط المُعنوَن في الذاكرة¶
تكشف معظم مستشعرات I2C عن حالتها كمجموعة من السجلات الداخلية: أعلام الحالة عند عنوان، وبايتات القياس عند آخر، والإعدادات عند ثالث. وتحتوي المكتبة على طريقتين ملائمتين تُطابقان مباشرةً نمط "عنونة الجهاز، ثم الإشارة إلى سجل، ثم قراءة البيانات أو كتابتها".
قراءة سجل:
addr = 0x68
reg = 0x3B
data = i2c.readfrom_mem(addr, reg, 6)
# `data` is 6 bytes starting at register 0x3B
يُصدر هذا الاستدعاء الواحد START، ويرسل عنوان الجهاز مع بت الكتابة، ويرسل عنوان السجل (بدء متكرر)، ويعيد إرسال العنوان مع بت القراءة، ويقرأ 6 بايتات، ويصدر NACK للأخير، ثم STOP. فيرى الجهاز تسلسل "القراءة من السجل" المعياري؛ ويرى البرنامج النصي استدعاء Python واحدًا.
الكتابة إلى سجل:
i2c.writeto_mem(addr, 0x6B, b"\x00")
هذا التسلسل هو ذاته في صورة معكوسة: START، عنوان + كتابة، سجل، بايت الحمولة، STOP.
للأجهزة التي يكون عنوان سجلها بعرض بايتين (بعض ذواكر EEPROM والذواكر المُعنوَنة بـ 16 بت) مرِّر addrsize=16:
data = i2c.readfrom_mem(addr, 0x0100, 32, addrsize=16)
3.24.3. قراءة مستشعر¶
حلقة قصيرة تقرأ معرّف WHO_AM_I ومحور X لمقياس التسارع على مستشعر MEMS نموذجي:
import time
import struct
from machine import I2C
i2c = I2C(2, freq=400_000)
ADDR = 0x68
WHO_AM_I = 0x75
ACCEL_X_HI = 0x3B
print("WHO_AM_I:", i2c.readfrom_mem(ADDR, WHO_AM_I, 1)[0])
while True:
raw = i2c.readfrom_mem(ADDR, ACCEL_X_HI, 2)
x = struct.unpack(">h", raw)[0]
print("accel X:", x)
time.sleep_ms(100)
تعيد readfrom_mem() كائن bytes، لذا فإن قراءة WHO_AM_I تفهرس البايت الأول (والوحيد) بـ [0]. أما قراءة مقياس التسارع فهي بايتان خامان يجب تجميعهما في قيمة واحدة بإشارة من 16 بت؛ وتنسيق struct المتمثل في ">h" يفعل ذلك بترتيب البايتات الكبير أولًا مع إشارة -- وهو ترتيب البايتات الذي تستخدمه معظم مستشعرات I2C، وهو عكس اصطلاح الصغير أولًا الشائع على وصلات UART. وتعيد struct.unpack() دائمًا صفًّا (عنصرًا واحدًا لكل محرف تنسيق)، لذا فإن [0] تستخرج القيمة الوحيدة.
3.24.4. الوصول الأدنى مستوى¶
للأجهزة التي لا تتبع نمط خريطة السجلات، تمنح طريقتان أدنى مستوى تحكمًا مباشرًا في طور العنوان واتجاه البيانات:
i2c.writeto(addr, b"\x01\x02\x03") # write 3 bytes to addr
data = i2c.readfrom(addr, 4) # read 4 bytes from addr
هاتان مفيدتان للأجهزة ذات واجهات تيار الأوامر (الشاشات، وبعض الساعات اللحظية) حيث لا تتناسب تمامًا المساعدات الملائمة لخريطة السجلات.
3.24.5. النقر البتي عندما لا تتوفر كتلة عتادية¶
يوفر machine.SoftI2C الواجهة البرمجية نفسها على دبابيس GPIO اعتباطية، منفِّذًا البروتوكول نقرًا بتيًا في البرمجيات:
from machine import SoftI2C, Pin
i2c = SoftI2C(scl=Pin("P5"), sda=Pin("P4"), freq=100_000)
استخدمه عندما يحتاج الجهاز إلى أن يكون على دبابيس غير موصولة بكتلة I2C عتادية، أو عندما تكون كل كتلة عتادية قيد الاستخدام. حلقة النقر البتي أبطأ من I2C العتادي وتبقى وحدة المعالجة المركزية مشغولة طوال المعاملة، لكن الواجهة البرمجية مطابقة لـ I2C لذا تنتقل الشيفرة القائمة إليها بتغيير الباني فقط.