리눅스 어플리케이션에서 I2C 사용하기
I2C나 SPI로 통신되는 칩들은 대부분 리눅스 디바이스 드라이버로 핸들링하는게 보통이지만 테스트나 간단한 기능을 하는 칩의 경우에는 어플리케이션에서 핸들링하기도 하지요.
이번에는 리눅스 어플리케이션에서 I2C 칩을 Read/Write 하는 방법을 설명해 볼까 합니다.
아마도 대부분은 I2C 노드가 /dev 디렉터리에 i2c/0, i2c/1 ... i2c/N 이렇게 존재하거나 i2c-0, i2c-1 ... i2cN 이렇게 존재할 것입니다. 이 노드들을 open, close, ioctl, read, write와 같은 I/O 함수들로 I2C 통신을 할 수 있습니다.
읽어오기
MCU의 I2C 0번 버스에 Slave address가 A0h인 칩이 있고, Sub address가 16비트이고 데이터는 8비트로 통신되는 칩과 가정하겠습니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
const int bus = 0;
const unsigned long slave_addr = 0xA0;
const unsigned short sub_addr = 0x10;
int main()
{
int fd;
int rval;
char path[20];
unsigned char buf[2];
unsigned char rxd[1];
sprintf(path, "/dev/i2c/%d", bus);
fd = open(path, O_RDWR);
if ((fd < 0) && (errno == ENOENT)) {
sprintf(path, "/dev/i2c-%d", bus);
fd = open(path, O_RDWR);
}
if (fd < 0) {
fprintf(stderr, "Can't open file '/dev/i2c-%d' or '/dev/i2c/%d': %s\r\n", bus, bus, strerror(errno));
exit(EXIT_FAILURE);
}
// Read 1byte
ioctl(fd, I2C_SLAVE, slave_addr);
buf[0] = (sub_addr & 0xFF00) >> 8;
buf[1] = sub_addr & 0xFF;
if ((rval = write(fd, buf, 2)) < 0) {
fprintf(stderr, "Writing error! (%lX): %s\r\n", slave_addr, strerror(errno));
exit(EXIT_FAILURE);
}
if ((rval = read(fd, rxd, sizeof(unsigned char))) < 0) {
fprintf(stderr, "Reading error! (%lX): %s\r\n", slave_addr, strerror(errno));
exit(EXIT_FAILURE);
}
fprintf(stdout, "Read 1byte = 0x%02X\r\n", rxd[0]);
close(fd);
return 0;
}
위와 같이 I2C 노드를 열고 I2C 통신 표준에 따라 Slave address를 전송한 뒤 Sub address를 전송하면 read 함수로 데이터를 읽어 올 수 있습니다.
한꺼번에 여러 바이트를 전송할 수 있는 칩이라면 여러 바이트를 읽어 올 수 있습니다.
전송하기
읽어 오는 걸 하셨으니 전송하는 것은 바로 하실 수 있으실 겁니다. Sub address와 함께 데이터를 전송하시면 됩니다.
// Write
unsigned char buf[3];
ioctl(fd, I2C_SLAVE_FORCE, slave_addr);
buf[0] = (sub_addr & 0xFF00) >> 8;
buf[1] = sub_addr & 0xFF;
buf[2] = 0xAA;
if ((rval = write(fd, buf, 3)) < 0) {
fprintf(stderr, "Writing error! (%lX): %s\r\n", slave_addr, strerror(errno));
exit(EXIT_FAILURE);
}
read와 마찬가지로 한꺼번에 여러 바이트를 전송받을 수 있는 칩이라면 버퍼 크기와 전송 크기를 수정해서 여러 바이트를 전송하실 수 있습니다.
읽어오기 2
다음과 같이 데이터를 읽어 올 수도 있습니다.
// Read multi-bytes
struct i2c_msg msg[2];
struct i2c_rdwr_ioctl_data idata;
unsigned char rxd[10];
msg[0].addr = (__u16)slave_addr;
msg[0].flags = 0;
msg[0].len = 2;
msg[0].buf = (__u8 *)&sub_addr;
msg[1].addr = (__u16)slave_addr;
msg[1].flags = I2C_M_RD;
msg[1].len = (__u16)10;
msg[1].buf = (__u8 *)rxd;
idata.msgs = msg;
idata.nmsgs = 2;
if ((rval = ioctl(fd, I2C_RDWR, &idata)) < 0) {
fprintf(stderr, "IOCTL error! (%lX): %s\r\n", slave_addr, strerror(errno));
exit(EXIT_FAILURE);
}
기능(Function) 조사하기
그리고, I2C 칩이 블록 전송이 가능한지 등 지원되는 기능이 무엇인지 조사할 수 도 있습니다.
// Check funcs
unsigned long func_flags;
if ((rval = ioctl(fd, I2C_FUNCS, &func_flags)) < 0) {
fprintf(stderr, "Could not get the adapter functionality matrix: %s\r\n", strerror(errno));
exit(EXIT_FAILURE);
}
조사한 결과는 각 비트 플레그를 조사하시면 됩니다. 각 비트 플레그의 의미는 (링크)를 참조하세요.