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]

이것은 새 버스를 배선한 후 가장 먼저 시도해 볼 일입니다. 장치가 목록에 있으면 배선과 풀업이 정상입니다. 목록이 비어 있거나(또는 예상보다 짧으면) 코드를 의심하기 전에 풀업, 배선, 또는 장치의 주소 핀을 의심하세요.

3.24.2. 메모리 매핑 패턴#

대부분의 I2C 센서는 자신의 상태를 일련의 내부 레지스터로 노출합니다. 상태 플래그는 한 주소에, 측정 바이트는 다른 주소에, 구성은 또 다른 주소에 둡니다. 라이브러리에는 “장치를 주소 지정하고, 레지스터를 가리킨 다음, 데이터를 읽거나 쓴다”는 패턴에 직접 대응하는 두 가지 편의 메서드가 있습니다.

레지스터 읽기:

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

이 한 번의 호출은 START를 발행하고, 쓰기 비트와 함께 장치 주소를 보내고, 레지스터 주소를 보내고(반복 START), 읽기 비트와 함께 주소를 다시 보내고, 6바이트를 읽고, 마지막 바이트에 NACK를 보낸 뒤 STOP합니다. 장치는 표준 “레지스터에서 읽기” 시퀀스를 보고, 스크립트는 하나의 Python 호출을 봅니다.

레지스터에 쓰기:

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

이 시퀀스는 거울에 비친 듯 동일합니다: START, 주소 + 쓰기, 레지스터, 페이로드 바이트, STOP.

레지스터 주소가 2바이트 폭인 장치(일부 EEPROM과 16비트 주소 지정 메모리)의 경우 addrsize=16을 전달합니다:

data = i2c.readfrom_mem(addr, 0x0100, 32, addrsize=16)

3.24.3. 센서 읽기#

일반적인 MEMS 센서에서 WHO_AM_I 식별자와 가속도계 X축을 읽는 짧은 루프:

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 핀에서 동일한 API를 제공하며, 프로토콜을 소프트웨어로 비트뱅잉합니다:

from machine import SoftI2C, Pin

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

장치가 하드웨어 I2C 블록에 연결되지 않은 핀에 있어야 하거나, 모든 하드웨어 블록이 사용 중일 때 이를 사용하세요. 비트뱅 루프는 하드웨어 I2C보다 느리고 트랜잭션 내내 CPU가 바쁜 상태를 유지하지만, API가 I2C와 동일하므로 기존 코드는 생성자만 바꾸면 전환됩니다.