프로그래밍/리눅스

리눅스 어플리케이션에서 I2C 사용하기

kyudoc 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