3.24. I2C ในโค้ด

machine.I2C ห่อหุ้มตัวควบคุม I2C ฮาร์ดแวร์ สร้างด้วย id ของบัสและความถี่นาฬิกาที่ไม่บังคับ:

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]

นี่คือสิ่งแรกที่ควรลองหลังจากต่อสายบัสใหม่ ถ้าอุปกรณ์อยู่ในรายการ การต่อสายและ pull-up ก็ดี ถ้ารายการว่างเปล่า (หรือสั้นกว่าที่คาด) ให้สงสัย pull-up, การต่อสาย, หรือพินที่อยู่ของอุปกรณ์ก่อนที่จะสงสัยโค้ด

3.24.2. รูปแบบ memory-mapped

เซนเซอร์ I2C ส่วนใหญ่เปิดเผยสถานะของตัวเองเป็นชุด register ภายใน: ธงสถานะที่ที่อยู่หนึ่ง ไบต์การวัดที่ที่อยู่อื่น การกำหนดค่าที่ที่สาม ไลบรารีมีสองวิธีอำนวยความสะดวกที่แมปโดยตรงกับรูปแบบ "กำหนดที่อยู่อุปกรณ์, ชี้ไปที่ 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 ของ accelerometer บนเซนเซอร์ 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() ส่งคืน object bytes ดังนั้นการอ่าน WHO_AM_I จะนำดัชนีแรก (และเดียว) ด้วย [0] การอ่าน accelerometer เป็นสองไบต์ดิบที่ต้องประกอบเป็นค่า signed 16 บิตค่าเดียว; รูปแบบ struct ">h" ทำสิ่งนั้นเป็น big-endian signed -- ลำดับ byte ที่เซนเซอร์ I2C ส่วนใหญ่ใช้ ตรงข้ามกับแบบแผน little-endian ที่ใช้ทั่วไปบนลิงค์ UART struct.unpack() ส่งคืน tuple เสมอ (หนึ่งองค์ประกอบต่อตัวอักษรรูปแบบ) ดังนั้น [0] ดึงออกค่าเดียว

3.24.4. การเข้าถึงระดับต่ำกว่า

สำหรับอุปกรณ์ที่ไม่ปฏิบัติตามรูปแบบ register-map สองวิธีระดับต่ำกว่าให้การควบคุมโดยตรงเหนือเฟส address และทิศทางข้อมูล:

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

สิ่งเหล่านี้มีประโยชน์สำหรับอุปกรณ์ที่มี interface แบบ command-stream (จอแสดงผล, นาฬิกาเวลาจริงบางตัว) ที่วิธีอำนวยความสะดวก register-map ไม่พอดีนัก

3.24.5. การ bit-bang เมื่อไม่มีบล็อกฮาร์ดแวร์ว่าง

machine.SoftI2C ให้ API เดียวกันบนพิน GPIO ใดก็ได้ โดย bit-bang โปรโตคอลในซอฟต์แวร์:

from machine import SoftI2C, Pin

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

ใช้เมื่ออุปกรณ์ต้องอยู่บนพินที่ไม่ได้ต่อสายไปยังบล็อก I2C ฮาร์ดแวร์ หรือเมื่อบล็อกฮาร์ดแวร์ทุกตัวกำลังใช้งาน ลูป bit-bang ช้ากว่า I2C ฮาร์ดแวร์และ CPU ยังคงยุ่งอยู่ตลอดธุรกรรม แต่ API เหมือนกับ I2C ดังนั้นโค้ดที่มีอยู่สามารถสลับได้โดยเปลี่ยนเฉพาะ constructor