본문 바로가기
임베디드 개발/리눅스 디바이스 드라이버

TCA9538 데이터시트 및 리눅스 드라이버 분석 (Tested on Raspberry Pi 4)

by eteo 2025. 4. 17.

 

 

데이터시트 분석

 

TCA9538은 I2C 및 SMBus를 지원하는 8채널 I/O Expander이다.

 

 

1. 주요 특징

 

  • 주요 기능은 I2C to Parallel Port 확장 기능
  • Open-Drain Active Low 인터럽트 출력 기능 제공
  • Active Low 리셋 입력 기능 제공
  • 공급 전압 : 1.65V~5.5V
  • 5V tolerant I/O 포트
  • 최대 400kHz(Fast Mode)의 I2C 통신 속도 지원
  • 전원 공급 시 기본적으로 모든 채널이 Input으로 설정됨
  • 2개의 하드웨어 주소 핀(A0, A1)을 통해 최대 4개의 디바이스 연결 가능
  • 전원 인가 시 글리치 없음
  • SCL/SDA 라인에 노이즈 필터 존

 

 

2. 블락 다이어그램

 

 

 

 

 

 

3. 핀아웃

 

 

 

 

 

 

 

4. 제어 방법

1) Supported I2C Mode

  • Standard Mode : 100kHz
  • Fast Mode : 400kHz

 

 

 

2) I2C Slave Address

기본 주소는 7h70부터 7h73까지이며 A0, A1 핀을 VCC에 연결하느냐 Ground에 연결하느냐에 따라 달라진다.

 

 

 

3) Control Register (= Command Byte)

Command Byte는 I2C 데이터 전송 시 Address Byte 다음으로 등장하는 두 번째 바이트로, 어떤 레지스터를 읽거나 쓸 것인지 선택하는 역할을 한다.

 

 

 

4) 레지스터 종류

  • Input Port Register (0) : 읽기 전용, P0~P7 핀의 현재 입력 상태를 읽는 용도
  • Output Port Register (1) : 읽기/쓰기, P0~P7 핀의 출력을 설정하는 용도
  • Polarity Inversion Register (2) : 읽기/쓰기, 입력으로 설정된 핀의 입력 값의 논리 반전 여부를 설정하는 용도
  • Configuration Register (3) : 읽기/쓰기, 핀을 입력 또는 출력으로 설정하는 용도

 

 

 

 

 

5) 인터럽트 동작 방식

인터럽트 설정을 위한 별도 레지스터가 존재하지 않으며, 인터럽트 기능은 입력 핀(P0~P7)의 상태 변화를 감지하는 하드웨어 로직에 의해 자동으로 동작한다.

 

 

  • /INT 출력 핀은 Open-Drain 구조이므로 외부 풀업 저항이 필요하다.
  • Input으로 설정된 P0~P7 핀에서 신호의 rising edge 또는 falling edge가 발생하면 /INT핀이 Low가 되어 활성화된다.
  • 인터럽트 발생 이후 Input Port register(0)을 읽으면 인터럽트 상태가 해제되어 /INT핀이 다시 High 상태로 돌아간다.
  • Output으로 설정된 핀은 인터럽트를 발생시키지 않는다.
  • Output으로 설정된 핀을 도중에 Input으로 변경하는 경우 false interrupt가 발생할 가능성이 있다.

 

 

 

 

6) I2C Write 패킷 구조

 

[START] [Slave Address + W(0)] [ACK(S)] [Command Byte] [ACK(S)] [Data Byte] [ACK(S)] [STOP]

 

 

 

7) I2C Read 패킷 구조

 

Read 동작은 먼저 읽을 레지스터를 지정하고 그 다음 읽는 두 단계에 걸쳐 수행된다. 보통 I2C 트랜잭션은 START 조건으로 시작하고 STOP 조건으로 끝나는데, 이 경우에는 도중에 STOP을 발생시키지 않는 REPEATED START 방식을 사용하는 것을 볼 수 있다.

