I2C는 Inter-Integrated Circuit의 약자로, 8bit 패킷을 주고받으며 간단한 입출력을 지원하는 2-Wire 인터페이스를 뜻합니다.
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 인터페이스는 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 어댑터를 가져오기 위한 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;
};
이번 튜토리얼에선 I2C를 이해하고 디바이스 클라이언트를 생성하는 것까지 완료하였습니다. 다음 시간에는 본격적인 제어 함수들을 다뤄 보도록 합시다.