ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 리눅스 어플리케이션에서 I2C 사용하기
    프로그래밍/리눅스 2021. 3. 23. 22:05
    728x90

    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);
    	}

     

    조사한 결과는 각 비트 플레그를 조사하시면 됩니다. 각 비트 플레그의 의미는 (링크)를 참조하세요.

    728x90

    댓글

Designed by Tistory.