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와 동일하므로 기존 코드는 생성자만 바꾸면 전환됩니다.