[ BBB - yocto (12) ] I2C

0) 준비물

1. mpu 6050

2. MPU-6000-Register-Map1.pdf
3. MPU-6000-Datasheet1.pdf


노드 속성을 알고 싶다면, 아래 사이트에 들어가서 compatible에 맞는 드라이버를 찾으면 된다.

https://www.kernel.org/doc/Documentation/devicetree/bindings


am33xx의 i2c compatible에 omap4-i2c를 사용하므로 아래 2개 사이트에서 i2c 노드에 대한 설명을 볼 수 있다.

https://www.kernel.org/doc/Documentation/devicetree/bindings/i2c/ti%2Comap4-i2c.yaml

https://www.kernel.org/doc/Documentation/devicetree/bindings/i2c/i2c-omap.txt



 1) I2C란?

- i2c는 일종의 통신 프로토콜이다. i2c에는 데이터 버스(SDA)와 클럭신호(SCL)가 존재한다.

- slave와 master가 있다. 통신을 할 때 Master는 하나만 있을 수 있으며 슬레이브 디바이스는 여러개가 존재할 수 있다.

- 정해진 통신 절차를 사용해 통신한다. 

- i2c 슬레이브의 주소는 10비트를 사용할 수도 있고, 7비트를 사용할 수 도 있다.

- 한번에 여러 바이트를 읽거나 쓸 수 있다. 아래는 데이터의 읽기/쓰기 시퀀스이다.


1. 1byte 동작


- 여기서 쓰기 비트는0, 읽기 비트는 1에 해당한다. 모든 데이터는 클럭에 맞춰서 읽히거나 쓰여진다.


- 시작 상태는 SDA(시리얼 데이터 버스)가 HIGH에서 LOW로 떨어지는 것을 의미한다.


- 멈춤 상태는 SDA가 LOW에서 HIGH로 올라오는 것을 의미한다.


- ACK(acknowledgement) 신호는 응답 문자로 불리며, 신호를 잘 받았다는 말이다. 나중에 TCP/IP 프로토콜을 다룰 것인데, 여기서도 핸드쉐이킹 과정을 거치며 ACK, NACK 신호를 교환한다.


- NACK(not acknowledgement) 신호는 신호의 비정상 수신 시 전송되는 신호다. 다른 의미로 쓰이기도 하는데, 여기서는 통신이 끝났음을 slave에 알리기 위해서 쓰였다.


-read 동작에서 시작상태, slave 주소 읽기 비트를 한번 더 보낸다. 이후 slave가 ACK를 보낸뒤 데이터를 보내며, master는 NACK를 slave로 보내 통신이 끝났음을 알린다.


-write 동작에서 데이터를 보내면, slave가 ACK를 보내고, 멈춤상태 이후에 통신이 끝난다.


2. 2byte 동작


- read 동작에서 master가 ACK를 slave에 전송하면, 데이터를 한번 더 읽어 낼 수 있고 NACK를 전송해 통신이 끝났음을 슬레이브에 알린다.


- write 동작에서 data가 전송된 이후 ack를 보내는데, 데이터를 한번 더 보내고, ACK 신호를 수신받은 뒤에 SDA를 멈춤 상태로 만든다.


3. i2c의 회로 구성



- i2c의 시리얼 데이터 라인과 클럭 라인에는 반드시 풀업 저항이 걸려주어야 한다.


- 이유는 i2c에 연결된 시리얼 데이터 라인과 클럭 라인이 둘 다 오픈 드레인을 쓰기 때문이다.


- 예를 들어보자, 여기서 모스펫이 NMOS로 디바이스 1이 마스터, 2가 슬레이브라 가정하고 진행하겠다. 


- 먼저 클럭 신호를 만드는 경우다. 디바이스 1이 클럭 신호를 생성하려고 한다. 만약 풀업 레지스터가 없다고 가정한다면, 시리얼 클럭 라인의 신호를 제대로 만들 수 없다.

 시리얼 클럭 버스는  SCLKN1 OUT이 high 상태 일 때, nmos 게이트가 열려 라인이 LOW 상태다. 그러나 SCLKN1 OUT이 low로 떨어지면 모스펫 채널이 닫히고, 시리얼 클럭 라인은 floating 상태가 된다.

즉 시리얼 클럭 라인에 high 신호를 만들어 낼 수 없다.


- 데이터 라인도 마찬가지다. high 신호를 만들 수가 없다.


- 그러나, 풀업 저항을 달아주면, 모스펫의 채널이 닫히게 되는 경우 floating 상태 대신, 버스 라인의 전압이 HIGH 상태가 된다.


