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 הוא standard mode, 400_000 היא מהירות ה-”fast“ הנפוצה שרוב החיישנים תומכים בה.

3.24.1. סריקת האפיק

scan() עובר על מרחב המען בן 7 הביטים ומחזיר רשימה של כל התקן שאישר את המען שלו:

>>> i2c.scan()
[0x40, 0x68]

זה הדבר הראשון שכדאי לנסות לאחר חיווט אפיק חדש. אם התקן נמצא ברשימה, החיווט וה-pull-up תקינים. אם הרשימה ריקה (או קצרה מהצפוי), חשדו ב-pull-up, בחיווט או בפיני המען של ההתקן לפני שתחשדו בקוד.

3.24.2. תבנית הזיכרון הממופה

רוב חיישני ה-I2C חושפים את מצבם כקבוצה של registers פנימיים: דגלי סטטוס במען אחד, בתי מדידה במען אחר, תצורה במען שלישי. לספרייה יש שתי מתודות נוחות שממופות ישירות לתבנית ”מַעֲנֵן את ההתקן, הצבע על register, ואז קרא או כתוב נתונים“.

קריאת register:

addr = 0x68
reg  = 0x3B
data = i2c.readfrom_mem(addr, reg, 6)
# `data` is 6 bytes starting at register 0x3B

קריאה בודדת זו מנפיקה START, שולחת את מען ההתקן עם ביט הכתיבה, שולחת את מען ה-register (repeated start), שולחת מחדש את המען עם ביט הקריאה, קוראת 6 בתים, מבצעת NACK על האחרון, ומבצעת STOP. ההתקן רואה את רצף ”הקריאה מ-register“ הסטנדרטי; הסקריפט רואה קריאת Python אחת.

כתיבה ל-register:

i2c.writeto_mem(addr, 0x6B, b"\x00")

אותו רצף בתמונת ראי: START, מען + כתיבה, register, בית מטען, STOP.

עבור התקנים שבהם מען ה-register ברוחב שני בתים (חלק מה-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" עושה זאת כ-big-endian מסומן – סדר הבתים שרוב חיישני ה-I2C משתמשים בו, ההפוך מהמוסכמה של little-endian הנפוצה בקישורי UART. struct.unpack() תמיד מחזיר tuple (איבר אחד לכל תו פורמט), ולכן [0] שולף את הערך היחיד.

3.24.4. גישה ברמה נמוכה יותר

עבור התקנים שאינם עוקבים אחר תבנית מפת ה-register, שתי מתודות ברמה נמוכה יותר נותנות שליטה ישירה על שלב המען ועל כיוון הנתונים:

i2c.writeto(addr, b"\x01\x02\x03")    # write 3 bytes to addr
data = i2c.readfrom(addr, 4)          # read 4 bytes from addr

אלה שימושיות עבור התקנים עם ממשקי command-stream (תצוגות, חלק משעוני זמן אמת) שבהם פונקציות הנוחות של מפת ה-register אינן ממש מתאימות.

3.24.5. Bit-banging כשאין בלוק חומרה זמין

machine.SoftI2C מספק את אותו API על פיני GPIO שרירותיים, ומבצע bit-banging לפרוטוקול בתוכנה:

from machine import SoftI2C, Pin

i2c = SoftI2C(scl=Pin("P5"), sda=Pin("P4"), freq=100_000)

השתמשו בו כאשר ההתקן צריך להיות על פינים שאינם מחווטים לבלוק I2C חומרתי, או כאשר כל בלוקי החומרה בשימוש. לולאת ה-bit-bang איטית יותר מ-I2C חומרתי וה-CPU נשאר עסוק במשך כל הטרנזקציה, אך ה-API זהה ל-I2C כך שקוד קיים עובר על ידי שינוי הבנאי בלבד.