리눅스 I2C 슬레이브 디바이스

Posted by Lee Yunjin on May 19, 2025 · 5 mins read

I2C란?

I2C는 Inter-Integrated Circuit의 약자로, 8bit 패킷을 주고받으며 간단한 입출력을 지원하는 2-Wire 인터페이스를 뜻합니다.

I2C 버스

I2C 버스는 각각의 I2C 디바이스들이 연결된 마스터-슬레이브 구조를 의미합니다.

마스터는 슬레이브에 데이터 전송을 요청하고 슬레이브는 마스터에 데이터로 응답합니다.

이러한 구조에서는 하드웨어 장치는 절대적으로 슬레이브에 있다는 것을 알 수 있습니다.

하나의 마스터에 연결 가능한 최대 슬레이브 디바이스의 개수는 이론상 27(128)개입니다. 그러나 실제로는 24(16)개 만큼이 예약되어 있어 27-264(112)개의 최대 장치 수를 가집니다.

그런데, I2C 버스에 대해 조금 검색해 보신 분들은 I2C SmBus라는 걸 많이 들어 봤을 것입니다. SmBus가 대체 무멋의 약자인지 생각해 보지 않은 사람들도 많을 것입니다. 이것은 Standard-mode Bus 라는 뜻이고, 다른 모드도 존재합니다.

이제 버스의 종류를 알아 보도록 하겠습니다.

Mode Speed
Standard Mode(Sm) 100 kbit/s
Fast Mode(Fm) 400 kbit/s
Fast Mode Plus(Fm+) 1 Mbit/s
High-speed mode(Hs-mode) 3.4 Mbit/s
Ultra-fast speed mode(UFm) 5 Mbit/s

Fast-mode 등의 속도가 안정성보다 우선시되는 작업을 위한 고속 버스도 눈에 띕니다. 대부분의 I2C Bus는 Standard Bus인 경우가 많으나 특수한 경우를 위해 기억해 둡시다.

I2C 인터페이스

I2C 인터페이스는 SDA(Serial DATA), SCL(Serial CLOCK) 두 개의 핀을 통해 구성됩니다. 패킷 1개의 길이는 8bit입니다.

패킷 송/수신이 완료된 후에만 Stop 시그널을 보냅니다. 송/수신에 실패하는 등의 상황이 생기면 Stop 시그널이 생성되지 않았으므로 동일 패킷에 대한 Start 시그널이 다시 들어오거나 나갈 수 있습니다.

패킷은 읽기, 쓰기 값을 의미하는 초기 1비트와 7bit의 페이로드로 구성됩니다.


0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07

0x00 주소의 값이 0인 경우 I2C 동작은 쓰기 동작입니다. 0x00 주소의 값이 1인 경우 I2C 동작은 읽기 동작입니다.

I2C는 편리한 입출력 작업을 위한 2-Wire 인터패이스라고 이해하시면 편하고, 낮은 비용으로 반이중 제어가 필요한 디바이스들을 다수 관리할 수 있습니다.

I2C 디바이스 드라이버 작성

I2C 어댑터 정의

I2C 어댑터를 가져오기 위한 C 함수 원형은 다음과 같습니다.

struct i2c_adapter *i2c_get_adapter(int nr);

만약 1번 버스를 가져오고 싶다면, 다음과 같이 하면 됩니다.

struct i2c_adapter *adapter = NULL;
adapter = i2c_get_adapter(1);
if(adapter == NULL) {
    fprintf(stderr, "I2C Bus 1 is unavailable");
    //null check하기
}

보드 정보 작성

보드 정보에 대한 구조체 원형은 필드가 아주 단순하지만은 않습니다.

struct i2c_board_info {
    char type[I2C_NAME_SIZE];
    unsigned short flags;
    unsigned short addr;
    void *platform_data;
    struct dev arch_data *archdata;
    struct device_node *of_node;
    int irq;
};

당연히 이 필드를 직접 채워넣는 것은 수고롭습니다. 이런 수고를 덜어주기 위한 매크로 함수를 사용하도록 합시다.

I2C_BOARD_INFO(dev_type, dev_addr);

같은 식으로 넣어 줍니다.

여기서는 LED 제어 장치가 0x20 슬레이브 주소에 인식되었다고 가정해 봅시다.

struct i2c_board_info LED_CONTROLLER = {
    I2C_BOARD_INFO("LED_CTRL", 0x20);
}

새 디바이스 클라이언트 생성

struct i2c_client *i2c_new_device(i2c_adapter *adapter, i2c_board_info *boardinfo);

앞의 내용들을 어댑터 가져오기-> 디바이스 클라이언트 생성 순으로 적용해 봅시다.

#define SLAVE_DEVICE_NAME LED_CTRL
#define SLAVE_ADDR 0x20
#define I2C_AVAILABLE_BUS 1
static struct i2c_adapter *adapter = NULL;
static struct i2c_client  *client  = NULL;
static struct i2c_board_info info = {
    I2C_BOARD_INFO
    (
        SLAVE_DEVICE_NAME,
        SLAVE_ADDR
    )
};

static int __init driver_init(void) {
    adapter = i2c_get_adapter(I2C_AVAILABLE_BUS);
    client  = i2c__new_device(adapter, 
    return 0;
};

NEXT

이번 튜토리얼에선 I2C를 이해하고 디바이스 클라이언트를 생성하는 것까지 완료하였습니다. 다음 시간에는 본격적인 제어 함수들을 다뤄 보도록 합시다.