- i2c 표준에 의하면 이처럼 오픈드레인을 사용하도록 되어있으며, 각 버스 라인에 풀업 저항이 필요하다.






2) 디바이스 오버레이 설정하기

이제 위에 학습한 내용을 바탕으로 오버레이를 설정해보자

우선 비글본의 회로도이다. 


사용할 핀헤더는 녹색 박스로 된 2개의 부분이다.

저 핀을 검색해서 코어 부분을 확인해보자.

이 모드 0번을 복사하여, am3358-ep.pdf에서 검색해 준다.

SPI0_D1, SPI0_CS0를 mode0에서 사용할 수 있는 핀인데, 이 핀을 i2c로 쓰려면 모드 2번으로 멀티플랙싱 해주어야 함을 알 수 있다.

앞으로 비글본에서 핀 멀티플랙싱 설정을 하려면 이렇게 찾아주면 된다.


자 이제 오버레이를 수정하여 핀 멀티플랙싱 설정을 해주자.

/dts-v1/;
/plugin/;

#include <dt-bindings/pinctrl/am33xx.h>

&{/chosen} {
	overlays{
		BB-USER_DEVICE_TREE.kernel = __TIMESTAMP__;
	};
};


/{
	fragment@0 {
		target = <&am33xx_pinmux>;
		__overlay__{
			uart1_pins: uart1-pins {
				pinctrl-single,pins = <
				AM33XX_PADCONF(AM335X_PIN_UART1_RXD, PIN_INPUT, MUX_MODE0)
				AM33XX_PADCONF(AM335X_PIN_UART1_TXD, PIN_OUTPUT, MUX_MODE0)
				>;
			};
			
			i2c1_pins: i2c1-pins {
				pinctrl-single,pins = <
				AM33XX_PADCONF(AM335X_PIN_SPI0_CS0, PIN_INPUT_PULLUP, MUX_MODE2)
				AM33XX_PADCONF(AM335X_PIN_SPI0_D1, PIN_INPUT_PULLUP, MUX_MODE2)
				>;
			};
		};
	};

	fragment@1{
		target = <&uart1>;
		__overlay__{
			pinctrl-names = "default";
			pinctrl-0 = <&uart1_pins>;
			status = "okay";
		};
	};

	fragment@2{
		target = <&i2c1>;
		__overlay__{
			pinctrl-names = "default";
			pinctrl-0 = <&i2c1_pins>;

			status = "okay";
			clock-frequency = <400000>;
		};
	};
};

i2c 노드의 새로운 속성 clock-frequency에 대해 볼 수 있다. 

i2c 클럭 주파수를 설하는 속성이다.

클럭 주파수는 os가 동작하는 도중에 변경하는 것은 불가능하며, 디바이스 트리의 설정을 계속 따른다.


$ bitbake -c compile -f virtual/kernel
$ bitbake -c deploy -f virtual/kernel

오버레이를 컴파일하고, 배포 태스크를 실행해 주자


여기서 input pullup을 사용했는데 회로적으로 open drain을 i2c 디바이스가 사용하기 때문이다.

output pullup의 경우 devices or resources are busy 오류가 뜨니 사용하지 말자. 원인은 버스 충돌로 output pullup이 논리적으로 구현되었기 때문에 버스에 접근하면 무조건 오류가 나는 것으로 보인다.


부트 섹터 1번의 BB-USER_DEVICE_TREE.dtbo를 교체해 주자.


/dev밑의 파일을 보면 i2c-1이 활성화 된 것을 볼 수 있다.




3) 어플단에서 i2c 제어하기

2가지 방식으로 i2c를 제어할 수 있다.


첫번째는 unistd.h의 read/write 함수를 방식이다. 두번째는 ioctl을 사용하고 linux/i2c-dev.h, linux/i2c.h 두가지 헤더를 사용하는 방식이다.


1. unistd.h를 사용하는 방식

- i2c 제어가 read/write 함수를 사용해서 이뤄진다. 아래는 동작 과정이다.

(1) "/dev/i2c-[버스 번호]" 를 open 함수로 열어준다.


(2) ioctl로 slave 주소를 설정한다. (I2C_SLAVE 를 ioctl __request에 넣어주고, 슬레이브 주소를 인자로 넘겨준다.)


(3) read/write 함수를 사용해 읽기 쓰기 동작을 진행해 준다. 파일 디스크립터는 (1)의 값이다.