[START] [Slave Address + W(0)] [ACK(S)] [Command Byte] [ACK(S)]
[RESTART] [Slave Address + R(1)] [ACK(S)] [Data Byte] [ACK(M)] ... [Data Byte] [NACK(M)] [STOP]

 

 

 

 

 

 

 

 

 

 

 


Raspberry Pi 4B에서 테스트

 

 

1. 배선

Raspberry Pi 4 TCA9538
3.3V VCC
GND GND
SDA (핀 3, GPIO2) SDA (Pull-up to Vcc)
SCL (핀 5, GPIO3) SCL  (Pull-up to Vcc)
GND A0
GND A1
- /RESET (Pull-up to Vcc)

 

 

 

 

2. i2c 활성화 및 디바이스 트리 적용

 

 

/boot/firmware/config.txt 파일을 에디터로 열고 아래 항목을 편집한다.

$ sudo vim /boot/firmware/config.txt

 

 

# I2C 활성화를 위해 아래 라인 주석 해제 (기본적으로 I2C1 사용)
dtparam=i2c_arm=on

# 아래 문장 추가하여 디바이스 트리 적용
dtoverlay=pca953x,addr=0x70,pca9538

 

$ sudo reboot

 

 

 

 

3. 드라이버 로드 및 동작 확인

$ dmesg | grep pca

 

$ i2cdetect -y 1

 

 

libgpiod를 사용하면 TCA9538(i2c-1, 0x70) 장치가 gpiochip2 컨트롤러가 등록되었고 그 하위에 8개의 gpio line이 존재하는 것을 확인할 수 있다. 이렇게 된건 DT의 gpio-controller; 속성 때문인데 이 속성은 해당 노드가 gpio 컨트롤러임을 의미한다.

 

💡 참고.

  • libgpiod는 기존 /sys/class/gpio/를 통한 GPIO방식이 deprecated 되면서 최신 커널에서 권장되는 GPIO 제어 방식이다.
  • gpioget, gpioset 명령을 사용하면 내부적으로 핀 direction이 자동 설정된다. (라즈베리 파이에서는 가능한데 일부 플랫폼에서는 핀 입출력 방향을 시스템 동작 중에 못바꾸는 경우도 있다.)
$ gpiodetect
$ gpioinfo gpiochip2

 

 

# P0핀 읽기
$ gpioget gpiochip2 0
# P1핀 쓰기
$ gpioset gpiochip2 1=0
$ gpioset gpiochip2 1=1

 

 

 

💡 참고.

  • 현재 gpiochip2의 line name이 unamed로 되어있는데 DT에서 gpio-controller; 속성 아래에 gpio-line-names 속성으로 문자열 리스트 값을 추가하면 line name을 지정할 수 있다.
  • GPIO 컨트롤러 노드에 gpio-hog{}; 노드를 추가하면 line의 입출력 방향과 초기값을 설정할 수 있다. 다만, gpio-hog{}; 노드 추가 시 해당 리소스가 컨트롤러에 의해 선점된 상태이기 때문에 libgpiod로 제어하는데 제한이 있을 수 있다. 자세한 설정 방법은 /Documentation/devicetree/bindings/gpio/gpio.txt 문서를 참조한다.
  • 예시.
gpio-controller@00000000 {
	compatible = "foo";
	reg = <0x00000000 0x1000>;
	gpio-controller;
	#gpio-cells = <2>;
	ngpios = <18>;
	gpio-reserved-ranges = <0 4>, <12 2>;
	gpio-line-names = "MMC-CD", "MMC-WP", "VDD eth", "RST eth", "LED R",
		"LED G", "LED B", "Col A", "Col B", "Col C", "Col D",
		"Row A", "Row B", "Row C", "Row D", "NMI button",
		"poweroff", "reset";
}

qe_pio_a: gpio-controller@1400 {
    compatible = "fsl,qe-pario-bank-a", "fsl,qe-pario-bank";
    reg = <0x1400 0x18>;
    gpio-controller;
    #gpio-cells = <2>;

    line_b {
        gpio-hog;
        gpios = <6 0>;
        output-low;
        line-name = "foo-bar-gpio";
    };
};