(4) 읽기 동작의 경우 write 함수를 먼저 사용하여, 슬레이브 주소를 i2c 디바이스에 전송한다. 그 이후 read 함수를 사용해 읽어낼 버퍼 주소와, 버퍼 길이를 전달해 주면 된다.

 이때, read를 여러번 쓰지 말고 한번에 쓰도록 한다. (write로 레지스터 주소 쓴 이후, read 함수 여러번 써서 읽는 건 불가능하다.)


(5) 쓰기 동작의 경우, write 함수로 한번에 전송해야 한다. 레지스터 주소 +  쓸 데이터 값을 포함하여 한 버퍼에 집어 넣고 그 버퍼의 크기를 인자로 전달해 준다.



2. i2c.h와 i2c-dev.h를 사용하는 방식

- ioctl 함수만 사용한다. i2c_msg, i2c_rdwr_ioctl_data 구조체를 사용하여 전송한다.


- 구조체 i2c_msg의 맴버

맴버
가능한 값
addri2c 슬레이브 주소슬레이브 주소의 값, 7비트 혹은 10비트 값이 가능하며,
10비트로 사용하기 위해서는 flags맴버 변수에 i2C_M_TEN을 설정해야 한다.
flagsi2c 플레그I2C_M_RD : 읽기 모드, 쓰기로 사용하려면 0으로 설정하자.
lenbuf 주소의 데이터의 길이buf 주소의 데이터의 전체 길이가 들어간다.
bufi2c 버퍼 주소i2c에 읽거나 쓸 버퍼의 주소 값을 넘겨준다.


-구조체 i2c_rdwr_ioctl_data의 멤버 변수

맴버가능한 값
msgs메시지 주소i2c_msg 구조체 배열의 주소다. i2c_msg는 여러개 사용 가능하다.
nmsgs메시지 숫자i2c 메시지의 숫자


-아래는 동작 과정이다.

(1) "/dev/i2c-[버스 번호]" 를 open 함수로 열어준다.


(2)  i2c_msg 멤버에서 읽기를 할 것인지, 쓰기를 할 것인지를 flags에 정해주고, addr에는 i2c 주소를, buf에서는 쓸 버퍼의 길이를, len에는 버퍼 길이를 넣어준다.


(3) 읽기 동작의 경우, i2c_msg의 배열 크기를 2로 설정한다. 

인덱스0의 i2c_msg에는 flags에 0을 입력해 쓰기 모드로 설정한다. 그리고 buf에는 레지스터 주소가 저장된 버퍼의 주소를, len에는 buf 길이(바이트 단위)를 입력해 준다.

flags에 I2C_M_RD를 넣어 읽기 모드로 설정한다. 그리고 buf에 쓸 주소가 저장될 버퍼를, len에는 버퍼 크기를 입력해준다.

이후 i2c_rdwr_ioctl_data 구조체의 변수에 i2c_msg 주소와 메시지 수를 입력하고 ioctl로  I2C_RDWR을 __request로, 추가 가변 인자로 i2c_rdwr_ioctl_data 구조체 주소를 넘겨주면 된다.


(4) 쓰기 동작의 경우, i2c_msg의 배열 크기를 1로 설정한다. 

i2c_msg에는 flags에 0을 입력해 쓰기 모드로 설정한다. 그리고 buf에는 레지스터 주소와 쓸 데이터가 저장된 버퍼의 주소를, len에는 buf 길이(바이트 단위)를 입력해 준다.

이후 i2c_rdwr_ioctl_data 구조체의 변수에 i2c_msg 주소와 메시지 수를 입력하고 ioctl로  I2C_RDWR을 __request로, 추가 가변 인자로 i2c_rdwr_ioctl_data 구조체 주소를 넘겨주면 된다.





4) mpu6050 어플리케이션 구현

아래는 어플리케이션의 동작 과정이다.

1. 슬립 모드 해제

2. 가속도 범위 설정

3. x, y, z + 온도 값 읽기 (burst read 사용, burst read는 2바이트 씩 레지스터 값을 읽는 것을 의미한다.)


https://github.com/leejugy/mpu6050_i2c


레지스터 맵을 보면서 제어해 보는 것을 추천한다.





5) 결과


가속도 센서를 뒤집어보거나 세워보면 가속도 값이 달라지는 것을 확인할 수 있다.


댓글

이 블로그의 인기 게시물

[ BBB - yocto (9) ] 장치트리(DEVICE TREE)

[ BBB - yocto (11) ] uart

[ BBB - yocto (5) ] makefile 작성법과 컴파일 자동화